diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index 09024eae5eab9a692adac8ea8e0bc085bcf8b4e1..8b5a17af9086e65b12abbb2cc734cf033a7a4a89 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -9,13 +9,22 @@ from django.utils.translation import gettext as _ from calendarweek import CalendarWeek +from aleksis.apps.alsijil.managers import PersonalNoteQuerySet from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.core.models import Group, Person from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote -def alsijil_url(self, week: Optional[CalendarWeek] = None): +def alsijil_url( + self: Union[LessonPeriod, Event, ExtraLesson], week: Optional[CalendarWeek] = None +) -> str: + """Build URL for the detail page of register objects. + + Works with `LessonPeriod`, `Event` and `ExtraLesson`. + + On `LessonPeriod` objects, it will work with annotated or passed weeks. + """ if isinstance(self, LessonPeriod): week = week or self.week return reverse("lesson_period", args=[week.year, week.week, self.pk]) @@ -45,8 +54,8 @@ def mark_absent( ): """Mark a person absent for all lessons in a day, optionally starting with a selected period number. - This function creates `PersonalNote` objects for every `LessonPeriod` the person - participates in on the selected day and marks them as absent/excused. + This function creates `PersonalNote` objects for every `LessonPeriod` and `ExtraLesson` + the person participates in on the selected day and marks them as absent/excused. :param dry_run: With this activated, the function won't change any data and just return the count of affected lessons @@ -115,10 +124,15 @@ def mark_absent( return lesson_periods.count() + extra_lessons.count() -def get_personal_notes(self, persons: QuerySet, wanted_week: Optional[CalendarWeek] = None): - """Get all personal notes for that lesson in a specified week. +def get_personal_notes( + self, persons: QuerySet, wanted_week: Optional[CalendarWeek] = None +) -> PersonalNoteQuerySet: + """Get all personal notes for that register object in a specified week. + + The week is optional for extra lessons and events as they have own date information. - Returns all linked `PersonalNote` objects, filtered by the given weeek, + Returns all linked `PersonalNote` objects, + filtered by the given week for `LessonPeriod` objects, creating those objects that haven't been created yet. ..note:: Only available when AlekSIS-App-Alsijil is installed. @@ -253,7 +267,7 @@ def get_absences(self, week: Optional[CalendarWeek] = None) -> Iterator: ) -def get_absences_simple(self, week: Optional[CalendarWeek] = None) -> Iterator: +def get_absences_simple(self, week: Optional[CalendarWeek] = None) -> PersonalNoteQuerySet: """Get all personal notes of absent persons for this event/extra lesson.""" return self.personal_notes.all() diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 726bc3e7aaf60c950fe7f5cbc94f163e680821a6..016252970583907dab576fe8ccb1458c6575c9b8 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -88,8 +88,13 @@ lesson_related_constraint_q = ( class RegisterObjectRelatedMixin(WeekRelatedMixin): + """Mixin with common API for lesson documentations and personal notes.""" + @property - def register_object(self) -> Union[LessonPeriod, Event, ExtraLesson]: + def register_object( + self: Union["LessonDocumentation", "PersonalNote"] + ) -> Union[LessonPeriod, Event, ExtraLesson]: + """Get the object related to this lesson documentation or personal note.""" if self.lesson_period: return self.lesson_period elif self.event: @@ -98,23 +103,38 @@ class RegisterObjectRelatedMixin(WeekRelatedMixin): return self.extra_lesson @property - def calendar_week(self) -> CalendarWeek: + def calendar_week(self: Union["LessonDocumentation", "PersonalNote"]) -> CalendarWeek: + """Get the calendar week of this lesson documentation or personal note. + + .. note:: + + As events can be longer than one week, + this will return the week of the start date for events. + """ if self.lesson_period: - return CalendarWeek(week=self.week, year=self.year,) + 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 school_term(self) -> SchoolTerm: + def school_term(self: Union["LessonDocumentation", "PersonalNote"]) -> SchoolTerm: + """Get the school term of the related register object.""" if self.lesson_period: return self.lesson_period.lesson.validity.school_term else: return self.register_object.school_term @property - def date(self) -> Optional[date]: + def date(self: Union["LessonDocumentation", "PersonalNote"]) -> Optional[date]: + """Get the date of this lesson documentation or personal note. + + :: warning:: + + As events can be longer than one day, + this will return `None` for events. + """ if self.lesson_period: return super().date elif self.extra_lesson: @@ -122,14 +142,20 @@ class RegisterObjectRelatedMixin(WeekRelatedMixin): return None @property - def date_formatted(self) -> str: + def date_formatted(self: Union["LessonDocumentation", "PersonalNote"]) -> str: + """Get a formatted version of the date of this object. + + Lesson periods, extra lessons: formatted date + Events: formatted date range + """ return ( date_format(self.date) if self.date - else f"{self.event.date_start}–{self.event.date_end}" + else f"{date_format(self.event.date_start)}–{date_format(self.event.date_end)}" ) - def get_absolute_url(self) -> str: + def get_absolute_url(self: Union["LessonDocumentation", "PersonalNote"]) -> str: + """Get the absolute url of the detail view for the related register object.""" return self.register_object.get_alsijil_url(self.calendar_week) @@ -204,6 +230,7 @@ class PersonalNote(RegisterObjectRelatedMixin, ExtensibleModel): return f"{self.date_formatted}, {self.lesson_period}, {self.person}" def get_absolute_url(self) -> str: + """Get the absolute url of the detail view for the related register object.""" return super().get_absolute_url() + "#personal-notes" class Meta: @@ -283,7 +310,7 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel): if changed: lesson_documentation.save() - def __str__(self): + def __str__(self) -> str: return f"{self.lesson_period}, {self.date_formatted}" def save(self, *args, **kwargs): diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index 1ad17814238fcd0f76ea726245194954bcd103c4..34d9ff4a3b37b09fadf6e01813b2f43cde4742d7 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -58,7 +58,7 @@ def get_timetable_instance_by_pk( def annotate_documentations( klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int] ) -> QuerySet: - + """Return a annotated queryset of all provided register objects.""" if isinstance(klass, LessonPeriod): prefetch = Prefetch( "documentations",