diff --git a/Palto/Palto/admin.py b/Palto/Palto/admin.py index 67d2e10..ba604c8 100644 --- a/Palto/Palto/admin.py +++ b/Palto/Palto/admin.py @@ -24,38 +24,45 @@ class AdminUser(admin.ModelAdmin): class AdminDepartment(admin.ModelAdmin): list_display = ("id", "name", "email") search_fields = ("id", "name", "email") + readonly_fields = ("id",) @admin.register(models.StudentGroup) class AdminStudentGroup(admin.ModelAdmin): list_display = ("id", "name", "owner", "department") search_fields = ("id", "name", "owner", "department") + list_filter = ("department",) + readonly_fields = ("id",) @admin.register(models.TeachingUnit) class AdminTeachingUnit(admin.ModelAdmin): list_display = ("id", "name", "email") search_fields = ("id", "name", "email") + readonly_fields = ("id",) @admin.register(models.StudentCard) class AdminStudentCard(admin.ModelAdmin): - list_display = ("id", "uid", "owner") - search_fields = ("id", "uid", "owner") - readonly_fields = ("uid",) + list_display = ("id", "uid", "department", "owner") + search_fields = ("id", "uid", "department", "owner") + readonly_fields = ("id", "uid",) + list_filter = ("department",) @admin.register(models.TeachingSession) class AdminTeachingSession(admin.ModelAdmin): - list_display = ("id", "start", "end", "duration", "teacher") - search_fields = ("id", "start", "end", "duration", "teacher") - list_filter = ("start", "duration") + list_display = ("id", "start", "end", "unit", "duration", "teacher") + search_fields = ("id", "start", "end", "unit", "duration", "teacher") + readonly_fields = ("id",) + list_filter = ("unit",) @admin.register(models.Attendance) class AdminAttendance(admin.ModelAdmin): list_display = ("id", "date", "student") search_fields = ("id", "date", "student") + readonly_fields = ("id",) list_filter = ("date",) @@ -63,6 +70,7 @@ class AdminAttendance(admin.ModelAdmin): class AdminAbsence(admin.ModelAdmin): list_display = ("id", "message", "student", "start", "end") search_fields = ("id", "message", "student", "start", "end") + readonly_fields = ("id",) list_filter = ("start", "end") @@ -70,3 +78,4 @@ class AdminAbsence(admin.ModelAdmin): class AdminAbsenceAttachment(admin.ModelAdmin): list_display = ("id", "content", "absence") search_fields = ("id", "content", "absence") + readonly_fields = ("id",) diff --git a/Palto/Palto/models.py b/Palto/Palto/models.py index 28ad2f6..d695988 100644 --- a/Palto/Palto/models.py +++ b/Palto/Palto/models.py @@ -39,17 +39,31 @@ class ModelPermissionHelper: @classmethod @abstractmethod def all_editable_by_user(cls, user: "User") -> QuerySet: + """ + Return the list of object that the user can edit + """ + + def is_editable_by_user(self, user: "User") -> bool: """ Return True if the user can edit this object """ + return self in self.all_editable_by_user(user) + @classmethod @abstractmethod def all_visible_by_user(cls, user: "User") -> QuerySet: + """ + Return the list of object that the user can see + """ + + def is_visible_by_user(self, user: "User") -> bool: """ Return True if the user can see this object """ + return self in self.all_visible_by_user(user) + class User(AbstractUser, ModelPermissionHelper): """ @@ -522,6 +536,17 @@ class TeachingSession(models.Model, ModelPermissionHelper): def end(self) -> datetime: return self.start + self.duration + @property + def related_absences(self) -> QuerySet["Absence"]: + """ + Return the sessions that match the user absence + """ + + return Absence.objects.filter( + student__in=self.group.students, + start__lte=self.start, end__gte=self.end + ).distinct() + # validations def clean(self): diff --git a/Palto/Palto/templates/Palto/absence_list.html b/Palto/Palto/templates/Palto/absence_list.html new file mode 100644 index 0000000..9e16e6a --- /dev/null +++ b/Palto/Palto/templates/Palto/absence_list.html @@ -0,0 +1,42 @@ +{% extends "Palto/base.html" %} + +{% block body %} + {# table of all the absences #} + + + + + + + + + + + {# show the information for every session #} + {% for absence in absences %} + + + + + + + {% endfor %} + +
IdentifiantDépartementÉtudiantPériode
{{ absence.short_id }}{{ absence.department }}{{ absence.student }}{{ absence.start }}
{{ absence.end }}
+ + {# page navigator #} + {# TODO(Faraphel): page navigator as template ? #} + {# TODO(Faraphel): new absence button #} + +
+ {% if sessions.has_previous %} + Previous + {% endif %} + + {{ sessions.number }} + + {% if sessions.has_next %} + Next + {% endif %} +
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/absence_view.html b/Palto/Palto/templates/Palto/absence_view.html index 91f8e29..03e1c27 100644 --- a/Palto/Palto/templates/Palto/absence_view.html +++ b/Palto/Palto/templates/Palto/absence_view.html @@ -9,7 +9,7 @@ Département - {{ absence.department }} + {{ absence.department }} Étudiant diff --git a/Palto/Palto/templates/Palto/department_view.html b/Palto/Palto/templates/Palto/department_view.html new file mode 100644 index 0000000..906a22f --- /dev/null +++ b/Palto/Palto/templates/Palto/department_view.html @@ -0,0 +1,53 @@ +{% extends "Palto/base.html" %} +{% load dict_tags %} + + +{% block body %} + {# department's information #} + + + + + + + + + + + + + +
Identifiant{{ department.id }}
Nom{{ department.name }}
Mail{% if department.email != None %}{{ department.email }}{% else %} / {% endif %}
+ + {# department's managers #} + + + + + + + + {% for manager in department.managers.all %} + + + + {% endfor %} + +
Responsables
{{ manager }}
+ + {# department's teachers #} + + + + + + + + {% for teacher in department.teachers.all %} + + + + {% endfor %} + +
Enseignants
{{ teacher }}
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/homepage.html b/Palto/Palto/templates/Palto/homepage.html index bade8c3..0795085 100644 --- a/Palto/Palto/templates/Palto/homepage.html +++ b/Palto/Palto/templates/Palto/homepage.html @@ -1,5 +1,9 @@ {% extends "Palto/base.html" %} {% block body %} - Hello there. +

Palto

+ +

+ Palto est un outil de gestion des présences d'élèves dans vos établissements scolaires. +

{% endblock %} diff --git a/Palto/Palto/templates/Palto/profile.html b/Palto/Palto/templates/Palto/profile.html index d2b55a3..e879b97 100644 --- a/Palto/Palto/templates/Palto/profile.html +++ b/Palto/Palto/templates/Palto/profile.html @@ -11,7 +11,7 @@ {% for department, profile_department_data in profile_departments_data.items %} {# department name #} - {{ department.name }} + {{ department.name }} {# relation information #} @@ -61,7 +61,7 @@ diff --git a/Palto/Palto/templates/Palto/student_group.html b/Palto/Palto/templates/Palto/student_group.html new file mode 100644 index 0000000..56de3b0 --- /dev/null +++ b/Palto/Palto/templates/Palto/student_group.html @@ -0,0 +1,39 @@ +{% extends "Palto/base.html" %} + +{% block body %} + {# group's information #} +
Groupe Étudiant {% for student_group in student_groups %} - {{ student_group.name }} + {{ student_group.name }} {% if not forloop.last %}
{% endif %} {% endfor %}
+ + + + + + + + + + + + + + + + +
Identifiant{{ group.id }}
Nom{{ group.name }}
Département{{ group.department }}
Propriétaire{{ group.owner }}
+ + {# group's students information #} + + + + + + + + {% for student in group.students.all %} + + + + {% endfor %} + +
Étudiants
{{ student }}
+{% endblock %} diff --git a/Palto/Palto/templates/Palto/teaching_session_view.html b/Palto/Palto/templates/Palto/teaching_session_view.html index 3a26783..ada42aa 100644 --- a/Palto/Palto/templates/Palto/teaching_session_view.html +++ b/Palto/Palto/templates/Palto/teaching_session_view.html @@ -26,7 +26,7 @@ Groupe - {{ session.group }} + {{ session.group }} diff --git a/Palto/Palto/templates/Palto/teaching_unit_view.html b/Palto/Palto/templates/Palto/teaching_unit_view.html index 084b84e..956aa5c 100644 --- a/Palto/Palto/templates/Palto/teaching_unit_view.html +++ b/Palto/Palto/templates/Palto/teaching_unit_view.html @@ -14,7 +14,7 @@ Département - {{ unit.department.name }} + {{ unit.department.name }} Mail diff --git a/Palto/Palto/urls.py b/Palto/Palto/urls.py index 513d372..c664492 100644 --- a/Palto/Palto/urls.py +++ b/Palto/Palto/urls.py @@ -19,6 +19,12 @@ urlpatterns = [ path("profile/", views.profile_view, name="my_profile"), path("profile//", views.profile_view, name="profile"), + # Student groups + path("student_groups/view//", views.student_group_view, name="student_group_view"), + + # Departments + path("departments/view//", views.department_view, name="department_view"), + # Units path("teaching_units/view//", views.teaching_unit_view, name="teaching_unit_view"), @@ -27,6 +33,7 @@ urlpatterns = [ path("teaching_sessions/view//", views.teaching_session_view, name="teaching_session_view"), # Absences + path("absences/", views.absence_list_view, name="absence_list"), path("absences/view//", views.absence_view, name="absence_view"), path("absences/new/", views.new_absence_view, name="absence_new"), ] diff --git a/Palto/Palto/utils.py b/Palto/Palto/utils.py index 06a968a..b7b1624 100644 --- a/Palto/Palto/utils.py +++ b/Palto/Palto/utils.py @@ -1,10 +1,10 @@ from typing import Optional from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Model, Manager +from django.db.models import Model, QuerySet -def get_object_or_none(manager: Manager, *args, **kwargs) -> Optional[Model]: +def get_object_or_none(manager: QuerySet, *args, **kwargs) -> Optional[Model]: """ Similar to the Manager.get method, but return None instead of raising an error. """ diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index ca4eabe..85d67e1 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -8,7 +8,6 @@ from django.contrib.auth import login, authenticate, logout from django.contrib.auth.decorators import login_required from django.core.handlers.wsgi import WSGIRequest from django.core.paginator import Paginator -from django.db import IntegrityError from django.http import HttpResponseForbidden from django.shortcuts import render, get_object_or_404, redirect @@ -72,7 +71,7 @@ def profile_view(request: WSGIRequest, profile_id: uuid.UUID = None): profile = get_object_or_404(models.User, id=profile_id) # check if the user is allowed to see this specific object - if profile not in models.User.all_visible_by_user(request.user): + if not profile.is_visible_by_user(request.user): return HttpResponseForbidden() # prepare the data and the "complex" query for the template @@ -124,8 +123,7 @@ def teaching_unit_view(request: WSGIRequest, unit_id: uuid.UUID): unit = get_object_or_404(models.TeachingUnit, id=unit_id) # check if the user is allowed to see this specific object - if unit not in models.TeachingUnit.all_visible_by_user(request.user): - # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) + if not unit.is_visible_by_user(request.user): return HttpResponseForbidden() # render the page @@ -143,23 +141,14 @@ def teaching_session_view(request: WSGIRequest, session_id: uuid.UUID): session = get_object_or_404(models.TeachingSession, id=session_id) # check if the user is allowed to see this specific object - if session not in models.TeachingSession.all_visible_by_user(request.user): - # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) + if not session.is_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__lte=session.start, end__gte=session.end - ), # TODO(Faraphel): property ? + "attendance": get_object_or_none(models.Attendance.objects, session=session, student=student), + "absence": get_object_or_none(session.related_absences, student=student) } for student in session.group.students.all() @@ -181,8 +170,7 @@ def absence_view(request: WSGIRequest, absence_id: uuid.UUID): absence = get_object_or_404(models.Absence, id=absence_id) # check if the user is allowed to see this specific object - if absence not in models.Absence.all_visible_by_user(request.user): - # TODO(Faraphel): syntaxic sugar session.visible_by_user(request.user) + if not absence.is_visible_by_user(request.user): return HttpResponseForbidden() # render the page @@ -236,3 +224,59 @@ def new_absence_view(request: WSGIRequest): form_new_absence=form_new_absence, ) ) + + +def absence_list_view(request): + # get all the absences that the user can see, sorted by starting date + raw_absences = models.Absence.all_visible_by_user(request.user).order_by("start") + # paginate them to avoid having too many elements at the same time + paginator = Paginator(raw_absences, ELEMENT_PER_PAGE) + + # get only the session for the requested page + page = request.GET.get("page", 0) + absences = paginator.get_page(page) + + # render the page + return render( + request, + "Palto/absence_list.html", + context=dict( + absences=absences + ) + ) + + +@login_required +def department_view(request: WSGIRequest, department_id: uuid.UUID): + department = get_object_or_404(models.Department, id=department_id) + + # check if the user is allowed to see this specific object + if not department.is_visible_by_user(request.user): + return HttpResponseForbidden() + + # render the page + return render( + request, + "Palto/department_view.html", + context=dict( + department=department, + ) + ) + + +@login_required +def student_group_view(request: WSGIRequest, group_id: uuid.UUID): + group = get_object_or_404(models.StudentGroup, id=group_id) + + # check if the user is allowed to see this specific object + if not group.is_visible_by_user(request.user): + return HttpResponseForbidden() + + # render the page + return render( + request, + "Palto/student_group.html", + context=dict( + group=group, + ) + )