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
|
||||
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue