diff --git a/aleksis/apps/alsijil/managers.py b/aleksis/apps/alsijil/managers.py new file mode 100644 index 0000000000000000000000000000000000000000..b2589345adac4c2c02159ea54fc872fe9055408a --- /dev/null +++ b/aleksis/apps/alsijil/managers.py @@ -0,0 +1,23 @@ +from aleksis.core.managers import CurrentSiteManagerWithoutMigrations + + +class PersonalNoteManager(CurrentSiteManagerWithoutMigrations): + """Manager adding specific methods to personal notes.""" + + def get_queryset(self): + """Ensure all related lesson and person data are loaded as well.""" + return ( + super() + .get_queryset() + .select_related( + "person", + "excuse_type", + "lesson_period", + "lesson_period__lesson", + "lesson_period__lesson__subject", + "lesson_period__period", + "lesson_period__lesson__validity", + "lesson_period__lesson__validity__school_term", + ) + .prefetch_related("extra_marks") + ) diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index b688332997fcc890dd719467c8997cae403758bd..7881eb9f995d8d59be15af19833cb0edda07e096 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Dict, Optional, Union +from typing import Dict, Iterator, Optional, Union from django.db.models import Exists, OuterRef, Q, QuerySet from django.db.models.aggregates import Count @@ -176,11 +176,14 @@ def get_lesson_documentation( """Get lesson documentation object for this lesson.""" if not week: week = self.week + # Use all to make effect of prefetched data + doc_filter = filter( + lambda d: d.week == week.week and d.year == week.year, + self.dopycumentations.all(), + ) try: - return LessonDocumentation.objects.get( - lesson_period=self, week=week.week, year=week.year - ) - except LessonDocumentation.DoesNotExist: + return next(doc_filter) + except StopIteration: return None @@ -198,11 +201,15 @@ def get_or_create_lesson_documentation( @LessonPeriod.method -def get_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet: +def get_absences(self, week: Optional[CalendarWeek] = None) -> Iterator: """Get all personal notes of absent persons for this lesson.""" if not week: week = self.week - return self.personal_notes.filter(week=week.week, year=week.year, absent=True) + + return filter( + lambda p: p.week == week.week and p.year == week.year and p.absent, + self.personal_notes.all(), + ) @LessonPeriod.method diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 66f6477d22165c085abd0855502686d505d75c75..e2f234a596275f57c4427f916144f534f9092adf 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -1,9 +1,12 @@ from django.db import models from django.utils.formats import date_format +from django.utils.functional import classproperty from django.utils.translation import gettext_lazy as _ +from cache_memoize import cache_memoize from calendarweek import CalendarWeek +from aleksis.apps.alsijil.managers import PersonalNoteManager from aleksis.apps.chronos.mixins import WeekRelatedMixin from aleksis.apps.chronos.models import LessonPeriod from aleksis.apps.chronos.util.date import get_current_year @@ -46,6 +49,8 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin): and remarks about a student in a single lesson period. """ + objects = PersonalNoteManager() + person = models.ForeignKey( "core.Person", models.CASCADE, related_name="personal_notes" ) diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index 8ee92ec5eb2f53b472e0b873995a3522d54c6d32..cb2bda3333471b171a0e76010c574fb22843fdbc 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -29,11 +29,11 @@ from .util.predicates import ( # View lesson view_lesson_predicate = has_person & ( - has_global_perm("alsijil.view_lesson") - | is_none # View is opened as "Current lesson" + is_none # View is opened as "Current lesson" | is_lesson_teacher | is_lesson_participant | is_lesson_parent_group_owner + | has_global_perm("alsijil.view_lesson") | has_lesson_group_object_perm("core.view_week_class_register_group") ) add_perm("alsijil.view_lesson", view_lesson_predicate) @@ -43,37 +43,37 @@ add_perm("alsijil.view_lesson_menu", has_person) # View lesson personal notes view_lesson_personal_notes_predicate = view_lesson_predicate & ( - has_global_perm("alsijil.view_personalnote") - | ~is_lesson_participant + ~is_lesson_participant + | has_global_perm("alsijil.view_personalnote") | has_lesson_group_object_perm("core.view_personalnote_group") ) add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate) # Edit personal note edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & ( - has_global_perm("alsijil.change_personalnote") - | ~is_lesson_parent_group_owner + ~is_lesson_parent_group_owner + | has_global_perm("alsijil.change_personalnote") | has_lesson_group_object_perm("core.edit_personalnote_group") ) add_perm("alsijil.edit_lesson_personalnote", edit_lesson_personal_note_predicate) # View personal note view_personal_note_predicate = has_person & ( - has_global_perm("alsijil.view_personalnote") - | is_personal_note_lesson_teacher - | ( + ( is_own_personal_note & is_site_preference_set("alsijil", "view_own_personal_notes") ) + | is_personal_note_lesson_teacher | is_personal_note_lesson_parent_group_owner + | has_global_perm("alsijil.view_personalnote") | has_personal_note_group_perm("core.view_personalnote_group") ) add_perm("alsijil.view_personalnote", view_personal_note_predicate) # Edit personal note edit_personal_note_predicate = view_personal_note_predicate & ( - has_global_perm("alsijil.view_personalnote") - | ~is_own_personal_note + ~is_own_personal_note + | has_global_perm("alsijil.view_personalnote") | has_personal_note_group_perm("core.edit_personalnote_group") ) add_perm("alsijil.edit_personalnote", edit_personal_note_predicate) @@ -84,18 +84,18 @@ add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate # Edit lesson documentation edit_lesson_documentation_predicate = view_lesson_predicate & ( - has_global_perm("alsijil.change_lessondocumentation") - | is_lesson_teacher + is_lesson_teacher + | has_global_perm("alsijil.change_lessondocumentation") | has_lesson_group_object_perm("core.edit_lessondocumentation_group") ) add_perm("alsijil.edit_lessondocumentation", edit_lesson_documentation_predicate) # View week overview view_week_predicate = has_person & ( - has_global_perm("alsijil.view_week") - | is_current_person + is_current_person | is_group_member | is_group_owner + | has_global_perm("alsijil.view_week") | has_object_perm("core.view_week_class_register_group") ) add_perm("alsijil.view_week", view_week_predicate) @@ -105,10 +105,10 @@ add_perm("alsijil.view_week_menu", has_person) # View week personal notes view_week_personal_notes_predicate = has_person & ( - has_global_perm("alsijil.view_personalnote") - | has_object_perm("core.view_personalnote_group") + (is_current_person & is_teacher) | is_group_owner - | (is_current_person & is_teacher) + | has_global_perm("alsijil.view_personalnote") + | has_object_perm("core.view_personalnote_group") ) add_perm("alsijil.view_week_personalnote", view_week_personal_notes_predicate) @@ -120,21 +120,21 @@ add_perm("alsijil.view_register_absence", view_register_absence_predicate) # Register absence register_absence_predicate = has_person & ( - has_global_perm("alsijil.register_absence") - | has_person_group_object_perm("core.register_absence_group") - | has_object_perm("core.register_absence_person") - | ( + ( is_person_primary_group_owner & is_site_preference_set("alsijil", "register_absence_as_primary_group_owner") ) + | has_global_perm("alsijil.register_absence") + | has_object_perm("core.register_absence_person") + | has_person_group_object_perm("core.register_absence_group") ) add_perm("alsijil.register_absence", register_absence_predicate) # View full register for group view_full_register_predicate = has_person & ( - has_global_perm("alsijil.view_full_register") + is_group_owner + | has_global_perm("alsijil.view_full_register") | has_object_perm("core.view_full_register_group") - | is_group_owner ) add_perm("alsijil.view_full_register", view_full_register_predicate) @@ -159,10 +159,10 @@ add_perm("alsijil.view_person_overview_menu", view_person_overview_menu_predicat # View person overview personal notes view_person_overview_personal_notes_predicate = view_person_overview_predicate & ( - has_global_perm("alsijil.view_personalnote") - | has_person_group_object_perm("core.view_personalnote_group") + (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) | is_person_primary_group_owner - | (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) + | has_global_perm("alsijil.view_personalnote") + | has_person_group_object_perm("core.view_personalnote_group") ) add_perm( "alsijil.view_person_overview_personalnote", @@ -173,8 +173,8 @@ add_perm( edit_person_overview_personal_notes_predicate = ( view_person_overview_personal_notes_predicate & ( - has_global_perm("alsijil.edit_personalnote") - | ~is_current_person + ~is_current_person + | has_global_perm("alsijil.edit_personalnote") | has_person_group_object_perm("core.edit_personalnote_group") ) ) diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html index 19dfebb0b8874746824ce9ffba840920dc8e2e3d..5f8ba67a9904999b8be2ed74f0f454f8ad28e54c 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html @@ -12,7 +12,7 @@ {% block page_title %} {% with lesson_period.get_lesson_documentation as lesson_doc %} <a href="{% url "week_view_by_week" lesson_doc.year lesson_doc.week "group" lesson_period.lesson.groups.all.0.pk %}" - class="btn-flat primary-color-text waves-light waves-effect"> + class="btn-flat primary-color-text waves-light waves-effect"> <i class="material-icons left">chevron_left</i> {% trans "Back" %} </a> {% endwith %} @@ -40,21 +40,17 @@ <div class="row"> <div class="col s12"> - {% with prev_lesson=lesson_period.prev %} - <a class="btn-flat left waves-effect waves-light" - href="{% url "lesson_by_week_and_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"> - <i class="material-icons left">arrow_back</i> - {% trans "Previous lesson" %} - </a> - {% endwith %} + <a class="btn-flat left waves-effect waves-light" + href="{% url "lesson_by_week_and_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"> + <i class="material-icons left">arrow_back</i> + {% trans "Previous lesson" %} + </a> - {% with next_lesson=lesson_period.next %} - <a class="btn-flat right waves-effect waves-light" - href="{% url "lesson_by_week_and_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"> - <i class="material-icons right">arrow_forward</i> - {% trans "Next lesson" %} - </a> - {% endwith %} + <a class="btn-flat right waves-effect waves-light" + href="{% url "lesson_by_week_and_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"> + <i class="material-icons right">arrow_forward</i> + {% trans "Next lesson" %} + </a> </div> </div> @@ -130,75 +126,74 @@ </div> {% with prev_lesson=lesson_period.prev prev_doc=prev_lesson.get_lesson_documentation %} - {% with prev_doc=prev_lesson.get_lesson_documentation absences=prev_lesson.get_absences tardinesses=prev_lesson.get_tardinesses extra_marks=prev_lesson.get_extra_marks %} - {% has_perm "alsijil.view_lessondocumentation" user prev_lesson as can_view_prev_lesson_documentation %} - {% if prev_doc and can_view_prev_lesson_documentation %} - {% weekday_to_date prev_lesson.week prev_lesson.period.weekday as prev_date %} - <div class="col s12" id="previous-lesson"> - <div class="card"> - <div class="card-content"> + {% with absences=prev_lesson.get_absences tardinesses=prev_lesson.get_tardinesses extra_marks=prev_lesson.get_extra_marks %} + {% has_perm "alsijil.view_lessondocumentation" user prev_lesson as can_view_prev_lesson_documentation %} + {% if prev_doc and can_view_prev_lesson_documentation %} + {% weekday_to_date prev_lesson.week prev_lesson.period.weekday as prev_date %} + <div class="col s12" id="previous-lesson"> + <div class="card"> + <div class="card-content"> <span class="card-title"> {% blocktrans %}Overview: Previous lesson{% endblocktrans %} ({{ prev_date }}, {% blocktrans with period=prev_lesson.period.period %}{{ period }}. period{% endblocktrans %}) </span> - <table> - {% if prev_doc.topic %} - <tr> - <th class="collection-item">{% trans "Lesson topic of previous lesson:" %}</th> - <td>{{ prev_doc.topic }}</td> - </tr> - {% endif %} + <table> + {% if prev_doc.topic %} + <tr> + <th class="collection-item">{% trans "Lesson topic of previous lesson:" %}</th> + <td>{{ prev_doc.topic }}</td> + </tr> + {% endif %} - {% if prev_doc.homework %} - <tr> - <th class="collection-item">{% trans "Homework for this lesson:" %}</th> - <td>{{ prev_doc.homework }}</td> - </tr> - {% endif %} + {% if prev_doc.homework %} + <tr> + <th class="collection-item">{% trans "Homework for this lesson:" %}</th> + <td>{{ prev_doc.homework }}</td> + </tr> + {% endif %} - {% if prev_doc.group_note %} - <tr> - <th class="collection-item">{% trans "Group notes for previous lesson:" %}</th> - <td>{{ prev_doc.group_note }}</td> - </tr> - {% endif %} + {% if prev_doc.group_note %} + <tr> + <th class="collection-item">{% trans "Group notes for previous lesson:" %}</th> + <td>{{ prev_doc.group_note }}</td> + </tr> + {% endif %} - {% if absences %} - <tr> - <th>{% trans "Absent persons:" %}</th> - <td>{% include "alsijil/partials/absences.html" with notes=absences %}</td> - </tr> - {% endif %} + {% if absences %} + <tr> + <th>{% trans "Absent persons:" %}</th> + <td>{% include "alsijil/partials/absences.html" with notes=absences %}</td> + </tr> + {% endif %} - {% if tardinesses %} - <tr> - <th>{% trans "Late persons:" %}</th> - <td>{% include "alsijil/partials/tardinesses.html" with notes=tardinesses %}</td> - </tr> - {% endif %} + {% if tardinesses %} + <tr> + <th>{% trans "Late persons:" %}</th> + <td>{% include "alsijil/partials/tardinesses.html" with notes=tardinesses %}</td> + </tr> + {% endif %} - {% for extra_mark, notes in extra_marks.items %} - <tr> - <th>{{ extra_mark.name }}</th> - <td> - {% for note in notes %} - {% has_perm "alsijil.view_personalnote" user note as can_view_personalnote %} - {% if can_view_personalnote %} - <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span> - {% endif %} - {% endfor %} - </td> - </tr> - {% endfor %} + {% for extra_mark, notes in extra_marks.items %} + <tr> + <th>{{ extra_mark.name }}</th> + <td> + {% for note in notes %} + {% has_perm "alsijil.view_personalnote" user note as can_view_personalnote %} + {% if can_view_personalnote %} + <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span> + {% endif %} + {% endfor %} + </td> + </tr> + {% endfor %} - </table> - </div> - </div> + </table> </div> - {% endif %} - {% endwith %} + </div> + {% endif %} {% endwith %} + {% endwith %} {% if not lesson_period.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %} <div class="col s12" id="personal-notes"> @@ -284,8 +279,8 @@ <td>{{ form.person_name.value }}</td> <td><i class="material-icons center">{{ form.absent.value|yesno:"check,clear" }}</i></td> <td> - <i class="material-icons center">{{ form.late.value|yesno:"check,clear" }}</i> - <span class="alsijil-tardiness-text"> + <i class="material-icons center">{{ form.late.value|yesno:"check,clear" }}</i> + <span class="alsijil-tardiness-text"> {% if form.late.value %}{{ form.late.value|to_time|time:"i\m" }}{% endif %} </span> </td> @@ -321,10 +316,10 @@ </div> </div> {% endif %} - </div> + </div> - {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %} - <p>{% include "core/partials/save_button.html" %}</p> - {% endif %} + {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %} + <p>{% include "core/partials/save_button.html" %}</p> + {% endif %} </form> {% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html index 03b75de7b70508885bf06f35e42d1058a5c80b1a..6820996b7370426869a37a054a58d5faf77aa0a6 100644 --- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html +++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html @@ -175,7 +175,7 @@ </thead> <tbody> - {% for lesson in group.lessons.all %} + {% for lesson in lessons %} <tr> <td>{{ lesson.subject.name }}</td> <td>{{ lesson.teachers.all|join:', ' }}</td> @@ -206,7 +206,7 @@ </thead> <tbody> - {% for child_group in group.child_groups.all %} + {% for child_group in child_groups %} {% for lesson in child_group.lessons.all %} <tr> <td>{{ child_group.name }}</td> diff --git a/aleksis/apps/alsijil/templatetags/time_helpers.py b/aleksis/apps/alsijil/templatetags/time_helpers.py index 00aedad7fee98b2b0d303f63f927fbe722317651..cae2ee2e35ef5c25c338b617b784508185d669e8 100644 --- a/aleksis/apps/alsijil/templatetags/time_helpers.py +++ b/aleksis/apps/alsijil/templatetags/time_helpers.py @@ -1,7 +1,7 @@ -from django import template - import datetime +from django import template + register = template.Library() diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index 3d988c8f4c3134b46451d04b24a570177473c44a..4bdd2a34ca1bf7ed1e81777e4f99e493d071905c 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -1,12 +1,14 @@ from typing import Any, Union -from django.contrib.auth.models import User +from django.contrib.auth.models import Permission, User +from guardian.models import UserObjectPermission from guardian.shortcuts import get_objects_for_user from rules import predicate from aleksis.apps.chronos.models import LessonPeriod from aleksis.core.models import Group, Person +from aleksis.core.util.core_helpers import get_content_type_by_perm, get_site_preferences from aleksis.core.util.predicates import check_object_permission from ..models import PersonalNote @@ -41,7 +43,9 @@ def is_lesson_participant(user: User, obj: LessonPeriod) -> bool: the groups linked to the given LessonPeriod. """ if hasattr(obj, "lesson"): - return obj.lesson.groups.filter(members=user.person).exists() + for group in obj.lesson.groups.all(): + if user.person in list(group.members.all()): + return True return False @@ -54,7 +58,10 @@ def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool: any parent groups of any groups of the given LessonPeriods lesson. """ if hasattr(obj, "lesson"): - return obj.lesson.groups.filter(parent_groups__owners=user.person).exists() + for group in obj.lesson.groups.all(): + for parent_group in group.parent_groups.all(): + if user.person in list(parent_group.owners.all()): + return True return False @@ -66,7 +73,7 @@ def is_group_owner(user: User, obj: Union[Group, Person]) -> bool: If there isn't provided a group, it will return `False`. """ if isinstance(obj, Group): - if obj.owners.filter(pk=user.person.pk).exists(): + if user.person in obj.owners.all(): return True return False @@ -81,7 +88,10 @@ def is_person_group_owner(user: User, obj: Person) -> bool: the owner of any group of the given person. """ if obj: - return obj.member_of.filter(owners=user.person).exists() + for group in obj.member_of.all(): + if user.person in list(group.owners.all()): + return True + return False return False @@ -105,12 +115,19 @@ def has_person_group_object_perm(perm: str): """ name = f"has_person_group_object_perm:{perm}" + ct = get_content_type_by_perm(perm) + permissions = Permission.objects.filter(content_type=ct, codename=perm) + @predicate(name) def fn(user: User, obj: Person) -> bool: - for group in obj.member_of.all(): - if check_object_permission(user, perm, group): - return True - return False + groups = obj.member_of.all() + qs = UserObjectPermission.objects.filter( + object_pk__in=list(groups.values_list("pk", flat=True)), + content_type=ct, + user=user, + permission__in=permissions, + ) + return qs.exists() return fn @@ -123,7 +140,7 @@ def is_group_member(user: User, obj: Union[Group, Person]) -> bool: If there isn't provided a group, it will return `False`. """ if isinstance(obj, Group): - if obj.members.filter(pk=user.person.pk).exists(): + if user.person in obj.members.all(): return True return False @@ -136,14 +153,21 @@ def has_lesson_group_object_perm(perm: str): """ name = f"has_lesson_group_object_perm:{perm}" + ct = get_content_type_by_perm(perm) + permissions = Permission.objects.filter(content_type=ct, codename=perm) + @predicate(name) def fn(user: User, obj: LessonPeriod) -> bool: if hasattr(obj, "lesson"): - for group in obj.lesson.groups.all(): - if check_object_permission(user, perm, group): - return True - return False - return True + groups = obj.lesson.groups.all() + qs = UserObjectPermission.objects.filter( + object_pk__in=list(groups.values_list("pk", flat=True)), + content_type=ct, + user=user, + permission__in=permissions, + ) + return qs.exists() + return False return fn @@ -155,13 +179,21 @@ def has_personal_note_group_perm(perm: str): """ name = f"has_personal_note_person_or_group_perm:{perm}" + ct = get_content_type_by_perm(perm) + permissions = Permission.objects.filter(content_type=ct, codename=perm) + @predicate(name) def fn(user: User, obj: PersonalNote) -> bool: if hasattr(obj, "person"): - for group in obj.person.member_of.all(): - if check_object_permission(user, perm, group): - return True - return False + groups = obj.person.member_of.all() + qs = UserObjectPermission.objects.filter( + object_pk__in=list(groups.values_list("pk", flat=True)), + content_type=ct, + user=user, + permission__in=permissions, + ) + return qs.exists() + return False return fn @@ -208,10 +240,10 @@ def is_personal_note_lesson_parent_group_owner(user: User, obj: PersonalNote) -> """ if hasattr(obj, "lesson_period"): if hasattr(obj.lesson_period, "lesson"): - return obj.lesson_period.lesson.groups.filter( - parent_groups__owners=user.person - ).exists() - return False + for group in obj.lesson_period.lesson.groups.all(): + for parent_group in group.parent_groups.all(): + if user.person in list(parent_group.owners.all()): + return True return False @@ -220,10 +252,10 @@ def has_any_object_absence(user: User) -> bool: """ Predicate which builds a query with all the persons the given users is allowed to register an absence for. """ - if get_objects_for_user(user, "core.register_absence_person", Person).exists(): - return True if Person.objects.filter(member_of__owners=user.person).exists(): return True + if get_objects_for_user(user, "core.register_absence_person", Person).exists(): + return True if Person.objects.filter( member_of__in=get_objects_for_user(user, "core.register_absence_group", Group) ).exists(): diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 6120e8dca3e0ba06bc0d93ed05c1b11adee003c7..d72c5856334a31b78c2cfca6630fef2416f13027 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -2,7 +2,7 @@ from datetime import date, datetime, timedelta from typing import Optional from django.core.exceptions import PermissionDenied -from django.db.models import Count, Exists, OuterRef, Q, Subquery, Sum +from django.db.models import Count, Exists, OuterRef, Prefetch, Q, Subquery, Sum from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse, reverse_lazy @@ -16,7 +16,7 @@ from reversion.views import RevisionMixin from rules.contrib.views import PermissionRequiredMixin, permission_required from aleksis.apps.chronos.managers import TimetableType -from aleksis.apps.chronos.models import LessonPeriod, TimePeriod +from aleksis.apps.chronos.models import LessonPeriod, LessonSubstitution, TimePeriod from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_date from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView @@ -158,6 +158,8 @@ def lesson( context["lesson_documentation"] = lesson_documentation context["lesson_documentation_form"] = lesson_documentation_form context["personal_note_formset"] = personal_note_formset + context["prev_lesson"] = lesson_period.prev + context["next_lesson"] = lesson_period.next return render(request, "alsijil/class_register/lesson.html", context) @@ -179,17 +181,13 @@ def week_view( instance = get_timetable_instance_by_pk(request, year, week, type_, id_) - lesson_periods = LessonPeriod.objects.in_week(wanted_week).annotate( - has_documentation=Exists( - LessonDocumentation.objects.filter( - ~Q(topic__exact=""), - lesson_period=OuterRef("pk"), - week=wanted_week.week, - year=wanted_week.year, - ) - ) + lesson_periods = LessonPeriod.objects.in_week(wanted_week).prefetch_related( + "lesson__groups__members", + "lesson__groups__parent_groups", + "lesson__groups__parent_groups__owners", ) + lesson_periods_query_exists = True if type_ and id_: if isinstance(instance, HttpResponseNotFound): return HttpResponseNotFound() @@ -204,6 +202,7 @@ def week_view( else: lesson_periods = lesson_periods.filter_participant(request.user.person) else: + lesson_periods_query_exists = False lesson_periods = None # Add a form to filter the view @@ -231,10 +230,38 @@ def week_view( else: group = None - if lesson_periods: - # Aggregate all personal notes for this group and week + extra_marks = ExtraMark.objects.all() + + if lesson_periods_query_exists: lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True)) + lesson_periods = ( + LessonPeriod.objects.prefetch_related( + Prefetch( + "documentations", + queryset=LessonDocumentation.objects.filter( + week=wanted_week.week, year=wanted_week.year + ), + ) + ) + .filter(pk__in=lesson_periods_pk) + .annotate_week(wanted_week) + .annotate( + has_documentation=Exists( + LessonDocumentation.objects.filter( + ~Q(topic__exact=""), + lesson_period=OuterRef("pk"), + week=wanted_week.week, + year=wanted_week.year, + ) + ) + ) + .order_by("period__weekday", "period__period") + ) + else: + lesson_periods_pk = [] + if lesson_periods_pk: + # Aggregate all personal notes for this group and week persons_qs = Person.objects.filter(is_active=True) if not request.user.has_perm("alsijil.view_week_personalnote", instance): @@ -248,7 +275,17 @@ def week_view( persons_qs = ( persons_qs.distinct() - .prefetch_related("personal_notes") + .prefetch_related( + Prefetch( + "personal_notes", + queryset=PersonalNote.objects.filter( + week=wanted_week.week, + year=wanted_week.year, + lesson_period__in=lesson_periods_pk, + ), + ), + "member_of__owners", + ) .annotate( absences_count=Count( "personal_notes", @@ -285,7 +322,7 @@ def week_view( ) ) - for extra_mark in ExtraMark.objects.all(): + for extra_mark in extra_marks: persons_qs = persons_qs.annotate( **{ extra_mark.count_label: Count( @@ -304,24 +341,12 @@ def week_view( persons = [] for person in persons_qs: persons.append( - { - "person": person, - "personal_notes": person.personal_notes.filter( - week=wanted_week.week, - year=wanted_week.year, - lesson_period__in=lesson_periods_pk, - ), - } + {"person": person, "personal_notes": list(person.personal_notes.all())} ) else: persons = None - # Resort lesson periods manually because an union queryset doesn't support order_by - lesson_periods = sorted( - lesson_periods, key=lambda x: (x.period.weekday, x.period.period) - ) - - context["extra_marks"] = ExtraMark.objects.all() + context["extra_marks"] = extra_marks context["week"] = wanted_week context["weeks"] = get_weeks_for_year(year=wanted_week.year) context["lesson_periods"] = lesson_periods @@ -369,7 +394,14 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: LessonPeriod.objects.filter_group(group) .filter(lesson__validity__school_term=current_school_term) .distinct() - .prefetch_related("documentations", "personal_notes") + .prefetch_related( + "documentations", + "personal_notes", + "personal_notes__excuse_type", + "personal_notes__extra_marks", + "personal_notes__person", + "personal_notes__groups_of_person", + ) ) weeks = CalendarWeek.weeks_within( @@ -404,35 +436,49 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: (lesson_period, documentations, notes, substitution) ) - persons = Person.objects.filter( - personal_notes__groups_of_person=group, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ).annotate( - absences_count=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + persons = ( + Person.objects.prefetch_related( + "personal_notes", + "personal_notes__excuse_type", + "personal_notes__extra_marks", + "personal_notes__lesson_period__lesson__subject", + "personal_notes__lesson_period__substitutions", + "personal_notes__lesson_period__substitutions__subject", + "personal_notes__lesson_period__substitutions__teachers", + "personal_notes__lesson_period__lesson__teachers", + "personal_notes__lesson_period__period", + ) + .filter( + personal_notes__groups_of_person=group, + personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + ) + .annotate( + absences_count=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + ), ), - ), - excused=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__excused=True, - personal_notes__excuse_type__isnull=True, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + excused=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excused=True, + personal_notes__excuse_type__isnull=True, + personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + ), ), - ), - unexcused=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__excused=False, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + unexcused=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excused=False, + personal_notes__lesson_period__lesson__validity__school_term=current_school_term, + ), ), - ), - tardiness=Sum("personal_notes__late"), + tardiness=Sum("personal_notes__late"), + ) ) for extra_mark in ExtraMark.objects.all(): @@ -471,7 +517,18 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: context["periods_by_day"] = periods_by_day context["lesson_periods"] = lesson_periods context["today"] = date.today() - + context["lessons"] = ( + group.lessons.all() + .select_related("validity", "subject") + .prefetch_related("teachers", "lesson_periods") + ) + context["child_groups"] = group.child_groups.all().prefetch_related( + "lessons", + "lessons__validity", + "lessons__subject", + "lessons__teachers", + "lessons__lesson_periods", + ) return render(request, "alsijil/print/full_register.html", context) @@ -570,10 +627,16 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp person.refresh_from_db() + person_personal_notes = person.personal_notes.all().prefetch_related( + "lesson_period__lesson__groups", + "lesson_period__lesson__teachers", + "lesson_period__substitutions", + ) + if request.user.has_perm("alsijil.view_person_overview_personalnote", person): - allowed_personal_notes = person.personal_notes.all() + allowed_personal_notes = person_personal_notes.all() else: - allowed_personal_notes = person.personal_notes.filter( + allowed_personal_notes = person_personal_notes.filter( lesson_period__lesson__groups__owners=request.user.person ) @@ -591,6 +654,8 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp context["personal_notes"] = personal_notes context["excuse_types"] = ExcuseType.objects.all() + extra_marks = ExtraMark.objects.all() + excuse_types = ExcuseType.objects.all() if request.user.has_perm("alsijil.view_person_statistics_personalnote", person): school_terms = SchoolTerm.objects.all().order_by("-date_start") stats = [] @@ -620,14 +685,14 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp ) stat.update(personal_notes.aggregate(tardiness=Sum("late"))) - for extra_mark in ExtraMark.objects.all(): + for extra_mark in extra_marks: stat.update( personal_notes.filter(extra_marks=extra_mark).aggregate( **{extra_mark.count_label: Count("pk")} ) ) - for excuse_type in ExcuseType.objects.all(): + for excuse_type in excuse_types: stat.update( personal_notes.filter( absent=True, excuse_type=excuse_type @@ -636,8 +701,10 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp stats.append((school_term, stat)) context["stats"] = stats - context["excuse_types"] = ExcuseType.objects.all() - context["extra_marks"] = ExtraMark.objects.all() + + context["excuse_types"] = excuse_types + context["extra_marks"] = extra_marks + return render(request, "alsijil/class_register/person.html", context)