[untested] finished models permissions
This commit is contained in:
parent
7acc292bad
commit
5ce9a808aa
3 changed files with 413 additions and 46 deletions
|
@ -3,67 +3,111 @@ Serializers for the Palto project's API v1.
|
||||||
|
|
||||||
A serializers tell the API how should a model should be serialized to be used by an external user.
|
A serializers tell the API how should a model should be serialized to be used by an external user.
|
||||||
"""
|
"""
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from Palto.Palto import models
|
from Palto.Palto import models
|
||||||
|
|
||||||
|
|
||||||
# TODO(Raphaël): Voir pour les related_name
|
# TODO: voir les relations inversées ?
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class ModelSerializerContrains(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Similar to the base ModelSerializer, but automatically check for contrains for the user
|
||||||
|
when trying to create a new instance or modifying a field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model: Type[models.ModelPermissionHelper]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# get the fields that this user can modify
|
||||||
|
field_contrains = self.Meta.model.user_fields_contrains(self.context["request"].user)
|
||||||
|
|
||||||
|
# for every constrains
|
||||||
|
for field, constrains in field_contrains.items():
|
||||||
|
# check if the value is in the constrains.
|
||||||
|
value = validated_data.get(field)
|
||||||
|
if value is not None and value not in constrains:
|
||||||
|
raise PermissionDenied(f"You are not allowed to use this value for the field {field}.")
|
||||||
|
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
# get the fields that this user can modify
|
||||||
|
field_contrains = self.Meta.model.user_fields_contrains(self.context["request"].user)
|
||||||
|
|
||||||
|
# for every constrains
|
||||||
|
for field, constrains in field_contrains.items():
|
||||||
|
# check if the value of the request is in the constrains.
|
||||||
|
value = validated_data.get(field)
|
||||||
|
if value is not None and value not in constrains:
|
||||||
|
raise PermissionDenied(f"You are not allowed to use this value for the field {field}.")
|
||||||
|
|
||||||
|
# check if the value of the already existing instance is in the constrains.
|
||||||
|
value = getattr(instance, field, None)
|
||||||
|
if value is not None and value not in constrains:
|
||||||
|
raise PermissionDenied(f"You are not allowed to use this value for the field {field}.")
|
||||||
|
|
||||||
|
# check that the user is managing the department
|
||||||
|
if instance.department not in self.context["request"].user.managing_departments:
|
||||||
|
raise PermissionDenied("You don't manage this department.")
|
||||||
|
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = ['id', 'username', 'first_name', 'last_name', 'email']
|
fields = ['id', 'username', 'first_name', 'last_name', 'email']
|
||||||
|
|
||||||
|
|
||||||
class DepartmentSerializer(serializers.ModelSerializer):
|
class DepartmentSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Department
|
model = models.Department
|
||||||
fields = ['id', 'name', 'email', 'managers']
|
fields = ['id', 'name', 'email', 'managers', 'teachers', 'students']
|
||||||
# NOTE: teachers, students
|
|
||||||
|
|
||||||
|
|
||||||
class StudentGroupSerializer(serializers.ModelSerializer):
|
class StudentGroupSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.StudentGroup
|
model = models.StudentGroup
|
||||||
fields = ['id', 'name', 'owner', 'department']
|
fields = ['id', 'name', 'owner', 'department', 'students']
|
||||||
# NOTE: students
|
|
||||||
|
|
||||||
|
|
||||||
class TeachingUnitSerializer(serializers.ModelSerializer):
|
class TeachingUnitSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TeachingUnit
|
model = models.TeachingUnit
|
||||||
fields = ['id', 'name', 'department']
|
fields = ['id', 'name', 'department', 'managers', 'teachers', 'student_groups']
|
||||||
# NOTE: managers, teachers, student_groups
|
|
||||||
|
|
||||||
|
|
||||||
class StudentCardSerializer(serializers.ModelSerializer):
|
class StudentCardSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.StudentCard
|
model = models.StudentCard
|
||||||
fields = ['id', 'uid', 'department', 'owner']
|
fields = ['id', 'uid', 'department', 'owner']
|
||||||
|
|
||||||
|
|
||||||
class TeachingSessionSerializer(serializers.ModelSerializer):
|
class TeachingSessionSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TeachingSession
|
model = models.TeachingSession
|
||||||
fields = ['id', 'start', 'duration', 'note', 'unit', 'group', 'teacher']
|
fields = ['id', 'start', 'duration', 'note', 'unit', 'group', 'teacher']
|
||||||
|
|
||||||
|
|
||||||
class AttendanceSerializer(serializers.ModelSerializer):
|
class AttendanceSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Attendance
|
model = models.Attendance
|
||||||
fields = ['id', 'date', 'student', 'session']
|
fields = ['id', 'date', 'student', 'session']
|
||||||
|
|
||||||
|
|
||||||
class AbsenceSerializer(serializers.ModelSerializer):
|
class AbsenceSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Absence
|
model = models.Absence
|
||||||
fields = ['id', 'message', 'student', 'session']
|
fields = ['id', 'message', 'student', 'session']
|
||||||
|
|
||||||
|
|
||||||
class AbsenceAttachmentSerializer(serializers.ModelSerializer):
|
class AbsenceAttachmentSerializer(ModelSerializerContrains):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.AbsenceAttachment
|
model = models.AbsenceAttachment
|
||||||
fields = ['id', 'content', 'absence']
|
fields = ['id', 'content', 'absence']
|
||||||
|
|
|
@ -158,14 +158,10 @@ class DepartmentApiTestCase(test.APITestCase):
|
||||||
|
|
||||||
self.client.force_login(student1)
|
self.client.force_login(student1)
|
||||||
|
|
||||||
"""
|
|
||||||
TODO: this test require to show the field students before creating it.
|
|
||||||
|
|
||||||
# check for a get request and that he can see the other student
|
# check for a get request and that he can see the other student
|
||||||
response = self.client.get("/api/v1/departments/")
|
response = self.client.get("/api/v1/departments/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn(serializers.UserSerializer(student2).data, response.json()["results"])
|
self.assertIn(serializers.UserSerializer(student2).data, response.json()["results"])
|
||||||
"""
|
|
||||||
|
|
||||||
# check for a post request
|
# check for a post request
|
||||||
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
|
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
|
||||||
|
@ -173,8 +169,114 @@ class DepartmentApiTestCase(test.APITestCase):
|
||||||
|
|
||||||
|
|
||||||
class StudentGroupApiTestCase(test.APITestCase):
|
class StudentGroupApiTestCase(test.APITestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
self.user_admin = factories.FakeUserFactory(is_superuser=True)
|
||||||
|
self.user_other = factories.FakeUserFactory()
|
||||||
|
|
||||||
|
# fake group creation data
|
||||||
|
self.test_manager_related = factories.FakeUserFactory()
|
||||||
|
self.test_manager_other = factories.FakeUserFactory()
|
||||||
|
|
||||||
|
self.test_teacher_owner = factories.FakeUserFactory()
|
||||||
|
self.test_teacher_other = factories.FakeUserFactory()
|
||||||
|
|
||||||
|
self.test_students_group = [factories.FakeUserFactory() for _ in range(10)]
|
||||||
|
self.test_students_other = [factories.FakeUserFactory() for _ in range(10)]
|
||||||
|
|
||||||
|
self.test_department = factories.FakeDepartmentFactory(
|
||||||
|
managers=[self.test_manager_related],
|
||||||
|
teachers=[self.test_teacher_owner, self.test_teacher_other],
|
||||||
|
students=[*self.test_students_group, *self.test_students_other],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.student_group_creation_data: dict = {
|
||||||
|
"name": "Groupe 1",
|
||||||
|
"owner": self.test_teacher_owner.pk,
|
||||||
|
"department": self.test_department.pk,
|
||||||
|
"students": map(lambda obj: obj.pk, self.test_students_group)
|
||||||
|
}
|
||||||
|
|
||||||
|
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/student_groups/")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.json()["count"], models.StudentGroup.objects.count())
|
||||||
|
|
||||||
|
# check for a post request
|
||||||
|
response = self.client.post("/api/v1/student_groups/", data=self.student_group_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/student_groups/")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
# check for a post request
|
||||||
|
response = self.client.post("/api/v1/student_groups/", data=self.student_group_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 ?
|
||||||
|
for user in (self.user_other, *self.test_students_other):
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
# check for a get request and that he can't see anything
|
||||||
|
response = self.client.get("/api/v1/student_groups/")
|
||||||
|
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/student_groups/", data=self.student_group_creation_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_permission_related(self):
|
||||||
|
""" Test the API permission for a related user """
|
||||||
|
|
||||||
|
for user in self.test_students_group:
|
||||||
|
# TODO: use reverse to get the url ?
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
# check for a get request and that he can see the students
|
||||||
|
response = self.client.get("/api/v1/student_groups/")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
list(serializers.UserSerializer(student).data for student in self.test_students_group),
|
||||||
|
response.json()["results"]["students"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# check for a post request
|
||||||
|
response = self.client.post("/api/v1/student_groups/", data=self.student_group_creation_data)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_permission_owner(self):
|
||||||
|
""" Test the API permission for the owner """
|
||||||
|
|
||||||
|
# TODO: use reverse to get the url ?
|
||||||
|
self.client.force_login(self.test_teacher_owner)
|
||||||
|
|
||||||
|
# check for a get request and that he can see the students
|
||||||
|
response = self.client.get("/api/v1/student_groups/")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
list(serializers.UserSerializer(student).data for student in self.test_students_group),
|
||||||
|
response.json()["results"]["students"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# check for a post request
|
||||||
|
response = self.client.post("/api/v1/student_groups/", data=self.student_group_creation_data)
|
||||||
|
# TODO: autorisé ?
|
||||||
|
|
||||||
class TeachingUnitApiTestCase(test.APITestCase):
|
class TeachingUnitApiTestCase(test.APITestCase):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -3,10 +3,11 @@ Models for the Palto project.
|
||||||
|
|
||||||
Models are the class that represent and abstract the database.
|
Models are the class that represent and abstract the database.
|
||||||
"""
|
"""
|
||||||
|
import operator
|
||||||
import uuid
|
import uuid
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from functools import reduce
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
@ -15,15 +16,27 @@ from django.db import models
|
||||||
from django.db.models import QuerySet, Q, F
|
from django.db.models import QuerySet, Q, F
|
||||||
|
|
||||||
|
|
||||||
class ModelPermissionHelper:
|
# TODO(Raphaël): split permissions from models for readability
|
||||||
|
# TODO(Raphaël): allow other function for permissions than in
|
||||||
|
|
||||||
|
|
||||||
|
class ModelPermissionHelper:
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if the user can create a new instance of this object
|
Return True if the user can create a new instance of this object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
return user.is_superuser
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
"""
|
||||||
|
Return the list of fields in that model that the user can modify
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -76,17 +89,24 @@ class User(AbstractUser, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: propriétaire d'établissement
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a department, allow him to create user
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
queryset = QuerySet()
|
||||||
|
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
queryset = cls.objects.all()
|
queryset = cls.objects.all()
|
||||||
else:
|
else:
|
||||||
queryset = QuerySet()
|
# all the users related to a department the user is managing
|
||||||
# TODO: propriétaire d'établissement
|
if user.managing_departments.count() > 0:
|
||||||
|
queryset = cls.objects.all()
|
||||||
|
|
||||||
return queryset.order_by("pk")
|
return queryset.order_by("pk")
|
||||||
|
|
||||||
|
@ -157,8 +177,10 @@ class Department(models.Model, ModelPermissionHelper):
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
queryset = cls.objects.all()
|
queryset = cls.objects.all()
|
||||||
else:
|
else:
|
||||||
queryset = QuerySet()
|
queryset = cls.objects.filter(
|
||||||
# TODO: propriétaire d'établissement ?
|
# if the user is the manager of the department
|
||||||
|
managers=user,
|
||||||
|
)
|
||||||
|
|
||||||
return queryset.order_by("pk")
|
return queryset.order_by("pk")
|
||||||
|
|
||||||
|
@ -198,9 +220,27 @@ class StudentGroup(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: department managers can create group
|
return True
|
||||||
# TODO: can teacher create group ?
|
|
||||||
|
# if the user is managing a department
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if the user is teaching a department
|
||||||
|
if user.teaching_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# the managers and teachers can only interact with their departments
|
||||||
|
"department": (user.managing_departments | user.teaching_departments).distinct()
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -267,8 +307,23 @@ class TeachingUnit(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: allow department manager
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a department
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# the managers can only interact with their departments
|
||||||
|
"department": user.managing_departments
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -324,8 +379,27 @@ class StudentCard(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: Allow new student cards by department managers ?
|
return True
|
||||||
|
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# the managers can only interact with their departments
|
||||||
|
"department": user.managing_departments,
|
||||||
|
# the owner of the card can be any students in a department that is managed by the user
|
||||||
|
"owner": reduce(
|
||||||
|
operator.or_,
|
||||||
|
(department.students.all() for department in user.managing_departments)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -390,8 +464,64 @@ class TeachingSession(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: Allow new teaching session by managers or teachers
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a department
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a unit
|
||||||
|
if user.managing_units.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if the user is teaching a unit
|
||||||
|
if user.teaching_units.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# the managers can only interact with their departments
|
||||||
|
"department": (user.managing_departments | user.teaching_departments).distinct(),
|
||||||
|
"teacher":
|
||||||
|
# the teacher can be any teacher in a department that the user is managing
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(department.teachers.all() for department in user.managing_departments)
|
||||||
|
) | reduce(
|
||||||
|
# or a teacher in a unit that the user is managing
|
||||||
|
operator.or_,
|
||||||
|
(department.teachers.all() for department in user.managing_units)
|
||||||
|
) | (
|
||||||
|
# or the user itself
|
||||||
|
User.objects.filter(pk=user.pk)
|
||||||
|
),
|
||||||
|
"unit":
|
||||||
|
# the unit can be any unit in the department that the user is managing
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(department.teaching_units.all() for department in user.managing_departments)
|
||||||
|
) | (
|
||||||
|
# or the units that the user is teaching
|
||||||
|
user.teaching_sessions
|
||||||
|
),
|
||||||
|
"group":
|
||||||
|
# any group of a department where the user is a manager
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(department.student_groups for department in user.managing_departments)
|
||||||
|
) |
|
||||||
|
# any group where the user is a manager or a teacher of a unit
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(unit.student_groups for unit in (user.managing_units | user.teaching_units))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -465,8 +595,67 @@ class Attendance(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: Allow new attendance by managers or teachers
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a department
|
||||||
|
if user.managing_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if the user is managing a unit
|
||||||
|
if user.managing_units.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# if the user is teaching a unit
|
||||||
|
if user.teaching_units.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# the managers can only interact with their departments
|
||||||
|
"department": user.managing_departments | user.teaching_departments,
|
||||||
|
"student":
|
||||||
|
# student can be any student from a department the user is managing or teaching
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(
|
||||||
|
department.students.all()
|
||||||
|
for department in (user.managing_departments | user.teaching_departments)
|
||||||
|
)
|
||||||
|
) |
|
||||||
|
# or any student from a unit the user is managing or teaching
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(
|
||||||
|
student_group.students.all()
|
||||||
|
for unit in (user.managing_units | user.teaching_units)
|
||||||
|
for student_group in unit.student_groups
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"session":
|
||||||
|
# the session can be any session where the user is managing the department
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(
|
||||||
|
unit.sessions
|
||||||
|
for department in user.managing_departments
|
||||||
|
for unit in department.teaching_units
|
||||||
|
)
|
||||||
|
) |
|
||||||
|
# or where is the user is a teacher
|
||||||
|
reduce(
|
||||||
|
operator.or_,
|
||||||
|
(
|
||||||
|
unit.sessions
|
||||||
|
for unit in (user.teaching_units | user.managing_units)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -517,7 +706,7 @@ class Absence(models.Model, ModelPermissionHelper):
|
||||||
message: str = models.TextField()
|
message: str = models.TextField()
|
||||||
|
|
||||||
department: Department = models.ForeignKey(to=Department, on_delete=models.CASCADE, related_name="absences")
|
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="absences")
|
||||||
start: datetime = models.DateTimeField()
|
start: datetime = models.DateTimeField()
|
||||||
end: datetime = models.DateTimeField()
|
end: datetime = models.DateTimeField()
|
||||||
|
|
||||||
|
@ -554,8 +743,25 @@ class Absence(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: Allow new absence by students
|
return True
|
||||||
|
|
||||||
|
# if the user is a student
|
||||||
|
if user.studying_departments.count() > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# all the departments the user is studying in
|
||||||
|
"department": user.studying_departments,
|
||||||
|
# the student itself
|
||||||
|
"student": User.objects.filter(pk=user.pk),
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
@ -617,8 +823,23 @@ class AbsenceAttachment(models.Model, ModelPermissionHelper):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_create(cls, user: "User") -> bool:
|
def can_user_create(cls, user: "User") -> bool:
|
||||||
# if the requesting user is admin
|
# if the requesting user is admin
|
||||||
return user.is_superuser
|
if user.is_superuser:
|
||||||
# TODO: Allow new absence attachment by students
|
return True
|
||||||
|
|
||||||
|
# if the user is a student
|
||||||
|
if user.objects.count():
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_fields_contrains(cls, user: "User") -> dict[str, QuerySet]:
|
||||||
|
# if the user is admin, no contrains
|
||||||
|
if user.is_superuser:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# all the departments the user is studying in
|
||||||
|
"absence": user.absences,
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
def all_editable_by_user(cls, user: "User") -> QuerySet:
|
||||||
|
|
Loading…
Reference in a new issue