implemented editable and visible filters by user for permissions

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

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

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

View file

@ -9,6 +9,9 @@ from django.contrib import admin
from . import models 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)

View file

@ -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
return True
if models.TeachingUnit.can_user_create(request.user):
# for writing, only allowed users
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
return True
if models.StudentCard.can_user_create(request.user):
# for writing, only allowed users
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
return True
if models.TeachingSession.can_user_create(request.user):
# for writing, only allowed users
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
return True
if models.Attendance.can_user_create(request.user):
# for writing, only allowed users
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)

View file

@ -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 JWT token creation
""" """
Test the creation of users
"""
user = factories.FakeUserFactory()
class DepartmentTestCase(test.TestCase): class UserApiTestCase(test.APITestCase):
@staticmethod # fake user data for creations test
def test_creation(): USER_CREATION_DATA: dict = {
""" "username": "billybob",
Test the creation of departments "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): class DepartmentApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of student groups
"""
student_group = factories.FakeStudentGroupFactory()
class TeachingUnitTestCase(test.TestCase): class StudentGroupApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of teaching units
"""
teaching_unit = factories.FakeTeachingUnitFactory()
class StudentCardTestCase(test.TestCase): class TeachingUnitApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of student cards
"""
student_card = factories.FakeStudentCardFactory()
class TeachingSessionTestCase(test.TestCase): class StudentCardApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of teaching sessions
"""
teaching_session = factories.FakeTeachingSessionFactory()
class AttendanceTestCase(test.TestCase): class TeachingSessionApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of attendances
"""
attendance = factories.FakeAttendanceFactory()
class AbsenceTestCase(test.TestCase): class AttendanceApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of absences
"""
absence = factories.FakeAbsenceFactory()
class AbsenceAttachmentTestCase(test.TestCase): class AbsenceApiTestCase(test.APITestCase):
@staticmethod pass
def test_creation():
"""
Test the creation of absence attachments
"""
absence_attachment = factories.FakeAbsenceAttachmentFactory()
class AbsenceAttachmentApiTestCase(test.APITestCase):
pass

View file

@ -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)

View file

@ -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)

View file

@ -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"student={self.student.username}" f"department={self.department} "
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")

View file

@ -4,6 +4,61 @@ Tests for the Palto project.
Tests allow to easily check after modifying the logic behind a feature that everything still work as intended. 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()