Newer
Older

Nik | Klampfradler
committed
from __future__ import annotations
from datetime import date, datetime, time, timedelta
from typing import Dict, List, Optional, Tuple, Union
from django.core.exceptions import ValidationError
from django.db.models import Max, Min, Q
from django.db.models.functions import Coalesce
from django.urls import reverse
from django.utils import timezone
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
from calendarweek.django import CalendarWeek, i18n_day_abbr_choices_lazy, i18n_day_name_choices_lazy
from django_global_request.middleware import get_request
from aleksis.apps.chronos.managers import (
AbsenceQuerySet,
ExtraLessonQuerySet,
GroupPropertiesMixin,
HolidayQuerySet,
LessonPeriodManager,
LessonPeriodQuerySet,
LessonSubstitutionManager,
LessonSubstitutionQuerySet,
from aleksis.apps.chronos.mixins import (
ValidityRangeRelatedExtensibleModel,
WeekAnnotationMixin,
WeekRelatedMixin,
)
from aleksis.apps.chronos.util.date import get_current_year
from aleksis.apps.chronos.util.format import format_m2m
from aleksis.core.managers import CurrentSiteManagerWithoutMigrations
from aleksis.core.mixins import ExtensibleModel, SchoolTermRelatedExtensibleModel
from aleksis.core.models import DashboardWidget, SchoolTerm
from aleksis.core.util.core_helpers import has_person
class ValidityRange(ExtensibleModel):
"""Validity range model.
This is used to link data to a validity range.
"""
objects = CurrentSiteManagerWithoutMigrations.from_queryset(ValidityRangeQuerySet)()
school_term = models.ForeignKey(
SchoolTerm,
on_delete=models.CASCADE,
verbose_name=_("School term"),
related_name="validity_ranges",
)
name = models.CharField(verbose_name=_("Name"), max_length=255, blank=True)
date_start = models.DateField(verbose_name=_("Start date"))
date_end = models.DateField(verbose_name=_("End date"))
@classmethod

Jonathan Weth
committed
@cache_memoize(3600)
def get_current(cls, day: Optional[date] = None):
if not day:
day = timezone.now().date()
try:
return cls.objects.on_day(day).first()
except ValidityRange.DoesNotExist:
return None
@classproperty
def current(cls):
return cls.get_current()
def clean(self):
"""Ensure there is only one validity range at each point of time."""
if self.date_end < self.date_start:
raise ValidationError(
_("The start date must be earlier than the end date.")
)
if self.school_term:
if (
self.date_end > self.school_term.date_end
or self.date_start < self.school_term.date_start
):
raise ValidationError(
_("The validity range must be within the school term.")
)
qs = ValidityRange.objects.within_dates(self.date_start, self.date_end)
if self.pk:
if qs.exists():
raise ValidationError(
_(
"There is already a validity range for this time or a part of this time."
)
)
def __str__(self):
return (
self.name or f"{date_format(self.date_start)}–{date_format(self.date_end)}"
)
class Meta:
verbose_name = _("Validity range")
verbose_name_plural = _("Validity ranges")
unique_together = ["date_start", "date_end"]
class TimePeriod(ValidityRangeRelatedExtensibleModel):
WEEKDAY_CHOICES = i18n_day_name_choices_lazy()
WEEKDAY_CHOICES_SHORT = i18n_day_abbr_choices_lazy()
verbose_name=_("Week day"), choices=i18n_day_name_choices_lazy()
period = models.PositiveSmallIntegerField(verbose_name=_("Number of period"))
time_start = models.TimeField(verbose_name=_("Start time"))
time_end = models.TimeField(verbose_name=_("End time"))
return f"{self.get_weekday_display()}, {self.period}."
def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
for period in cls.objects.for_current_or_all().all():
periods[period.period] = (period.time_start, period.time_end)
return periods
def get_date(self, week: Optional[CalendarWeek] = None) -> date:
if isinstance(week, CalendarWeek):
wanted_week = week
else:
year = getattr(self, "_year", None) or date.today().year
week_number = getattr(self, "_week", None) or CalendarWeek().week
wanted_week = CalendarWeek(year=year, week=week_number)
return wanted_week[self.weekday]
def get_datetime_start(
self, week: Optional[Union[CalendarWeek, int]] = None
) -> datetime:
"""Get datetime of lesson start in a specific week."""
day = self.get_date(week)
return datetime.combine(day, self.time_start)
def get_datetime_end(
self, week: Optional[Union[CalendarWeek, int]] = None
) -> datetime:
"""Get datetime of lesson end in a specific week."""
day = self.get_date(week)
return datetime.combine(day, self.time_end)
def get_next_relevant_day(
cls, day: Optional[date] = None, time: Optional[time] = None, prev: bool = False
) -> date:
"""Return next (previous) day with lessons depending on date and time."""
if day is None:
day = timezone.now().date()
if time is not None and cls.time_max and not prev:
if time > cls.time_max:
day += timedelta(days=1)
cw = CalendarWeek.from_date(day)
if day.weekday() > cls.weekday_max:
if prev:
day = cw[cls.weekday_max]
else:
cw += 1
day = cw[cls.weekday_min]
elif day.weekday() < TimePeriod.weekday_min:
if prev:
cw -= 1
day = cw[cls.weekday_max]
else:
day = cw[cls.weekday_min]
Loading
Loading full blame...