implemented editable and visible filters by user for permissions
This commit is contained in:
parent
5a5e83aff1
commit
a731a03133
8 changed files with 780 additions and 248 deletions
30
.github/workflows/django-test.yaml
vendored
Normal file
30
.github/workflows/django-test.yaml
vendored
Normal 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
|
|
@ -9,6 +9,9 @@ from django.contrib import admin
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: plus de list_filter sur "department" ?
|
||||||
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
@admin.register(models.User)
|
@admin.register(models.User)
|
||||||
class AdminUser(admin.ModelAdmin):
|
class AdminUser(admin.ModelAdmin):
|
||||||
|
@ -58,8 +61,9 @@ class AdminAttendance(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(models.Absence)
|
@admin.register(models.Absence)
|
||||||
class AdminAbsence(admin.ModelAdmin):
|
class AdminAbsence(admin.ModelAdmin):
|
||||||
list_display = ("id", "message", "student")
|
list_display = ("id", "message", "student", "department", "start", "end")
|
||||||
search_fields = ("id", "message", "student")
|
search_fields = ("id", "message", "student", "department", "start", "end")
|
||||||
|
list_filter = ("department", "start", "end")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.AbsenceAttachment)
|
@admin.register(models.AbsenceAttachment)
|
||||||
|
|
|
@ -10,201 +10,200 @@ from Palto.Palto import models
|
||||||
|
|
||||||
|
|
||||||
class UserPermission(permissions.BasePermission):
|
class UserPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.User) -> bool:
|
# TODO: has_permission check for authentication
|
||||||
# if the requesting user is admin, allow all
|
|
||||||
if request.user.is_superuser:
|
def has_permission(self, request, view) -> bool:
|
||||||
|
if request.method in permissions.SAFE_METHODS:
|
||||||
|
# for reading, allow everybody
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if models.User.can_user_create(request.user):
|
||||||
# if the user is in one of the same department as the requesting user, allow read
|
# for writing, only allowed users
|
||||||
if obj in models.Department.multiple_related_users(request.user.related_departments):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class DepartmentPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.Department) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
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 True
|
||||||
|
|
||||||
return False
|
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):
|
class StudentGroupPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.StudentGroup) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the student is in the group, allow read
|
# for reading, allow everybody
|
||||||
if obj in request.user.student_groups:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if the user is a teacher from the same department, allow read
|
if models.StudentGroup.can_user_create(request.user):
|
||||||
if obj.department in request.user.teaching_departments:
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class TeachingUnitPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.TeachingUnit) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the user is related to the department, allow read
|
# for reading, allow everybody
|
||||||
if obj.department in request.user.related_departments:
|
return True
|
||||||
|
|
||||||
|
if models.TeachingUnit.can_user_create(request.user):
|
||||||
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class StudentCardPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.StudentCard) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the owner of the card is the user, allow read
|
# for reading, allow everybody
|
||||||
if obj.owner is request.user:
|
return True
|
||||||
|
|
||||||
|
if models.StudentCard.can_user_create(request.user):
|
||||||
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class TeachingSessionPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.TeachingSession) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the user was one of the student, allow read
|
# for reading, allow everybody
|
||||||
if request.user in obj.group.students:
|
return True
|
||||||
|
|
||||||
|
if models.TeachingSession.can_user_create(request.user):
|
||||||
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class AttendancePermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.Attendance) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the user was the student, allow read
|
# for reading, allow everybody
|
||||||
if obj.student is request.user:
|
return True
|
||||||
|
|
||||||
|
if models.Attendance.can_user_create(request.user):
|
||||||
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class AbsencePermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.Absence) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the unit of the session is managed by the user, allow read
|
# for reading, allow everybody
|
||||||
if obj.session.unit in request.user.managing_units:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if the teacher is the user, allow read
|
if models.Absence.can_user_create(request.user):
|
||||||
if obj.session.teacher is request.user:
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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):
|
class AbsenceAttachmentPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj: models.AbsenceAttachment) -> bool:
|
def has_permission(self, request, view) -> 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
|
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# if the unit of the session is managed by the user, allow read
|
# for reading, allow everybody
|
||||||
if obj.absence.session.unit in request.user.managing_units:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if the teacher is the user, allow read
|
if models.AbsenceAttachment.can_user_create(request.user):
|
||||||
if obj.absence.session.teacher is request.user:
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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)
|
|
@ -4,96 +4,121 @@ Tests for the Palto project's API v1.
|
||||||
Everything to test the API v1 is described here.
|
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):
|
class TokenJwtTestCase(test.APITestCase):
|
||||||
@staticmethod
|
|
||||||
def test_creation():
|
|
||||||
"""
|
"""
|
||||||
Test the creation of users
|
Test the JWT token creation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = factories.FakeUserFactory()
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 DepartmentTestCase(test.TestCase):
|
class DepartmentApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of departments
|
|
||||||
"""
|
|
||||||
|
|
||||||
department = factories.FakeDepartmentFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class StudentGroupTestCase(test.TestCase):
|
class StudentGroupApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of student groups
|
|
||||||
"""
|
|
||||||
|
|
||||||
student_group = factories.FakeStudentGroupFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class TeachingUnitTestCase(test.TestCase):
|
class TeachingUnitApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of teaching units
|
|
||||||
"""
|
|
||||||
|
|
||||||
teaching_unit = factories.FakeTeachingUnitFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class StudentCardTestCase(test.TestCase):
|
class StudentCardApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of student cards
|
|
||||||
"""
|
|
||||||
|
|
||||||
student_card = factories.FakeStudentCardFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class TeachingSessionTestCase(test.TestCase):
|
class TeachingSessionApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of teaching sessions
|
|
||||||
"""
|
|
||||||
|
|
||||||
teaching_session = factories.FakeTeachingSessionFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class AttendanceTestCase(test.TestCase):
|
class AttendanceApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of attendances
|
|
||||||
"""
|
|
||||||
|
|
||||||
attendance = factories.FakeAttendanceFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class AbsenceTestCase(test.TestCase):
|
class AbsenceApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of absences
|
|
||||||
"""
|
|
||||||
|
|
||||||
absence = factories.FakeAbsenceFactory()
|
|
||||||
|
|
||||||
|
|
||||||
class AbsenceAttachmentTestCase(test.TestCase):
|
class AbsenceAttachmentApiTestCase(test.APITestCase):
|
||||||
@staticmethod
|
pass
|
||||||
def test_creation():
|
|
||||||
"""
|
|
||||||
Test the creation of absence attachments
|
|
||||||
"""
|
|
||||||
|
|
||||||
absence_attachment = factories.FakeAbsenceAttachmentFactory()
|
|
||||||
|
|
|
@ -14,53 +14,71 @@ from ... import models
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
queryset = models.User.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.UserPermission]
|
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
|
serializer_class = serializers.DepartmentSerializer
|
||||||
queryset = models.Department.objects.all()
|
|
||||||
permission_classes = [permissions.DepartmentPermission]
|
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
|
serializer_class = serializers.StudentGroupSerializer
|
||||||
queryset = models.StudentGroup.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.StudentGroupPermission]
|
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
|
serializer_class = serializers.TeachingUnitSerializer
|
||||||
queryset = models.TeachingUnit.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.TeachingUnitPermission]
|
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
|
serializer_class = serializers.StudentCardSerializer
|
||||||
queryset = models.StudentCard.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.StudentCardPermission]
|
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
|
serializer_class = serializers.TeachingSessionSerializer
|
||||||
queryset = models.TeachingSession.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.TeachingSessionPermission]
|
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
|
serializer_class = serializers.AttendanceSerializer
|
||||||
queryset = models.Attendance.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.AttendancePermission]
|
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
|
serializer_class = serializers.AbsenceSerializer
|
||||||
queryset = models.Absence.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.AbsencePermission]
|
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
|
serializer_class = serializers.AbsenceAttachmentSerializer
|
||||||
queryset = models.AbsenceAttachment.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated, permissions.AbsenceAttachmentPermission]
|
permission_classes = [IsAuthenticated, permissions.AbsenceAttachmentPermission]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return models.AbsenceAttachment.all_visible_by_user(self.request.user)
|
||||||
|
|
|
@ -196,6 +196,6 @@ class FakeAbsenceAttachmentFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.AbsenceAttachment
|
model = models.AbsenceAttachment
|
||||||
|
|
||||||
content = factory.django.FileField()
|
content: str = factory.django.FileField()
|
||||||
|
|
||||||
absence = factory.SubFactory(FakeAbsenceFactory)
|
absence: models.Absence = factory.SubFactory(FakeAbsenceFactory)
|
||||||
|
|
|
@ -5,16 +5,41 @@ Models are the class that represent and abstract the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
from abc import abstractmethod
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
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.
|
A user.
|
||||||
|
|
||||||
|
@ -46,8 +71,38 @@ class User(AbstractUser):
|
||||||
|
|
||||||
return self.multiple_related_departments([self])
|
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.
|
A scholar department.
|
||||||
|
|
||||||
|
@ -89,8 +144,33 @@ class Department(models.Model):
|
||||||
|
|
||||||
return self.multiple_related_users([self])
|
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.
|
A student group.
|
||||||
|
|
||||||
|
@ -113,8 +193,51 @@ class StudentGroup(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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.
|
A teaching unit.
|
||||||
|
|
||||||
|
@ -139,8 +262,48 @@ class TeachingUnit(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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.
|
A student card.
|
||||||
|
|
||||||
|
@ -156,8 +319,44 @@ class StudentCard(models.Model):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} id={str(self.id)[:8]} owner={self.owner.username!r}>"
|
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.
|
A session of a teaching unit.
|
||||||
|
|
||||||
|
@ -186,8 +385,52 @@ class TeachingSession(models.Model):
|
||||||
def end(self) -> datetime:
|
def end(self) -> datetime:
|
||||||
return self.start + self.duration
|
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.
|
A student attendance to a session.
|
||||||
|
|
||||||
|
@ -217,8 +460,53 @@ class Attendance(models.Model):
|
||||||
f">"
|
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.
|
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)
|
id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36)
|
||||||
message: str = models.TextField()
|
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")
|
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):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
f"<{self.__class__.__name__} "
|
f"<{self.__class__.__name__} "
|
||||||
f"id={str(self.id)[:8]} "
|
f"id={str(self.id)[:8]} "
|
||||||
|
f"department={self.department} "
|
||||||
f"student={self.student.username} "
|
f"student={self.student.username} "
|
||||||
|
f"start={self.start} "
|
||||||
|
f"end={self.end}"
|
||||||
f">"
|
f">"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[{str(self.id)[:8]}] {self.student}"
|
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.
|
An attachment to a student justified absence.
|
||||||
|
|
||||||
|
@ -257,3 +611,50 @@ class AbsenceAttachment(models.Model):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} id={str(self.id)[:8]} content={self.content!r}>"
|
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")
|
||||||
|
|
|
@ -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.
|
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.
|
# 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()
|
||||||
|
|
Loading…
Reference in a new issue