detailled profile, session and session list pages
This commit is contained in:
parent
82591b27a2
commit
784d4d4001
8 changed files with 252 additions and 54 deletions
|
@ -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
|
||||
|
|
|
@ -1,9 +1,74 @@
|
|||
{% extends "Palto/base.html" %}
|
||||
{% load dict_tags %}
|
||||
|
||||
{% block body %}
|
||||
<div>
|
||||
{{ profile.username }}
|
||||
{{ profile.email }}
|
||||
{% if profile.is_superuser %}Administrator{% endif %}
|
||||
|
||||
{# user related departments table #}
|
||||
<table>
|
||||
{% for department, profile_department_data in profile_departments_data.items %}
|
||||
<tr>
|
||||
{# department name #}
|
||||
<th>{{ department.name }}</th>
|
||||
{# relation information #}
|
||||
<td>
|
||||
<table>
|
||||
{# user managing the department #}
|
||||
{% if profile_department_data|dict_get:"is_manager" %}
|
||||
<tr>
|
||||
<td>Responsable de Département</td>
|
||||
<td>/</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{# user managing units #}
|
||||
{% with managing_units=profile_department_data|dict_get:"managing_units" %}
|
||||
{% if managing_units|length > 0 %}
|
||||
<tr>
|
||||
<td>Responsable d'UE</td>
|
||||
<td>
|
||||
{% for managing_unit in managing_units %}
|
||||
<a href="{# TODO(Faraphel): redirect to unit #}">{{ managing_unit.name }}</a>
|
||||
{% if not forloop.last %}<br/>{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{# user teaching units #}
|
||||
{% with teaching_units=profile_department_data|dict_get:"teaching_units" %}
|
||||
{% if teaching_units|length > 0 %}
|
||||
<tr>
|
||||
<td>Enseignant</td>
|
||||
<td>
|
||||
{% for teaching_unit in teaching_units %}
|
||||
<a href="{# TODO(Faraphel): redirect to unit #}">{{ teaching_unit.name }}</a>
|
||||
{% if not forloop.last %}<br/>{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{# user studying groups #}
|
||||
{% with student_groups=profile_department_data|dict_get:"student_groups" %}
|
||||
{% if student_groups|length > 0 %}
|
||||
<tr>
|
||||
<td>Groupe Étudiant</td>
|
||||
<td>
|
||||
{% for student_group in student_groups %}
|
||||
<a href="{# TODO(Faraphel): redirect to group #}">{{ student_group.name }}</a>
|
||||
{% if not forloop.last %}<br/>{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,27 +1,66 @@
|
|||
{% extends "Palto/base.html" %}
|
||||
{% load dict_tags %}
|
||||
|
||||
{% block body %}
|
||||
{# table of all the sessions #}
|
||||
{# session's information #}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifiant</th>
|
||||
<td>{{ session.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Début</th>
|
||||
<td>{{ session.start }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Durée</th>
|
||||
<td>{{ session.duration }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Unité d'Enseignement</th>
|
||||
<td>{{ session.unit }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Enseignant</th>
|
||||
<td><a href="{% url "Palto:profile" session.teacher.id %}">{{ session.teacher }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Groupe</th>
|
||||
<td>{{ session.group }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{# session's students information #}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Identifiant</th>
|
||||
<th>Début</th>
|
||||
<th>Durée</th>
|
||||
<th>Unité d'Enseignement</th>
|
||||
<th>Enseignant</th>
|
||||
<th>Groupe</th>
|
||||
<th>Elève</th>
|
||||
<th>Présence</th>
|
||||
<th>Absence</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ session.id }}</td>
|
||||
<td>{{ session.start }}</td>
|
||||
<td>{{ session.duration }}</td>
|
||||
<td>{{ session.unit }}</td>
|
||||
<td>{{ session.teacher }}</td>
|
||||
<td>{{ session.group }}</td>
|
||||
</tr>
|
||||
{% for student, session_student_data in session_students_data.items %}
|
||||
<tr>
|
||||
<td><a href="{% url "Palto:profile" student.id %}">{{ student }}</a></td>
|
||||
<td>
|
||||
{% with attendance=session_student_data|dict_get:"attendance" %}
|
||||
{% if attendance != None %}
|
||||
{{ attendance.date }}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
{% with absence=session_student_data|dict_get:"attendance" %}
|
||||
{% if absence != None %}
|
||||
<a href="{# TODO(Raphaël): when absence page added #}">...</a>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{# TODO(Raphaël): export boutton #}
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,16 +6,21 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Identifiant</th>
|
||||
<th>Début</th>
|
||||
<th>Durée</th>
|
||||
<th>UE</th>
|
||||
<th>Horaire</th>
|
||||
<th>Enseignant</th>
|
||||
<th>Effectif</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{# show the information for every session #}
|
||||
{% for session in sessions %}
|
||||
<tr>
|
||||
<td><a href="{% url "Palto:teaching_session" session.id %}">{{ session.id }}</a></td>
|
||||
<td>{{ session.start }}</td>
|
||||
<td>{{ session.duration }}</td>
|
||||
<td><a href="{% url "Palto:teaching_session" session.id %}">{{ session.short_id }}</a></td>
|
||||
<td><a href="{# TODO(Faraphel): add when teaching units #}">{{ session.unit.name }}</a></td>
|
||||
<td>{{ session.start }}<br>{{ session.end }}</td>
|
||||
<td><a href="{% url "Palto:profile" session.teacher.id %}">{{ session.teacher }}</a></td>
|
||||
<td>{{ session.attendances.all|length }} / {{ session.group.students.all|length }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
0
Palto/Palto/templatetags/__init__.py
Normal file
0
Palto/Palto/templatetags/__init__.py
Normal file
6
Palto/Palto/templatetags/dict_tags.py
Normal file
6
Palto/Palto/templatetags/dict_tags.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.template.defaulttags import register
|
||||
|
||||
|
||||
@register.filter
|
||||
def dict_get(d: dict, key: str):
|
||||
return d.get(key)
|
15
Palto/Palto/utils.py
Normal file
15
Palto/Palto/utils.py
Normal file
|
@ -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
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue