diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index e75ab0d438be36c9399b1ad38c1ece05bb7b77dc..6be0957fb83b1321137b56762e165d0cea3059ba 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -3,6 +3,7 @@ from typing import Dict, Iterable, Iterator, Optional, Union from django.db.models import Exists, OuterRef, Q, QuerySet from django.db.models.aggregates import Count, Sum +from django.urls import reverse from django.utils.translation import gettext as _ import reversion @@ -16,6 +17,18 @@ from aleksis.core.models import Group, Person from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote +def alsijil_url(self): + if isinstance(self, LessonPeriod): + return reverse("lesson_period", args=[self.week.year, self.week.week, self.pk]) + else: + return reverse(self.label_, args=[self.pk]) + + +LessonPeriod.property_(alsijil_url) +Event.property_(alsijil_url) +ExtraLesson.property_(alsijil_url) + + @Person.method def mark_absent( self, @@ -52,17 +65,33 @@ def mark_absent( .filter(period__period__gte=from_period) .annotate_week(wanted_week) ) + extra_lessons = ( + ExtraLesson.objects.filter(groups__members=self) + .on_day(day) + .filter(period__period__gte=from_period) + ) if to_period: lesson_periods = lesson_periods.filter(period__period__lte=to_period) + extra_lessons = extra_lessons.filter(period__period__lte=to_period) # Create and update all personal notes for the discovered lesson periods if not dry_run: - for lesson_period in lesson_periods: - sub = lesson_period.get_substitution() + for register_object in list(lesson_periods) + list(extra_lessons): + sub = ( + register_object.get_substitution() + if isinstance(register_object, LessonPeriod) + else None + ) if sub and sub.cancelled: continue + q_attrs = ( + dict(week=wanted_week.week, year=wanted_week.year, lesson_period=register_object) + if isinstance(register_object, LessonPeriod) + else dict(extra_lesson=register_object) + ) + with reversion.create_revision(): set_user(get_request().user) personal_note, created = ( @@ -70,14 +99,12 @@ def mark_absent( .prefetch_related(None) .update_or_create( person=self, - lesson_period=lesson_period, - week=wanted_week.week, - year=wanted_week.year, defaults={ "absent": absent, "excused": excused, "excuse_type": excuse_type, }, + **q_attrs, ) ) personal_note.groups_of_person.set(self.member_of.all()) @@ -89,11 +116,10 @@ def mark_absent( personal_note.remarks = remarks personal_note.save() - return lesson_periods.count() + return lesson_periods.count() + extra_lessons.count() -@LessonPeriod.method -def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek): +def get_personal_notes(self, persons: QuerySet, wanted_week: Optional[CalendarWeek] = None): """Get all personal notes for that lesson in a specified week. Returns all linked `PersonalNote` objects, filtered by the given weeek, @@ -106,37 +132,30 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek): - Dominik George <dominik.george@teckids.org> """ # Find all persons in the associated groups that do not yet have a personal note for this lesson + if isinstance(self, LessonPeriod): + q_attrs = dict(week=wanted_week.week, year=wanted_week.year, lesson_period=self) + elif isinstance(self, Event): + q_attrs = dict(event=self) + else: + q_attrs = dict(extra_lesson=self) + missing_persons = persons.annotate( - no_personal_notes=~Exists( - PersonalNote.objects.filter( - week=wanted_week.week, - year=wanted_week.year, - lesson_period=self, - person__pk=OuterRef("pk"), - ) - ) + no_personal_notes=~Exists(PersonalNote.objects.filter(person__pk=OuterRef("pk"), **q_attrs)) ).filter( - member_of__in=Group.objects.filter(pk__in=self.lesson.groups.all()), + member_of__in=Group.objects.filter(pk__in=self.get_groups().all()), is_active=True, no_personal_notes=True, ) # Create all missing personal notes - new_personal_notes = [ - PersonalNote( - person=person, lesson_period=self, week=wanted_week.week, year=wanted_week.year, - ) - for person in missing_persons - ] + new_personal_notes = [PersonalNote(person=person, **q_attrs,) for person in missing_persons] PersonalNote.objects.bulk_create(new_personal_notes) for personal_note in new_personal_notes: personal_note.groups_of_person.set(personal_note.person.member_of.all()) return ( - PersonalNote.objects.filter( - lesson_period=self, week=wanted_week.week, year=wanted_week.year, person__in=persons, - ) + PersonalNote.objects.filter(**q_attrs, person__in=persons) .select_related(None) .prefetch_related(None) .select_related("person", "excuse_type") @@ -144,6 +163,10 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek): ) +LessonPeriod.method(get_personal_notes) +Event.method(get_personal_notes) +ExtraLesson.method(get_personal_notes) + # Dynamically add extra permissions to Group and Person models in core # Note: requires migrate afterwards Group.add_permission( @@ -214,9 +237,7 @@ def get_or_create_lesson_documentation_simple( self, week: Optional[CalendarWeek] = None ) -> LessonDocumentation: """Get or create lesson documentation object for this event/extra lesson.""" - lesson_documentation, created = LessonDocumentation.objects.get_or_create( - **{"event" if isinstance(self, Event) else "extra_lesson": self} - ) + lesson_documentation, created = LessonDocumentation.objects.get_or_create({self.label_: self}) return lesson_documentation diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 38526f78b80b24935b73659325e44aac1185e692..71f8d2b3ea1a976ffe7fbed10b2cb88d444a51ee 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -1,7 +1,9 @@ +from datetime import date +from typing import Optional, Union + from django.db import models from django.db.models.constraints import CheckConstraint from django.db.models.query_utils import Q -from django.urls import reverse from django.utils.formats import date_format from django.utils.translation import gettext_lazy as _ @@ -16,8 +18,7 @@ from aleksis.apps.alsijil.data_checks import ( ) 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 +from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.core.mixins import ExtensibleModel from aleksis.core.util.core_helpers import get_site_preferences @@ -73,7 +74,46 @@ lesson_related_constraint_q = ( ) -class PersonalNote(ExtensibleModel, WeekRelatedMixin): +class RegisterObjectRelatedMixin(WeekRelatedMixin): + @property + def register_object(self) -> Union[LessonPeriod, Event, ExtraLesson]: + if self.lesson_period: + return self.lesson_period + elif self.event: + return self.event + else: + return self.extra_lesson + + @property + def calendar_week(self) -> CalendarWeek: + if self.lesson_period: + return CalendarWeek(week=self.week, year=self.year,) + elif self.extra_lesson: + return self.extra_lesson.calendar_week + else: + return CalendarWeek.from_date(self.register_object.date_start) + + @property + def date(self) -> Optional[date]: + if self.lesson_period: + return super().date + elif self.extra_lesson: + return self.extra_lesson.date + return None + + @property + def date_formatted(self) -> str: + return ( + date_format(self.date) + if self.date + else f"{self.event.date_start}–{self.event.date_end}" + ) + + def get_absolute_url(self) -> str: + return self.register_object.alsijil_url + + +class PersonalNote(RegisterObjectRelatedMixin, ExtensibleModel): """A personal note about a single person. Used in the class register to note absences, excuses @@ -93,9 +133,7 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin): groups_of_person = models.ManyToManyField("core.Group", related_name="+") week = models.IntegerField(blank=True, null=True) - year = models.IntegerField( - verbose_name=_("Year"), default=get_current_year, blank=True, null=True - ) + year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True) lesson_period = models.ForeignKey( "chronos.LessonPeriod", models.CASCADE, related_name="personal_notes", blank=True, null=True @@ -144,16 +182,11 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin): self.remarks = defaults.remarks self.extra_marks.clear() - def __str__(self): - return f"{date_format(self.date)}, {self.lesson_period}, {self.person}" + def __str__(self) -> str: + return f"{self.date_formatted}, {self.lesson_period}, {self.person}" - def get_absolute_url(self): - return ( - reverse( - "lesson_by_week_and_period", args=[self.year, self.week, self.lesson_period.pk], - ) - + "#personal-notes" - ) + def get_absolute_url(self) -> str: + return super().get_absolute_url() + "#personal-notes" class Meta: verbose_name = _("Personal note") @@ -173,7 +206,7 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin): ] -class LessonDocumentation(ExtensibleModel, WeekRelatedMixin): +class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel): """A documentation on a single lesson period. Non-personal, includes the topic and homework of the lesson. @@ -182,9 +215,7 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin): data_checks = [LessonDocumentationOnHolidaysDataCheck] week = models.IntegerField(blank=True, null=True) - year = models.IntegerField( - verbose_name=_("Year"), default=get_current_year, blank=True, null=True - ) + year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True) lesson_period = models.ForeignKey( "chronos.LessonPeriod", models.CASCADE, related_name="documentations", blank=True, null=True @@ -233,16 +264,13 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin): lesson_documentation.save() def __str__(self): - return f"{self.lesson_period}, {date_format(self.date)}" - - def get_absolute_url(self): - return reverse( - "lesson_by_week_and_period", args=[self.year, self.week, self.lesson_period.pk], - ) + return f"{self.lesson_period}, {self.date_formatted}" def save(self, *args, **kwargs): - if get_site_preferences()["alsijil__carry_over"] and ( - self.topic or self.homework or self.group_note + if ( + get_site_preferences()["alsijil__carry_over"] + and (self.topic or self.homework or self.group_note) + and self.lesson_period ): self._carry_over_data() super().save(*args, **kwargs) diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index f553c2773ef6e96aab87b585d01c389399cecd50..36f6a93e01f6505918902e9370dd75f36906ad49 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -27,7 +27,7 @@ from .util.predicates import ( ) # View lesson -view_lesson_predicate = has_person & ( +view_register_object_predicate = has_person & ( is_none # View is opened as "Current lesson" | is_lesson_teacher | is_lesson_participant @@ -35,19 +35,19 @@ view_lesson_predicate = has_person & ( | 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) +add_perm("alsijil.view_register_object", view_register_object_predicate) # View lesson in menu add_perm("alsijil.view_lesson_menu", has_person) # View lesson personal notes -view_lesson_personal_notes_predicate = view_lesson_predicate & ( +view_lesson_personal_notes_predicate = view_register_object_predicate & ( ~is_lesson_participant | is_lesson_teacher | 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) +add_perm("alsijil.view_register_object_personalnote", view_lesson_personal_notes_predicate) # Edit personal note edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & ( @@ -55,7 +55,7 @@ edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & ( | 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) +add_perm("alsijil.edit_register_object_personalnote", edit_lesson_personal_note_predicate) # View personal note view_personal_note_predicate = has_person & ( @@ -76,11 +76,11 @@ edit_personal_note_predicate = view_personal_note_predicate & ( add_perm("alsijil.edit_personalnote", edit_personal_note_predicate) # View lesson documentation -view_lesson_documentation_predicate = view_lesson_predicate +view_lesson_documentation_predicate = view_register_object_predicate add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate) # Edit lesson documentation -edit_lesson_documentation_predicate = view_lesson_predicate & ( +edit_lesson_documentation_predicate = view_register_object_predicate & ( is_lesson_teacher | has_global_perm("alsijil.change_lessondocumentation") | has_lesson_group_object_perm("core.edit_lessondocumentation_group") diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html index 23c96614dba54513c8c8dc4d033fd10cdc2665d6..68c8fcccdabc20c7269a41fc29492a16567076b5 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html @@ -10,21 +10,21 @@ {% endblock %} {% block content %} - {% if next_lesson_person or prev_lesson_person %} + {% if next_lesson_person or prev_lesson_person or lesson_documentation %} <div class="row no-margin"> <div class="col s12 no-padding"> {# Back to week view #} - {% 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 %}" + {% if lesson_documentation %} + <a href="{% url "week_view_by_week" lesson_documentation.calendar_week.year lesson_documentation.calendar_week.week "group" register_object.get_groups.all.0.pk %}" class="btn primary-color waves-light waves-effect alsijil-top-button"> <i class="material-icons left">chevron_left</i> {% trans "Back to week view" %} </a> - {% endwith %} + {% endif %} {# Next lesson #} {% if prev_lesson_person %} <a class="btn primary waves-effect waves-light alsijil-top-button" - href="{% url "lesson_by_week_and_period" prev_lesson_person.week.year prev_lesson_person.week.week prev_lesson_person.id %}"> + href="{% url "lesson_period" prev_lesson_person.week.year prev_lesson_person.week.week prev_lesson_person.id %}"> <i class="material-icons left">arrow_back</i> {% trans "My previous lesson" %} </a> @@ -33,7 +33,7 @@ {# Previous lesson #} {% if next_lesson_person %} <a class="btn primary right waves-effect waves-light alsijil-top-button" - href="{% url "lesson_by_week_and_period" next_lesson_person.week.year next_lesson_person.week.week next_lesson_person.id %}"> + href="{% url "lesson_period" next_lesson_person.week.year next_lesson_person.week.week next_lesson_person.id %}"> <i class="material-icons right">arrow_forward</i> {% trans "My next lesson" %} </a> @@ -43,51 +43,60 @@ {% endif %} <h4> - {{ day }}, {% blocktrans with period=lesson_period.period.period %}{{ period }}. period{% endblocktrans %} – + {% if register_object.label_ == "event" %} + {{ register_object.date_start }} {{ register_object.period_from.period }}.–{{ register_object.date_end }} + {{ register_object.period_to.period }}., + {% else %} + {{ day }}, {% blocktrans with period=register_object.period.period %}{{ period }}. period{% endblocktrans %} – + {% endif %} - {% for group in lesson_period.get_groups.all %} - <span>{{ group.name }}</span>, - {% endfor %} + {{ register_object.group_names }}, - {{ lesson_period.get_subject.name }}, + {% if register_object.label_ == "event" %} + {% trans "Event" %} ({{ register_object.title }}) + {% else %} + {{ register_object.get_subject.name }} + {% endif %}, - {% for teacher in lesson_period.get_teachers.all %} - {{ teacher.short_name }} - {% endfor %} + {{ register_object.teacher_short_names }} <span class="right"> - {% include "alsijil/partials/lesson_status_icon.html" with period=lesson_period css_class="medium" %} - </span> + {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object css_class="medium" %} + </span> </h4> <br/> - {% has_perm "alsijil.view_lessondocumentation" user lesson_period as can_view_lesson_documentation %} - {% has_perm "alsijil.edit_lessondocumentation" user lesson_period as can_edit_lesson_documentation %} - {% has_perm "alsijil.edit_lesson_personalnote" user lesson_period as can_edit_lesson_personalnote %} + {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %} + {% has_perm "alsijil.edit_lessondocumentation" user register_object as can_edit_lesson_documentation %} + {% has_perm "alsijil.edit_register_object_personalnote" user register_object as can_edit_register_object_personalnote %} <form method="post" class="row"> <p> {% if not blocked_because_holidays %} - {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %} + {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %} {% include "core/partials/save_button.html" %} {% endif %} {% endif %} - <a class="btn waves-effect waves-light primary" - 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> - {% blocktrans with subject=lesson_period.get_subject.name %} - Previous {{ subject }} lesson - {% endblocktrans %} - </a> - - <a class="btn right waves-effect waves-light primary" - 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> - {% blocktrans with subject=lesson_period.get_subject.name %} - Next {{ subject }} lesson - {% endblocktrans %} - </a> + {% if prev_lesson %} + <a class="btn waves-effect waves-light primary" + href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"> + <i class="material-icons left">arrow_back</i> + {% blocktrans with subject=register_object.get_subject.name %} + Previous {{ subject }} lesson + {% endblocktrans %} + </a> + {% endif %} + + {% if next_lesson %} + <a class="btn right waves-effect waves-light primary" + href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"> + <i class="material-icons right">arrow_forward</i> + {% blocktrans with subject=register_object.get_subject.name %} + Next {{ subject }} lesson + {% endblocktrans %} + </a> + {% endif %} </p> {% csrf_token %} @@ -100,13 +109,13 @@ <li class="tab"> <a href="#lesson-documentation">{% trans "Lesson documentation" %}</a> </li> - {% if not lesson_period.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %} + {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %} <li class="tab"> <a href="#personal-notes">{% trans "Personal notes" %}</a> </li> {% endif %} - {% has_perm "alsijil.view_lessondocumentation" user lesson_period.prev as can_view_prev_lesson_documentation %} - {% if lesson_period.prev.get_lesson_documentation and can_view_prev_lesson_documentation %} + {% has_perm "alsijil.view_lessondocumentation" user register_object.prev as can_view_prev_lesson_documentation %} + {% if register_object.prev.get_lesson_documentation and can_view_prev_lesson_documentation %} <li class="tab"> <a href="#previous-lesson">{% trans "Previous lesson" %}</a> </li> @@ -158,7 +167,7 @@ </div> </div> - {% with prev_lesson=lesson_period.prev prev_doc=prev_lesson.get_lesson_documentation %} + {% with prev_lesson=register_object.prev prev_doc=prev_lesson.get_lesson_documentation %} {% 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 %} @@ -229,14 +238,14 @@ {% endwith %} {% endwith %} - {% if not lesson_period.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %} + {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %} <div class="col s12" id="personal-notes"> <div class="card"> <div class="card-content"> <span class="card-title"> {% blocktrans %}Personal notes{% endblocktrans %} </span> - {% if can_edit_lesson_personalnote %} + {% if can_edit_register_object_personalnote %} {% form form=personal_note_formset.management_form %}{% endform %} {% endif %} @@ -254,7 +263,7 @@ </thead> <tbody> {% for form in personal_note_formset %} - {% if can_edit_lesson_personalnote %} + {% if can_edit_register_object_personalnote %} <tr> {{ form.id }} <td>{{ form.person_name }}{{ form.person_name.value }}</td> @@ -354,25 +363,29 @@ <p> - {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %} + {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %} {% include "core/partials/save_button.html" %} {% endif %} - <a class="btn primary 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> - {% blocktrans with subject=lesson_period.get_subject.name %} - Previous {{ subject }} lesson - {% endblocktrans %} - </a> + {% if prev_lesson %} + <a class="btn primary waves-effect waves-light" + href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"> + <i class="material-icons left">arrow_back</i> + {% blocktrans with subject=register_object.get_subject.name %} + Previous {{ subject }} lesson + {% endblocktrans %} + </a> + {% endif %} - <a class="btn primary 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> - {% blocktrans with subject=lesson_period.get_subject.name %} - Next {{ subject }} lesson - {% endblocktrans %} - </a> + {% if next_lesson %} + <a class="btn primary right waves-effect waves-light" + href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"> + <i class="material-icons right">arrow_forward</i> + {% blocktrans with subject=register_object.get_subject.name %} + Next {{ subject }} lesson + {% endblocktrans %} + </a> + {% endif %} </p> {% else %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html index 4c7d73da476595d6ae4f23824bbc7e0916e080e8..22b936effe6b28f51c3a1086882084d3725ff3a8 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html @@ -115,7 +115,7 @@ </td> <td class="tr-link"> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% if register_object.period %} {{ register_object.period.period }}. {% else %} @@ -126,7 +126,7 @@ {% if not group %} <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% if register_object.lesson %} {{ register_object.lesson.group_names }} {% else %} @@ -137,41 +137,37 @@ {% endif %} <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% if register_object.get_subject %} {{ register_object.get_subject.name }} {% elif register_object.subject %} {{ register_object.subject }} {% else %} - {% trans "Event" %} + {% trans "Event" %} ({{ register_object.title }}) {% endif %} </a> </td> <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> - {% if register_object.get_teacher_names %} - {{ register_object.get_teacher_names }} - {% else %} + href="{{ register_object.alsijil_url }}"> {{ register_object.teacher_names }} - {% endif %} </a> </td> <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% firstof register_object.get_lesson_documentation.topic "–" %} </a> </td> <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% firstof register_object.get_lesson_documentation.homework "–" %} </a> </td> <td> <a class="tr-link" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% firstof register_object.get_lesson_documentation.group_note "–" %} </a> </td> @@ -194,7 +190,7 @@ {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %} {% if can_view_lesson_documentation %} <a class="collection-item avatar" - href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}"> + href="{{ register_object.alsijil_url }}"> {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object css_class="materialize-circle" color_suffix=" " %} <table class="hide-on-med-and-down"> <tr> @@ -229,11 +225,7 @@ <tr> <th>{% trans "Teachers" %}</th> <td> - {% if register_object.get_teacher_names %} - {{ register_object.get_teacher_names }} - {% else %} {{ register_object.teacher_names }} - {% endif %} </td> </tr> <tr> @@ -268,7 +260,7 @@ {% elif register_object.subject %} {{ register_object.subject }} {% else %} - {% trans "Event" %} + {% trans "Event" %} ({{ register_object.title }}) {% endif %} </li> {% if not group %} @@ -281,11 +273,7 @@ </li> {% endif %} <li class="collection-item"> - {% if register_object.get_teacher_names %} - {{ register_object.get_teacher_names }} - {% else %} {{ register_object.teacher_names }} - {% endif %} </li> <li class="collection-item"> {{ register_object.get_lesson_documentation.topic }} @@ -359,10 +347,10 @@ {% if note.remarks %} <blockquote> {{ note.remarks }} - {% weekday_to_date week note.lesson_period.period.weekday as note_date %} + {% weekday_to_date week note.register_object.period.weekday as note_date %} <em class="right"> - <a href="{% url 'lesson_by_week_and_period' week.year week.week note.lesson_period.id %}"> - {{ note_date }}, {{ note.lesson_period.get_subject.name }} + <a href="{{ note.register_object.alsijil_url }}"> + {{ note.date }}, {{ note.register_object.get_subject.name }} </a> </em> </blockquote> diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 673154f496b081b194b95cdde9aeef8464f05519..ca5da439368beb869b38035b49261b84480ae47e 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -3,12 +3,20 @@ from django.urls import path from . import views urlpatterns = [ - path("lesson", views.lesson, name="lesson"), + path("lesson", views.register_object, {"model": "lesson"}, name="lesson_period"), path( - "lesson/<int:year>/<int:week>/<int:period_id>", - views.lesson, - name="lesson_by_week_and_period", + "lesson/<int:year>/<int:week>/<int:id_>", + views.register_object, + {"model": "lesson"}, + name="lesson_period", ), + path( + "extra_lesson/<int:id_>/", + views.register_object, + {"model": "extra_lesson"}, + name="extra_lesson", + ), + path("event/<int:id_>/", views.register_object, {"model": "event"}, name="event",), path("week/", views.week_view, name="week_view"), path("week/<int:year>/<int:week>/", views.week_view, name="week_view_by_week"), path("week/year/cw/", views.week_view, name="week_view_placeholders"), diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index 24e04320e5a691e9a371c871d423f53453f7e613..1ad17814238fcd0f76ea726245194954bcd103c4 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -12,28 +12,33 @@ from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk -def get_lesson_period_by_pk( +def get_register_object_by_pk( request: HttpRequest, + model: Optional[str] = None, year: Optional[int] = None, week: Optional[int] = None, - period_id: Optional[int] = None, -): - """Get LessonPeriod object either by given object_id or by time and current person.""" + id_: Optional[int] = None, +) -> Optional[Union[LessonPeriod, Event, ExtraLesson]]: + """Get register object either by given object_id or by time and current person.""" wanted_week = CalendarWeek(year=year, week=week) - if period_id: - lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(pk=period_id) + if id_ and model == "lesson": + register_object = LessonPeriod.objects.annotate_week(wanted_week).get(pk=id_) + elif id_ and model == "event": + register_object = Event.objects.get(pk=id_) + elif id_ and model == "extra_lesson": + register_object = ExtraLesson.objects.get(pk=id_) elif hasattr(request, "user") and hasattr(request.user, "person"): if request.user.person.lessons_as_teacher.exists(): - lesson_period = ( + register_object = ( LessonPeriod.objects.at_time().filter_teacher(request.user.person).first() ) else: - lesson_period = ( + register_object = ( LessonPeriod.objects.at_time().filter_participant(request.user.person).first() ) else: - lesson_period = None - return lesson_period + register_object = None + return register_object def get_timetable_instance_by_pk( @@ -53,14 +58,18 @@ def get_timetable_instance_by_pk( def annotate_documentations( klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int] ) -> QuerySet: - instances = klass.objects.prefetch_related( - Prefetch( + + if isinstance(klass, LessonPeriod): + prefetch = Prefetch( "documentations", queryset=LessonDocumentation.objects.filter( week=wanted_week.week, year=wanted_week.year ), ) - ).filter(pk__in=pks) + else: + prefetch = Prefetch("documentations") + instances = klass.objects.prefetch_related(prefetch).filter(pk__in=pks) + if klass == LessonPeriod: instances = instances.annotate_week(wanted_week) if klass in (LessonPeriod, ExtraLesson): diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index 95c7825c2a1eceeb2aed222260816ec9575ad0a8..a831cf0fd4db684857c38ca9c1669c5a177c0755 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import Permission, User from guardian.models import UserObjectPermission from rules import predicate -from aleksis.apps.chronos.models import LessonPeriod +from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.core.models import Group, Person from aleksis.core.util.core_helpers import get_content_type_by_perm @@ -19,17 +19,17 @@ def is_none(user: User, obj: Any) -> bool: @predicate -def is_lesson_teacher(user: User, obj: LessonPeriod) -> bool: +def is_lesson_teacher(user: User, obj: Union[LessonPeriod, Event, ExtraLesson]) -> bool: """Predicate for teachers of a lesson. Checks whether the person linked to the user is a teacher in the lesson or the substitution linked to the given LessonPeriod. """ if obj: - sub = obj.get_substitution() + sub = obj.get_substitution() if isinstance(obj, LessonPeriod) else None if sub and sub in user.person.lesson_substitutions.all(): return True - return user.person in obj.lesson.teachers.all() + return user.person in obj.get_teachers().all() return False @@ -40,8 +40,8 @@ def is_lesson_participant(user: User, obj: LessonPeriod) -> bool: Checks whether the person linked to the user is a member in the groups linked to the given LessonPeriod. """ - if hasattr(obj, "lesson"): - for group in obj.lesson.groups.all(): + if hasattr(obj, "lesson") or hasattr(obj, "groups"): + for group in obj.get_groups().all(): if user.person in list(group.members.all()): return True return False @@ -55,8 +55,8 @@ def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool: Checks whether the person linked to the user is the owner of any parent groups of any groups of the given LessonPeriods lesson. """ - if hasattr(obj, "lesson"): - for group in obj.lesson.groups.all(): + if hasattr(obj, "lesson") or hasattr(obj, "groups"): + for group in obj.get_groups().all(): for parent_group in group.parent_groups.all(): if user.person in list(parent_group.owners.all()): return True diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index f5c5f18a0288cec7dcc54ebf44644bf85b46c0e9..5076124f759a051cbbc9b312d261866eb0312e2b 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -39,36 +39,37 @@ from .models import ExcuseType, ExtraMark, PersonalNote from .tables import ExcuseTypeTable, ExtraMarkTable from .util.alsijil_helpers import ( annotate_documentations, - get_lesson_period_by_pk, + get_register_object_by_pk, get_timetable_instance_by_pk, register_objects_sorter, ) -@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk) -def lesson( +@permission_required("alsijil.view_register_object", fn=get_register_object_by_pk) # FIXME +def register_object( request: HttpRequest, + model: Optional[str] = None, year: Optional[int] = None, week: Optional[int] = None, - period_id: Optional[int] = None, + id_: Optional[int] = None, ) -> HttpResponse: context = {} - lesson_period = get_lesson_period_by_pk(request, year, week, period_id) + register_object = get_register_object_by_pk(request, model, year, week, id_) - if period_id: + if id_ and model == "lesson": wanted_week = CalendarWeek(year=year, week=week) + elif id_ and model == "extra_lesson": + wanted_week = register_object.calendar_week elif hasattr(request, "user") and hasattr(request.user, "person"): wanted_week = CalendarWeek() else: wanted_week = None - if not all((year, week, period_id)): - if lesson_period: - return redirect( - "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk, - ) - else: + if not all((year, week, id_)): + if register_object and model == "lesson": + return redirect("lesson", wanted_week.year, wanted_week.week, register_object.pk,) + elif not register_object: raise Http404( _( "You either selected an invalid lesson or " @@ -76,22 +77,30 @@ def lesson( ) ) - date_of_lesson = week_weekday_to_date(wanted_week, lesson_period.period.weekday) + date_of_lesson = ( + week_weekday_to_date(wanted_week, register_object.period.weekday) + if not isinstance(register_object, Event) + else register_object.date_start + ) + start_time = ( + register_object.period.time_start + if not isinstance(register_object, Event) + else register_object.period_from.time_start + ) - if ( - date_of_lesson < lesson_period.lesson.validity.date_start - or date_of_lesson > lesson_period.lesson.validity.date_end + if isinstance(register_object, Event): + register_object.annotate_day(date_of_lesson) + if isinstance(register_object, LessonPeriod) and ( + date_of_lesson < register_object.lesson.validity.date_start + or date_of_lesson > register_object.lesson.validity.date_end ): return HttpResponseNotFound() if ( - datetime.combine( - wanted_week[lesson_period.period.weekday], lesson_period.period.time_start, - ) - > datetime.now() + datetime.combine(date_of_lesson, start_time) > datetime.now() and not ( get_site_preferences()["alsijil__open_periods_same_day"] - and wanted_week[lesson_period.period.weekday] <= datetime.now().date() + and date_of_lesson <= datetime.now().date() ) and not request.user.is_superuser ): @@ -106,39 +115,51 @@ def lesson( context["blocked_because_holidays"] = blocked_because_holidays context["holiday"] = holiday - next_lesson = request.user.person.next_lesson(lesson_period, date_of_lesson) - prev_lesson = request.user.person.previous_lesson(lesson_period, date_of_lesson) + next_lesson = ( + request.user.person.next_lesson(register_object, date_of_lesson) + if isinstance(register_object, LessonPeriod) + else None + ) + prev_lesson = ( + request.user.person.previous_lesson(register_object, date_of_lesson) + if isinstance(register_object, LessonPeriod) + else None + ) - context["lesson_period"] = lesson_period + context["register_object"] = register_object context["week"] = wanted_week - context["day"] = wanted_week[lesson_period.period.weekday] + context["day"] = date_of_lesson context["next_lesson_person"] = next_lesson context["prev_lesson_person"] = prev_lesson - context["prev_lesson"] = lesson_period.prev - context["next_lesson"] = lesson_period.next + context["prev_lesson"] = ( + register_object.prev if isinstance(register_object, LessonPeriod) else None + ) + context["next_lesson"] = ( + register_object.next if isinstance(register_object, LessonPeriod) else None + ) if not blocked_because_holidays: # Create or get lesson documentation object; can be empty when first opening lesson - lesson_documentation = lesson_period.get_or_create_lesson_documentation(wanted_week) + lesson_documentation = register_object.get_or_create_lesson_documentation(wanted_week) lesson_documentation_form = LessonDocumentationForm( request.POST or None, instance=lesson_documentation, prefix="lesson_documentation", ) # Create a formset that holds all personal notes for all persons in this lesson - if not request.user.has_perm("alsijil.view_lesson_personalnote", lesson_period): + if not request.user.has_perm("alsijil.view_register_object_personalnote", register_object): persons = Person.objects.filter(pk=request.user.person.pk) else: persons = Person.objects.all() - persons_qs = lesson_period.get_personal_notes(persons, wanted_week) + persons_qs = register_object.get_personal_notes(persons, wanted_week) personal_note_formset = PersonalNoteFormSet( request.POST or None, queryset=persons_qs, prefix="personal_notes" ) if request.method == "POST": if lesson_documentation_form.is_valid() and request.user.has_perm( - "alsijil.edit_lessondocumentation", lesson_period + "alsijil.edit_lessondocumentation", register_object ): with reversion.create_revision(): reversion.set_user(request.user) @@ -146,27 +167,33 @@ def lesson( messages.success(request, _("The lesson documentation has been saved.")) - substitution = lesson_period.get_substitution() + substitution = ( + register_object.get_substitution() + if isinstance(register_object, LessonPeriod) + else None + ) if ( not getattr(substitution, "cancelled", False) or not get_site_preferences()["alsijil__block_personal_notes_for_cancelled"] ): if personal_note_formset.is_valid() and request.user.has_perm( - "alsijil.edit_lesson_personalnote", lesson_period + "alsijil.edit_register_object_personalnote", register_object ): with reversion.create_revision(): reversion.set_user(request.user) instances = personal_note_formset.save() - # Iterate over personal notes and carry changed absences to following lessons - for instance in instances: - instance.person.mark_absent( - wanted_week[lesson_period.period.weekday], - lesson_period.period.period + 1, - instance.absent, - instance.excused, - instance.excuse_type, - ) + if not isinstance(register_object, Event): + # Iterate over personal notes + # and carry changed absences to following lessons + for instance in instances: + instance.person.mark_absent( + wanted_week[register_object.period.weekday], + register_object.period.period + 1, + instance.absent, + instance.excused, + instance.excuse_type, + ) messages.success(request, _("The personal notes have been saved.")) @@ -376,7 +403,12 @@ def week_view( persons = [] for person in persons_qs: - persons.append({"person": person, "personal_notes": list(person.personal_notes.all())}) + personal_notes = [] + for note in person.personal_notes.all(): + if note.lesson_period: + note.lesson_period.annotate_week(wanted_week) + personal_notes.append(note) + persons.append({"person": person, "personal_notes": personal_notes}) else: persons = None