implemented editable and visible filters by user for permissions

This commit is contained in:
Faraphel 2023-12-03 16:57:34 +01:00
parent 5a5e83aff1
commit a731a03133
8 changed files with 780 additions and 248 deletions

30
.github/workflows/django-test.yaml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Django CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.12]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: |
python manage.py test

View file

@ -9,6 +9,9 @@ from django.contrib import admin
from . import models
# TODO: plus de list_filter sur "department" ?
# Register your models here.
@admin.register(models.User)
class AdminUser(admin.ModelAdmin):
@ -58,8 +61,9 @@ class AdminAttendance(admin.ModelAdmin):
@admin.register(models.Absence)
class AdminAbsence(admin.ModelAdmin):
list_display = ("id", "message", "student")
search_fields = ("id", "message", "student")
list_display = ("id", "message", "student", "department", "start", "end")
search_fields = ("id", "message", "student", "department", "start", "end")
list_filter = ("department", "start", "end")
@admin.register(models.AbsenceAttachment)

View file

@ -10,201 +10,200 @@ from Palto.Palto import models
class UserPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.User) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
# TODO: has_permission check for authentication
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if request.method in permissions.SAFE_METHODS:
# if the user is in one of the same department as the requesting user, allow read
if obj in models.Department.multiple_related_users(request.user.related_departments):
return True
if models.User.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.User.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.User.all_editable_by_user(request.user)
class DepartmentPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.Department) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the group department is managed by the user, allow all
if obj in request.user.managing_departments:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# allow read to everybody
# for reading, allow everybody
return True
if models.Department.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Department.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Department.all_editable_by_user(request.user)
class StudentGroupPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.StudentGroup) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the group department is managed by the user, allow all
if obj.department in request.user.managing_departments:
return True
# if the user is the owner of the group, allow all
if obj.owner is request.user:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the student is in the group, allow read
if obj in request.user.student_groups:
return True
# for reading, allow everybody
return True
# if the user is a teacher from the same department, allow read
if obj.department in request.user.teaching_departments:
return True
if models.StudentGroup.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.StudentGroup.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.StudentGroup.all_editable_by_user(request.user)
class TeachingUnitPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.TeachingUnit) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the teaching unit department is managed by the user, allow all
if obj.department in request.user.managing_departments:
return True
# if the teaching unit is managed by the user, allow all
if obj in request.user.managing_units:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the user is related to the department, allow read
if obj.department in request.user.related_departments:
return True
# for reading, allow everybody
return True
if models.TeachingUnit.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.TeachingUnit.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.TeachingUnit.all_editable_by_user(request.user)
class StudentCardPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.StudentCard) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the card department is managed by the user, allow all
if obj.department in request.user.managing_departments:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the owner of the card is the user, allow read
if obj.owner is request.user:
return True
# for reading, allow everybody
return True
if models.StudentCard.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.StudentCard.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.StudentCard.all_editable_by_user(request.user)
class TeachingSessionPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.TeachingSession) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the teacher is the user, allow all
if obj.teacher is request.user:
return True
# if the unit of the session is managed by the user, allow all
if obj.unit in request.user.managing_units:
return True
# if the department of the session is managed by the user, allow all
if obj.unit.department in request.user.managing_departments:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the user was one of the student, allow read
if request.user in obj.group.students:
return True
# for reading, allow everybody
return True
if models.TeachingSession.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.TeachingSession.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.TeachingSession.all_editable_by_user(request.user)
class AttendancePermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.Attendance) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the teacher is the user, allow all
if obj.session.teacher is request.user:
return True
# if the unit of the session is managed by the user, allow all
if obj.session.unit in request.user.managing_units:
return True
# if the department of the session is managed by the user, allow all
if obj.session.unit.department in request.user.managing_departments:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the user was the student, allow read
if obj.student is request.user:
return True
# for reading, allow everybody
return True
if models.Attendance.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Attendance.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Attendance.all_editable_by_user(request.user)
class AbsencePermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.Absence) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the department of the session is managed by the user, allow all
if obj.session.unit.department in request.user.managing_departments:
return True
# if the user was the student, allow all
if obj.student is request.user:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the unit of the session is managed by the user, allow read
if obj.session.unit in request.user.managing_units:
return True
# for reading, allow everybody
return True
# if the teacher is the user, allow read
if obj.session.teacher is request.user:
return True
if models.Absence.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Absence.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Absence.all_editable_by_user(request.user)
class AbsenceAttachmentPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj: models.AbsenceAttachment) -> bool:
# if the requesting user is admin, allow all
if request.user.is_superuser:
return True
# if the department of the session is managed by the user, allow all
if obj.absence.session.unit.department in request.user.managing_departments:
return True
# if the user was the student, allow all
if obj.absence.student is request.user:
return True
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# if the unit of the session is managed by the user, allow read
if obj.absence.session.unit in request.user.managing_units:
return True
# for reading, allow everybody
return True
# if the teacher is the user, allow read
if obj.absence.session.teacher is request.user:
return True
if models.AbsenceAttachment.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.AbsenceAttachment.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.AbsenceAttachment.all_editable_by_user(request.user)

