diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index 049e80b..761ef10 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -61,11 +61,15 @@ class User(AbstractUser, ModelPermissionHelper): id: uuid.UUID = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, max_length=36) def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} username={self.username!r}>" + return f"<{self.__class__.__name__} id={self.short_id} username={self.username!r}>" def __str__(self): return f"{self.first_name} {self.last_name.upper()}" + @property + def short_id(self) -> str: + return str(self.id)[:8] + @staticmethod def multiple_related_departments(users: Iterable["User"]) -> QuerySet["Department"]: """ @@ -141,11 +145,15 @@ class Department(models.Model, ModelPermissionHelper): students = models.ManyToManyField(to=User, blank=True, related_name="studying_departments") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + @staticmethod def multiple_related_users(departments: Iterable["Department"]) -> QuerySet["User"]: """ @@ -212,22 +220,26 @@ class StudentGroup(models.Model, ModelPermissionHelper): students = models.ManyToManyField(to=User, blank=True, related_name="student_groups") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validators def clean(self): super().clean() # owner check - if self.department not in self.owner.teaching_departments: + if self.department not in self.owner.teaching_departments.all(): raise ValidationError("The owner is not related to the department.") # students check - if not all(self.department in student.studying_departments for student in self.students.all()): + if not all(self.department in student.studying_departments.all() for student in self.students.all()): raise ValidationError("A student is not related to the department.") # permissions @@ -254,9 +266,9 @@ class StudentGroup(models.Model, ModelPermissionHelper): return { # the user can only interact with a related departments - "department": lambda data: user.managing_departments | user.teaching_departments, + "department": lambda data: (user.managing_departments | user.teaching_departments).all(), # the owner must be a teacher or a manager of this department - "owner": lambda data: data["department"].managers | data["department"].teachers, + "owner": lambda data: (data["department"].managers | data["department"].teachers).all(), } @classmethod @@ -314,26 +326,30 @@ class TeachingUnit(models.Model, ModelPermissionHelper): student_groups = models.ManyToManyField(to=StudentGroup, blank=True, related_name="studying_units") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} name={self.name!r}>" + return f"<{self.__class__.__name__} id={self.short_id} name={self.name!r}>" def __str__(self): return self.name + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validations def clean(self): super().clean() # managers check - if not all(self.department in manager.managing_departments for manager in self.managers.all()): + if not all(self.department in manager.managing_departments.all() for manager in self.managers.all()): raise ValidationError("A manager is not related to the department.") # teachers check - if not all(self.department in teacher.teaching_departments for teacher in self.teachers.all()): + if not all(self.department in teacher.teaching_departments.all() for teacher in self.teachers.all()): raise ValidationError("A teacher is not related to the department.") # student groups check - if not all(self.department in student_group.department for student_group in self.student_groups.all()): + if not all(self.department in student_group.department.all() for student_group in self.student_groups.all()): raise ValidationError("A student group is not related to the department.") # permissions @@ -356,7 +372,7 @@ class TeachingUnit(models.Model, ModelPermissionHelper): return { # a user can only interact with a related departments - "department": lambda data: user.managing_departments | user.teaching_departments + "department": lambda data: (user.managing_departments | user.teaching_departments).all() } @classmethod @@ -406,7 +422,11 @@ class StudentCard(models.Model, ModelPermissionHelper): owner: User = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="student_cards") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} owner={self.owner.username!r}>" + return f"<{self.__class__.__name__} id={self.short_id} owner={self.owner.username!r}>" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # validations @@ -414,7 +434,7 @@ class StudentCard(models.Model, ModelPermissionHelper): super().clean() # owner check - if self.department not in self.owner.studying_departments: + if self.department not in self.owner.studying_departments.all(): raise ValidationError("The student is not related to the department.") # permissions @@ -436,7 +456,7 @@ class StudentCard(models.Model, ModelPermissionHelper): return { # a user can only interact with a related departments - "department": lambda field, data: field in user.managing_departments, + "department": lambda field, data: field in user.managing_departments.all(), } @classmethod @@ -488,11 +508,15 @@ class TeachingSession(models.Model, ModelPermissionHelper): teacher = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="teaching_sessions") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} unit={self.unit.name!r} start={self.start}>" + return f"<{self.__class__.__name__} id={self.short_id} unit={self.unit.name!r} start={self.start}>" def __str__(self): return f"{self.unit.name} ({self.start})" + @property + def short_id(self) -> str: + return str(self.id)[:8] + @property def end(self) -> datetime: return self.start + self.duration @@ -502,12 +526,12 @@ class TeachingSession(models.Model, ModelPermissionHelper): def clean(self): super().clean() - # group check - if self.unit.department not in self.group.department: + # department check + if self.unit.department != self.group.department: raise ValidationError("The group is not related to the unit department.") # teacher check - if self.unit not in self.teacher.teaching_units: + if self.unit not in self.teacher.teaching_units.all(): raise ValidationError("The teacher is not related to the unit.") # permissions @@ -545,7 +569,7 @@ class TeachingSession(models.Model, ModelPermissionHelper): user.teaching_units | # all the units of the department the user is managing TeachingUnit.objects.filter(pk__in=user.managing_departments.values("teaching_units")) - ) + ).all() } @classmethod @@ -609,19 +633,23 @@ class Attendance(models.Model, ModelPermissionHelper): def __repr__(self): return ( f"<{self.__class__.__name__} " - f"id={str(self.id)[:8]} " + f"id={self.short_id} " f"student={self.student.username} " - f"session={str(self.session.id)[:8]}" + f"session={self.session.short_id}" f">" ) + @property + def short_id(self) -> str: + return str(self.id)[:8] + # validations def clean(self): super().clean() # student check - if self.student not in self.session.group.students: + if self.student not in self.session.group.students.all(): raise ValidationError("The student is not related to the student group.") # permissions @@ -662,7 +690,7 @@ class Attendance(models.Model, ModelPermissionHelper): pk__in=user.managing_departments.values("teaching_units") ).values("sessions") ) - ) + ).all() } @classmethod @@ -721,7 +749,7 @@ class Absence(models.Model, ModelPermissionHelper): def __repr__(self): return ( f"<{self.__class__.__name__} " - f"id={str(self.id)[:8]} " + f"id={self.short_id} " f"department={self.department} " f"student={self.student.username} " f"start={self.start} " @@ -730,7 +758,11 @@ class Absence(models.Model, ModelPermissionHelper): ) def __str__(self): - return f"[{str(self.id)[:8]}] {self.student}" + return f"[{self.short_id}] {self.student}" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # validations @@ -738,7 +770,7 @@ class Absence(models.Model, ModelPermissionHelper): super().clean() # student check - if self.department not in self.student.studying_departments: + if self.department not in self.student.studying_departments.all(): raise ValidationError("The student is not related to the department.") # properties @@ -775,7 +807,7 @@ class Absence(models.Model, ModelPermissionHelper): return { # all the departments the user is studying in - "department": lambda data: user.studying_departments, + "department": lambda data: user.studying_departments.all(), } @classmethod @@ -831,7 +863,11 @@ class AbsenceAttachment(models.Model, ModelPermissionHelper): absence = models.ForeignKey(to=Absence, on_delete=models.CASCADE, related_name="attachments") def __repr__(self): - return f"<{self.__class__.__name__} id={str(self.id)[:8]} content={self.content!r}>" + return f"<{self.__class__.__name__} id={self.short_id} content={self.content!r}>" + + @property + def short_id(self) -> str: + return str(self.id)[:8] # permissions @@ -853,7 +889,7 @@ class AbsenceAttachment(models.Model, ModelPermissionHelper): return { # all the departments the user is studying in - "absence": lambda data: user.absences, + "absence": lambda data: user.absences.all(), } @classmethod diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html index 177ba14..6ed7d5f 100644 --- a/Palto/Palto/templates/Palto/profile.html +++ b/Palto/Palto/templates/Palto/profile.html @@ -1,9 +1,74 @@ {% extends "Palto/base.html" %} +{% load dict_tags %} {% block body %}
{{ profile.username }} {{ profile.email }} {% if profile.is_superuser %}Administrator{% endif %} + + {# user related departments table #} + + {% for department, profile_department_data in profile_departments_data.items %} + + {# department name #} + + {# relation information #} + + + {% endfor %} +
{{ department.name }} + + {# user managing the department #} + {% if profile_department_data|dict_get:"is_manager" %} + + + + + {% endif %} + {# user managing units #} + {% with managing_units=profile_department_data|dict_get:"managing_units" %} + {% if managing_units|length > 0 %} + + + + + {% endif %} + {% endwith %} + {# user teaching units #} + {% with teaching_units=profile_department_data|dict_get:"teaching_units" %} + {% if teaching_units|length > 0 %} + + + + + {% endif %} + {% endwith %} + {# user studying groups #} + {% with student_groups=profile_department_data|dict_get:"student_groups" %} + {% if student_groups|length > 0 %} + + + + + {% endif %} + {% endwith %} +
Responsable de Département/
Responsable d'UE + {% for managing_unit in managing_units %} + {{ managing_unit.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Enseignant + {% for teaching_unit in teaching_units %} + {{ teaching_unit.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
Groupe Étudiant + {% for student_group in student_groups %} + {{ student_group.name }} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
+
{% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session.html b/Palto/Palto/templates/Palto/teaching_session.html index 5a6c99c..c71e13d 100644 --- a/Palto/Palto/templates/Palto/teaching_session.html +++ b/Palto/Palto/templates/Palto/teaching_session.html @@ -1,27 +1,66 @@ {% extends "Palto/base.html" %} +{% load dict_tags %} {% block body %} - {# table of all the sessions #} + {# session's information #} + + + + + + + + + + + + + + + + + + + + + + + + + +
Identifiant{{ session.id }}
Début{{ session.start }}
Durée{{ session.duration }}
Unité d'Enseignement{{ session.unit }}
Enseignant{{ session.teacher }}
Groupe{{ session.group }}
+ + {# session's students information #} - - - - - - + + + - - - - - - - - + {% for student, session_student_data in session_students_data.items %} + + + + + + {% endfor %}
IdentifiantDébutDuréeUnité d'EnseignementEnseignantGroupeElèvePrésenceAbsence
{{ session.id }}{{ session.start }}{{ session.duration }}{{ session.unit }}{{ session.teacher }}{{ session.group }}
{{ student }} + {% with attendance=session_student_data|dict_get:"attendance" %} + {% if attendance != None %} + {{ attendance.date }} + {% endif %} + {% endwith %} + + {% with absence=session_student_data|dict_get:"attendance" %} + {% if absence != None %} + ... + {% endif %} + {% endwith %} +
+ + {# TODO(Raphaël): export boutton #} {% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session_list.html b/Palto/Palto/templates/Palto/teaching_session_list.html index f9ecc3e..6cbf08c 100644 --- a/Palto/Palto/templates/Palto/teaching_session_list.html +++ b/Palto/Palto/templates/Palto/teaching_session_list.html @@ -6,16 +6,21 @@ Identifiant - Début - Durée + UE + Horaire + Enseignant + Effectif + {# show the information for every session #} {% for session in sessions %} - {{ session.id }} - {{ session.start }} - {{ session.duration }} + {{ session.short_id }} + {{ session.unit.name }} + {{ session.start }}
{{ session.end }} + {{ session.teacher }} + {{ session.attendances.all|length }} / {{ session.group.students.all|length }} {% endfor %} diff --git a/Palto/Palto/templatetags/__init__.py b/Palto/Palto/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Palto/Palto/templatetags/dict_tags.py b/Palto/Palto/templatetags/dict_tags.py new file mode 100644 index 0000000..887312f --- /dev/null +++ b/Palto/Palto/templatetags/dict_tags.py @@ -0,0 +1,6 @@ +from django.template.defaulttags import register + + +@register.filter +def dict_get(d: dict, key: str): + return d.get(key) diff --git a/Palto/Palto/utils.py b/Palto/Palto/utils.py new file mode 100644 index 0000000..06a968a --- /dev/null +++ b/Palto/Palto/utils.py @@ -0,0 +1,15 @@ +from typing import Optional + +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Model, Manager + + +def get_object_or_none(manager: Manager, *args, **kwargs) -> Optional[Model]: + """ + Similar to the Manager.get method, but return None instead of raising an error. + """ + + try: + return manager.get(*args, **kwargs) + except ObjectDoesNotExist: + return None diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index b9ac81f..ead12c6 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -13,7 +13,7 @@ from django.shortcuts import render, get_object_or_404, redirect from Palto.Palto import models from Palto.Palto.forms import LoginForm - +from Palto.Palto.utils import get_object_or_none ELEMENT_PER_PAGE: int = 30 @@ -71,12 +71,25 @@ def profile_view(request: WSGIRequest, profile_id: uuid.UUID = None): # get the corresponding user from its id. profile = get_object_or_404(models.User, id=profile_id) + # prepare the data and the "complex" query for the template + profile_departments_data = { + department: { + "is_manager": profile in department.managers.all(), + "managing_units": models.TeachingUnit.objects.filter(department=department, managers=profile).all(), + "teaching_units": models.TeachingUnit.objects.filter(department=department, teachers=profile).all(), + "student_groups": models.StudentGroup.objects.filter(department=department, students=profile).all(), + } + + for department in profile.related_departments + } + # render the page return render( request, "Palto/profile.html", context=dict( - profile=profile + profile=profile, + profile_departments_data=profile_departments_data, ) ) @@ -110,11 +123,30 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): # TODO: syntaxic sugar session.visible_by_user(request.user) return HttpResponseForbidden() + # prepare the data and the "complex" query for the template + session_students_data = { + student: { + "attendance": get_object_or_none( + models.Attendance.objects, + session=session, + student=student + ), + "absence": get_object_or_none( + models.Absence.objects, + student=student, + start__gte=session.start, end__lte=session.end + ), + } + + for student in session.group.students.all() + } + # render the page return render( request, "Palto/teaching_session.html", context=dict( - session=session + session=session, + session_students_data=session_students_data, ) - ) \ No newline at end of file + )