-
Jonathan Weth authoredJonathan Weth authored
managers.py 6.97 KiB
from datetime import date, datetime
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.db.models.query_utils import Q
from django.utils.translation import gettext as _
from calendarweek import CalendarWeek
from aleksis.apps.chronos.managers import DateRangeQuerySetMixin
from aleksis.core.managers import AlekSISBaseManagerWithoutMigrations, PolymorphicBaseManager
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:
"""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",
),
default=Value(_("Event")),
)
)
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)
)
class LessonDocumentationManager(AlekSISBaseManagerWithoutMigrations):
pass
class LessonDocumentationQuerySet(RegisterObjectRelatedQuerySet, QuerySet):
def not_empty(self):
"""Get all not empty lesson documentations."""
return self.filter(~Q(topic="") | ~Q(group_note="") | ~Q(homework=""))
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,
)
)
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))
class DocumentationManager(PolymorphicBaseManager):
"""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")
)
class ParticipationStatusManager(PolymorphicBaseManager):
"""Manager adding specific methods to participation statuses."""
def get_queryset(self):
"""Ensure often used related data are loaded as well."""
return super().get_queryset().select_related("person", "absence_reason", "base_absence")