View file

@ -4,96 +4,121 @@ Tests for the Palto project's API v1.
Everything to test the API v1 is described here.
"""
from django import test
from rest_framework import status
from rest_framework import test
from Palto.Palto import models, factories
from Palto.Palto import factories
from Palto.Palto.api.v1 import serializers
class UserTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of users
"""
user = factories.FakeUserFactory()
class TokenJwtTestCase(test.APITestCase):
"""
Test the JWT token creation
"""
class DepartmentTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of departments
"""
class UserApiTestCase(test.APITestCase):
# fake user data for creations test
USER_CREATION_DATA: dict = {
"username": "billybob",
"first_name": "Billy",
"last_name": "Bob",
"email": "billy.bob@billybob.fr"
}
department = factories.FakeDepartmentFactory()
def setUp(self):
self.user_admin = factories.FakeUserFactory(is_superuser=True)
self.user_other = factories.FakeUserFactory()
def test_permission_admin(self):
""" Test the API permission for an administrator """
# TODO: use reverse to get the url ?
self.client.force_login(self.user_admin)
# check for a get request
response = self.client.get("/api/v1/users/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
# check for a post request
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_permission_anonymous(self):
""" Test the API permission for an anonymous user """
# TODO: use reverse to get the url ?
self.client.logout()
# check for a get request
response = self.client.get("/api/v1/users/")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
# check for a post request
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_permission_unrelated(self):
""" Test the API permission for an unrelated user """
# TODO: use reverse to get the url ?
self.client.force_login(self.user_other)
# check for a get request and that he can't see anybody
response = self.client.get("/api/v1/users/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 0)
# check for a post request
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_permission_related(self):
""" Test the API permission for a related user """
# TODO: use reverse to get the url ?
student1, student2 = factories.FakeUserFactory(), factories.FakeUserFactory()
department = factories.FakeDepartmentFactory(students=(student1, student2))
self.client.force_login(student1)
# check for a get request and that he can see the other student
response = self.client.get("/api/v1/users/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn(serializers.UserSerializer(student2).data, response.json()["results"])
# check for a post request
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class StudentGroupTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of student groups
"""
student_group = factories.FakeStudentGroupFactory()
class DepartmentApiTestCase(test.APITestCase):
pass
class TeachingUnitTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of teaching units
"""
teaching_unit = factories.FakeTeachingUnitFactory()
class StudentGroupApiTestCase(test.APITestCase):
pass
class StudentCardTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of student cards
"""
student_card = factories.FakeStudentCardFactory()
class TeachingUnitApiTestCase(test.APITestCase):
pass
class TeachingSessionTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of teaching sessions
"""
teaching_session = factories.FakeTeachingSessionFactory()
class StudentCardApiTestCase(test.APITestCase):
pass
class AttendanceTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of attendances
"""
attendance = factories.FakeAttendanceFactory()
class TeachingSessionApiTestCase(test.APITestCase):
pass
class AbsenceTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of absences
"""
absence = factories.FakeAbsenceFactory()
class AttendanceApiTestCase(test.APITestCase):
pass
class AbsenceAttachmentTestCase(test.TestCase):
@staticmethod
def test_creation():
"""
Test the creation of absence attachments
"""
class AbsenceApiTestCase(test.APITestCase):
pass
absence_attachment = factories.FakeAbsenceAttachmentFactory()
class AbsenceAttachmentApiTestCase(test.APITestCase):
pass

View file

@ -14,53 +14,71 @@ from ... import models
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
queryset = models.User.objects.all()
permission_classes = [IsAuthenticated, permissions.UserPermission]
def get_queryset(self):
return models.User.all_visible_by_user(self.request.user)
class DepartmentViewSet(UserViewSet):
class DepartmentViewSet(viewsets.ModelViewSet):
serializer_class = serializers.DepartmentSerializer
queryset = models.Department.objects.all()
permission_classes = [permissions.DepartmentPermission]
def get_queryset(self):
return models.Department.all_visible_by_user(self.request.user)
class StudentGroupViewSet(UserViewSet):
class StudentGroupViewSet(viewsets.ModelViewSet):
serializer_class = serializers.StudentGroupSerializer
queryset = models.StudentGroup.objects.all()
permission_classes = [IsAuthenticated, permissions.StudentGroupPermission]
def get_queryset(self):
return models.StudentGroup.all_visible_by_user(self.request.user)
class TeachingUnitViewSet(UserViewSet):
class TeachingUnitViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TeachingUnitSerializer
queryset = models.TeachingUnit.objects.all()
permission_classes = [IsAuthenticated, permissions.TeachingUnitPermission]
def get_queryset(self):
return models.TeachingUnit.all_visible_by_user(self.request.user)
class StudentCardViewSet(UserViewSet):
class StudentCardViewSet(viewsets.ModelViewSet):
serializer_class = serializers.StudentCardSerializer
queryset = models.StudentCard.objects.all()
permission_classes = [IsAuthenticated, permissions.StudentCardPermission]
def get_queryset(self):
return models.StudentCard.all_visible_by_user(self.request.user)
class TeachingSessionViewSet(UserViewSet):
class TeachingSessionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TeachingSessionSerializer
queryset = models.TeachingSession.objects.all()
permission_classes = [IsAuthenticated, permissions.TeachingSessionPermission]
def get_queryset(self):
return models.TeachingSession.all_visible_by_user(self.request.user)
class AttendanceViewSet(UserViewSet):
class AttendanceViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AttendanceSerializer
queryset = models.Attendance.objects.all()
permission_classes = [IsAuthenticated, permissions.AttendancePermission]
def get_queryset(self):
return models.Attendance.all_visible_by_user(self.request.user)
class AbsenceViewSet(UserViewSet):
class AbsenceViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AbsenceSerializer
queryset = models.Absence.objects.all()
permission_classes = [IsAuthenticated, permissions.AbsencePermission]
def get_queryset(self):
return models.Absence.all_visible_by_user(self.request.user)
class AbsenceAttachmentViewSet(UserViewSet):
class AbsenceAttachmentViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AbsenceAttachmentSerializer
queryset = models.AbsenceAttachment.objects.all()
permission_classes = [IsAuthenticated, permissions.AbsenceAttachmentPermission]
def get_queryset(self):
return models.AbsenceAttachment.all_visible_by_user(self.request.user)

View file

@ -196,6 +196,6 @@ class FakeAbsenceAttachmentFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.AbsenceAttachment
content = factory.django.FileField()
content: str = factory.django.FileField()
absence = factory.SubFactory(FakeAbsenceFactory)
absence: models.Absence = factory.SubFactory(FakeAbsenceFactory)

View file

@ -5,16 +5,41 @@ Models are the class that represent and abstract the database.
"""
import uuid
from abc import abstractmethod
from datetime import datetime, timedelta
from typing import Iterable
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models import QuerySet, Q
from django.db.models import QuerySet, Q, F
class User(AbstractUser):
class ModelPermissionHelper:
@classmethod
@abstractmethod
def can_user_create(cls, user: "User") -> bool:
"""
Return True if the user can create a new instance of this object
"""
@classmethod
@abstractmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
"""
Return True if the user can edit this object
"""
@classmethod
@abstractmethod
def all_visible_by_user(cls, user: "User") -> QuerySet:
"""
Return True if the user can see this object
"""
class User(AbstractUser, ModelPermissionHelper):
"""
A user.
@ -46,8 +71,38 @@ class User(AbstractUser):
return self.multiple_related_departments([self])
# permissions
class Department(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: propriétaire d'établissement
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = QuerySet()
# TODO: propriétaire d'établissement
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
# if the user is in one of the same department as the requesting user
queryset = Department.multiple_related_users(user.related_departments)
return queryset.order_by("pk")
class Department(models.Model, ModelPermissionHelper):
"""
A scholar department.
@ -89,8 +144,33 @@ class Department(models.Model):
return self.multiple_related_users([self])
# permissions
class StudentGroup(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = QuerySet()
# TODO: propriétaire d'établissement ?
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
# everybody can see all the departments
queryset = cls.objects.all()
return queryset.order_by("pk")
class StudentGroup(models.Model, ModelPermissionHelper):
"""
A student group.
@ -113,8 +193,51 @@ class StudentGroup(models.Model):
def __str__(self):
return self.name
# permissions
class TeachingUnit(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: department managers can create group
# TODO: can teacher create group ?
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the owner of the group, allow write
Q(owner=user) |
# if the user is a department manager, allow write
Q(department__managers=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the owner of the group, allow read
Q(owner=user) |
# if the user is one of the student, allow read
Q(students=user) |
# if the user is a department manager, allow read
Q(department__managers=user) |
# if the user is one of the teachers, allow read
Q(department__teachers=user)
).distinct()
return queryset.order_by("pk")
class TeachingUnit(models.Model, ModelPermissionHelper):
"""
A teaching unit.
@ -139,8 +262,48 @@ class TeachingUnit(models.Model):
def __str__(self):
return self.name
# permissions
class StudentCard(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: allow department manager
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is a manager of the department, allow write
Q(department__managers=user) |
# if the user is the manager of the unit, allow write
Q(managers=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is a manager of the department, allow read
Q(department__managers=user) |
# if the user is the manager of the unit, allow read
Q(managers=user) |
# if the department is related to the user, allow read
Q(department=user.related_departments)
).distinct()
return queryset.order_by("pk")
class StudentCard(models.Model, ModelPermissionHelper):
"""
A student card.
@ -156,8 +319,44 @@ class StudentCard(models.Model):
def __repr__(self):
return f"<{self.__class__.__name__} id={str(self.id)[:8]} owner={self.owner.username!r}>"
# permissions
class TeachingSession(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: Allow new student cards by department managers ?
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is a manager of the department
Q(department__managers=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the owner
Q(owner=user) |
# if the user is a manager of the department
Q(department__managers=user)
).distinct()
return queryset.order_by("pk")
class TeachingSession(models.Model, ModelPermissionHelper):
"""
A session of a teaching unit.
@ -186,8 +385,52 @@ class TeachingSession(models.Model):
def end(self) -> datetime:
return self.start + self.duration
# permissions
class Attendance(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: Allow new teaching session by managers or teachers
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the teacher, allow write
Q(teacher=user) |
# if the user is managing the unit, allow write
Q(unit__managers=user) |
# if the user is managing the department, allow write
Q(unit__department__managers=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the teacher, allow read
Q(teacher=user) |
# if the user is managing the unit, allow read
Q(unit__managers=user) |
# if the user is managing the department, allow read
Q(unit__department__managers=user) |
# if the user is part of the group, allow read
Q(group__students=user)
).distinct()
return queryset.order_by("pk")
class Attendance(models.Model, ModelPermissionHelper):
"""
A student attendance to a session.
@ -217,8 +460,53 @@ class Attendance(models.Model):
f">"
)
# permissions
class Absence(models.Model):
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: Allow new attendance by managers or teachers
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user was the teacher, allow write
Q(session__teacher=user) |
# if the user is manager of the unit, allow write
Q(session__unit__managers=user) |
# if the user is manager of the department, allow write
Q(session__unit__department__managers=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user was the teacher, allow read
Q(session__teacher=user) |
# if the user is manager of the unit, allow read
Q(session__unit__managers=user) |
# if the user is manager of the department, allow read
Q(session__unit__department__managers=user) |
# if the user is the student, allow read
Q(student=user)
).distinct()
return queryset.order_by("pk")
class Absence(models.Model, ModelPermissionHelper):
"""
A student justified absence to a session.
@ -228,22 +516,88 @@ class Absence(models.Model):
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
message: str = models.TextField()
department: Department = models.ForeignKey(to=Department, on_delete=models.CASCADE, related_name="absences")
student: User = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name="absented_sessions")
sessions: TeachingSession = models.ManyToManyField(to=TeachingSession, blank=True, related_name="absences")
start: datetime = models.DateTimeField()
end: datetime = models.DateTimeField()
def __repr__(self):
return (
f"<{self.__class__.__name__} "
f"id={str(self.id)[:8]} "
f"student={self.student.username}"
f"department={self.department} "
f"student={self.student.username} "
f"start={self.start} "
f"end={self.end}"
f">"
)
def __str__(self):
return f"[{str(self.id)[:8]}] {self.student}"
# properties
class AbsenceAttachment(models.Model):
def related_sessions(self) -> QuerySet[TeachingSession]:
"""
Return the sessions that match the user absence
"""
return TeachingSession.objects.filter(
# every session where the student participate
Q(group__students=self.student) &
# every session that start between the start and the end of our absence
Q(start__range=(self.start, self.end))
).distinct()
# permissions
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: Allow new absence by students
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the student, allow write
Q(student=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the student, allow read
Q(student=user) |
# if the user is related with the session, allow read
(
# if the sessions start between the start and the end of the absence
Q(department__teaching_units__sessions__start__range=(F("start"), F("end"))) &
(
# the user is a manager of the department
Q(department__managers=user) |
# the user is a manager of the unit
Q(department__teaching_units__teachers=user) |
# the user is the teacher of the session
Q(department__teaching_units__sessions__teacher=user)
)
)
).distinct()
return queryset.order_by("pk")
class AbsenceAttachment(models.Model, ModelPermissionHelper):
"""
An attachment to a student justified absence.
@ -257,3 +611,50 @@ class AbsenceAttachment(models.Model):
def __repr__(self):
return f"<{self.__class__.__name__} id={str(self.id)[:8]} content={self.content!r}>"
# permissions
@classmethod
def can_user_create(cls, user: "User") -> bool:
# if the requesting user is admin
return user.is_superuser
# TODO: Allow new absence attachment by students
@classmethod
def all_editable_by_user(cls, user: "User") -> QuerySet:
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the student, allow write
Q(absence__student=user)
).distinct()
return queryset.order_by("pk")
@classmethod
def all_visible_by_user(cls, user: "User"):
if user.is_superuser:
# if the requesting user is admin
queryset = cls.objects.all()
else:
queryset = cls.objects.filter(
# if the user is the student, allow read
Q(absence__student=user) |
# if the user is related with the session, allow read
(
# if the sessions start between the start and the end of the absence
Q(absence__department__teaching_units__sessions__start__range=(F("start"), F("end"))) &
(
# the user is a manager of the department
Q(absence__department__managers=user) |
# the user is a manager of the unit
Q(absence__department__teaching_units__teachers=user) |
# the user is the teacher of the session
Q(absence__department__teaching_units__sessions__teacher=user)
)
)
).distinct()
return queryset.order_by("pk")

View file

@ -4,6 +4,61 @@ Tests for the Palto project.
Tests allow to easily check after modifying the logic behind a feature that everything still work as intended.
"""
from django.test import TestCase
from django import test
from Palto.Palto import factories
# Create your tests here.
class UserTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeUserFactory()
class DepartmentTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeDepartmentFactory()
class StudentGroupTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeStudentGroupFactory()
class TeachingUnitTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeTeachingUnitFactory()
class StudentCardTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeStudentCardFactory()
class TeachingSessionTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeTeachingSessionFactory()
class AttendanceTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeAttendanceFactory()
class AbsenceTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeAbsenceFactory()
class AbsenceAttachmentTestCase(test.TestCase):
@staticmethod
def test_creation():
factories.FakeAbsenceAttachmentFactory()