-
Jonathan Weth authoredJonathan Weth authored
model_extensions.py 8.83 KiB
from datetime import date
from typing import Dict, Iterator, Optional, Union
from django.db.models import Exists, OuterRef, Q, QuerySet
from django.db.models.aggregates import Count
from django.utils.translation import gettext as _
import reversion
from calendarweek import CalendarWeek
from aleksis.apps.chronos.models import LessonPeriod
from aleksis.core.models import Group, Person
from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
@Person.method
def mark_absent(
self,
day: date,
from_period: int = 0,
absent: bool = True,
excused: bool = False,
excuse_type: Optional[ExcuseType] = None,
remarks: str = "",
to_period: Optional[int] = None,
):
"""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.
..note:: Only available when AlekSIS-App-Alsijil is installed.
:Date: 2019-11-10
:Authors:
- Dominik George <dominik.george@teckids.org>
"""
wanted_week = CalendarWeek.from_date(day)
# Get all lessons of this person on the specified day
lesson_periods = (
self.lesson_periods_as_participant.on_day(day)
.filter(period__period__gte=from_period)
.annotate_week(wanted_week)
)
if to_period:
lesson_periods = lesson_periods.filter(period__period__lte=to_period)
# Create and update all personal notes for the discovered lesson periods
for lesson_period in lesson_periods:
sub = lesson_period.get_substitution()
if sub and sub.cancelled:
continue
with reversion.create_revision():
personal_note, created = (
PersonalNote.objects.select_related(None)
.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,
},
)
)
personal_note.groups_of_person.set(self.member_of.all())
if remarks:
if personal_note.remarks:
personal_note.remarks += "; %s" % remarks
else:
personal_note.remarks = remarks
personal_note.save()
@LessonPeriod.method
def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
"""Get all personal notes for that lesson in a specified week.
Returns all linked `PersonalNote` objects, filtered by the given weeek,
creating those objects that haven't been created yet.
..note:: Only available when AlekSIS-App-Alsijil is installed.
:Date: 2019-11-09
:Authors:
- Dominik George <dominik.george@teckids.org>
"""
# Find all persons in the associated groups that do not yet have a personal note for this lesson
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"),
)
)
).filter(
member_of__in=Group.objects.filter(pk__in=self.lesson.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
]
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,
)
.select_related(None)
.prefetch_related(None)
.select_related("person", "excuse_type")
.prefetch_related("extra_marks")
)
# Dynamically add extra permissions to Group and Person models in core
# Note: requires migrate afterwards
Group.add_permission(
"view_week_class_register_group",
_("Can view week overview of group class register"),
)
Group.add_permission(
"view_lesson_class_register_group",
_("Can view lesson overview of group class register"),
)
Group.add_permission(
"view_personalnote_group", _("Can view all personal notes of a group")
)
Group.add_permission(
"edit_personalnote_group", _("Can edit all personal notes of a group")
)
Group.add_permission(
"view_lessondocumentation_group", _("Can view all lesson documentation of a group")
)
Group.add_permission(
"edit_lessondocumentation_group", _("Can edit all lesson documentation of a group")
)
Group.add_permission("view_full_register_group", _("Can view full register of a group"))
Group.add_permission(
"register_absence_group", _("Can register an absence for all members of a group")
)
Person.add_permission(
"register_absence_person", _("Can register an absence for a person")
)
@LessonPeriod.method
def get_lesson_documentation(
self, week: Optional[CalendarWeek] = None
) -> Union[LessonDocumentation, None]:
"""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 next(doc_filter)
except StopIteration:
return None
@LessonPeriod.method
def get_or_create_lesson_documentation(
self, week: Optional[CalendarWeek] = None
) -> LessonDocumentation:
"""Get or create lesson documentation object for this lesson."""
if not week:
week = self.week
lesson_documentation, created = LessonDocumentation.objects.get_or_create(
lesson_period=self, week=week.week, year=week.year
)
return lesson_documentation
@LessonPeriod.method
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 filter(
lambda p: p.week == week.week and p.year == week.year and p.absent,
self.personal_notes.all(),
)
@LessonPeriod.method
def get_excused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet:
"""Get all personal notes of excused absent persons for this lesson."""
if not week:
week = self.week
return self.personal_notes.filter(
week=week.week, year=week.year, absent=True, excused=True
)
@LessonPeriod.method
def get_unexcused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet:
"""Get all personal notes of unexcused absent persons for this lesson."""
if not week:
week = self.week
return self.personal_notes.filter(
week=week.week, year=week.year, absent=True, excused=False
)
@LessonPeriod.method
def get_tardinesses(self, week: Optional[CalendarWeek] = None) -> QuerySet:
"""Get all personal notes of late persons for this lesson."""
if not week:
week = self.week
return self.personal_notes.filter(week=week.week, year=week.year, late__gt=0)
@LessonPeriod.method
def get_extra_marks(
self, week: Optional[CalendarWeek] = None
) -> Dict[ExtraMark, QuerySet]:
"""Get all statistics on extra marks for this lesson."""
if not week:
week = self.week
stats = {}
for extra_mark in ExtraMark.objects.all():
qs = self.personal_notes.filter(
week=week.week, year=week.year, extra_marks=extra_mark
)
if qs:
stats[extra_mark] = qs
return stats
@Group.class_method
def get_groups_with_lessons(cls: Group):
"""Get all groups which have related lessons or child groups with related lessons."""
group_pks = (
cls.objects.for_current_school_term_or_all()
.annotate(lessons_count=Count("lessons"))
.filter(lessons_count__gt=0)
.values_list("pk", flat=True)
)
groups = cls.objects.filter(
Q(child_groups__pk__in=group_pks) | Q(pk__in=group_pks)
).distinct()
return groups
@Person.method
def get_owner_groups_with_lessons(self: Person):
"""Get all groups the person is an owner of and which have related lessons.
Groups which have child groups with related lessons are also included.
"""
return Group.get_groups_with_lessons().filter(owners=self)