Skip to content
Snippets Groups Projects
managers.py 6.81 KiB
Newer Older
from datetime import date, datetime
Jonathan Weth's avatar
Jonathan Weth committed
from typing import TYPE_CHECKING, Optional, Sequence, Union
from django.db.models import Case, ExpressionWrapper, F, Func, QuerySet, Value, When
from django.db.models.fields import DateField
from django.db.models.functions import Concat
from django.db.models.query import Prefetch
from django.utils.translation import gettext as _
from calendarweek import CalendarWeek

from aleksis.apps.chronos.managers import DateRangeQuerySetMixin
Julian's avatar
Julian committed
from aleksis.core.managers import AlekSISBaseManagerWithoutMigrations, RecurrencePolymorphicManager
Jonathan Weth's avatar
Jonathan Weth committed
if TYPE_CHECKING:
    from aleksis.core.models import Group

class RegisterObjectRelatedQuerySet(QuerySet):
    """Common queryset for personal notes and lesson documentations with shared API."""

    def _get_weekday_to_date(self, weekday_name, year_name="year", week_name="week"):
        """Get a ORM function which converts a weekday, a week and a year to a date."""
        return ExpressionWrapper(
            Func(
                Concat(F(year_name), F(week_name)),
                Value("IYYYIW"),
                output_field=DateField(),
                function="TO_DATE",
            )
            + F(weekday_name),
            output_field=DateField(),
        )

    def annotate_day(self) -> QuerySet:
        """Annotate every personal note/lesson documentation with the real date.

        Attribute name: ``day``

        .. note::
            For events, this will annotate ``None``.
        """
        return self.annotate(
            day=Case(
                When(
                    lesson_period__isnull=False,
                    then=self._get_weekday_to_date("lesson_period__period__weekday"),
                ),
                When(
                    extra_lesson__isnull=False,
                    then=self._get_weekday_to_date(
                        "extra_lesson__period__weekday", "extra_lesson__year", "extra_lesson__week"
                    ),
                ),
            )
        )

    def annotate_date_range(self) -> QuerySet:
        """Annotate every personal note/lesson documentation with the real date.

        Attribute names: ``day_start``, ``day_end``

        .. note::
            For lesson periods and extra lessons,
            this will annotate the same date for start and end day.
        """
        return self.annotate_day().annotate(
            day_start=Case(
                When(day__isnull=False, then="day"),
                When(day__isnull=True, then="event__date_start"),
            ),
            day_end=Case(
                When(day__isnull=False, then="day"),
                When(day__isnull=True, then="event__date_end"),
    def annotate_subject(self) -> QuerySet:
Julian's avatar
Julian committed
        """Annotate lesson documentations with the subjects."""
        return self.annotate(
            subject=Case(
                When(
                    lesson_period__isnull=False,
                    then="lesson_period__lesson__subject__name",
                ),
                When(
                    extra_lesson__isnull=False,
                    then="extra_lesson__subject__name",
                ),
Julian's avatar
Julian committed
                default=Value(_("Event")),
Jonathan Weth's avatar
Jonathan Weth committed
class PersonalNoteManager(AlekSISBaseManagerWithoutMigrations):
    """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",
                "event",
                "extra_lesson",
                "extra_lesson__subject",
            )
            .prefetch_related("extra_marks")
        )
class PersonalNoteQuerySet(RegisterObjectRelatedQuerySet, QuerySet):
    def not_empty(self):
        """Get all not empty personal notes."""
        return self.filter(
            ~Q(remarks="") | Q(absent=True) | ~Q(tardiness=0) | Q(extra_marks__isnull=False)
Jonathan Weth's avatar
Jonathan Weth committed
class LessonDocumentationManager(AlekSISBaseManagerWithoutMigrations):
class LessonDocumentationQuerySet(RegisterObjectRelatedQuerySet, QuerySet):
    def not_empty(self):
        """Get all not empty lesson documentations."""
        return self.filter(~Q(topic="") | ~Q(group_note="") | ~Q(homework=""))
Jonathan Weth's avatar
Jonathan Weth committed
class GroupRoleManager(AlekSISBaseManagerWithoutMigrations):
    pass


class GroupRoleQuerySet(QuerySet):
    def with_assignments(
        self, time_ref: Union[date, CalendarWeek], groups: Sequence["Group"]
    ) -> QuerySet:
        from aleksis.apps.alsijil.models import GroupRoleAssignment

        if isinstance(time_ref, CalendarWeek):
            qs = GroupRoleAssignment.objects.in_week(time_ref)
        else:
            qs = GroupRoleAssignment.objects.on_day(time_ref)

        qs = qs.for_groups(groups).distinct()
        return self.prefetch_related(
            Prefetch(
                "assignments",
                queryset=qs,
            )
        )
Jonathan Weth's avatar
Jonathan Weth committed
class GroupRoleAssignmentManager(AlekSISBaseManagerWithoutMigrations):
    pass


class GroupRoleAssignmentQuerySet(DateRangeQuerySetMixin, QuerySet):
    def within_dates(self, start: date, end: date):
        """Filter for all role assignments within a date range."""
        return self.filter(
            Q(date_start__lte=end) & (Q(date_end__gte=start) | Q(date_end__isnull=True))
        )

    def at_time(self, when: Optional[datetime] = None):
        """Filter for role assignments assigned at a certain point in time."""
        now = when or datetime.now()

        return self.on_day(now.date())

    def for_groups(self, groups: Sequence["Group"]):
        """Filter all role assignments for a sequence of groups."""
        qs = self
        for group in groups:
            qs = qs.for_group(group)
        return qs

    def for_group(self, group: "Group"):
        """Filter all role assignments for a group."""
        return self.filter(Q(groups=group) | Q(groups__child_groups=group))
Julian's avatar
Julian committed
class DocumentationManager(RecurrencePolymorphicManager):
    """Manager adding specific methods to documentations."""

    def get_queryset(self):
        """Ensure often used related data are loaded as well."""
        return (
            super()
            .get_queryset()
            .select_related(
                "course",
                "subject",
            )
            .prefetch_related("teachers")
        )


Julian's avatar
Julian committed
class ParticipationStatusManager(RecurrencePolymorphicManager):
    """Manager adding specific methods to participation statuses."""

Julian's avatar
Julian committed
    pass