Skip to content
Snippets Groups Projects
Verified Commit ae8988d7 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch 'master' into reformat

parents e32c5570 75e6e700
No related branches found
No related tags found
1 merge request!67Reformat all files
Pipeline #2883 passed
......@@ -20,6 +20,7 @@ from .models import (
SupervisionSubstitution,
TimePeriod,
TimetableWidget,
ValidityRange,
)
from .util.format import format_date_period, format_m2m
......@@ -201,3 +202,11 @@ class TimetableWidgetAdmin(admin.ModelAdmin):
admin.site.register(TimetableWidget, TimetableWidgetAdmin)
class ValidityRangeAdmin(admin.ModelAdmin):
list_display = ("__str__", "date_start", "date_end")
list_display_links = ("__str__", "date_start", "date_end")
admin.site.register(ValidityRange, ValidityRangeAdmin)
......@@ -4,15 +4,67 @@ from typing import Optional, Union
from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager
from django.db import models
from django.db.models import Count, F, Q
from django.db.models import Count, F, Q, QuerySet
from calendarweek import CalendarWeek
from aleksis.apps.chronos.util.date import week_weekday_from_date
from aleksis.core.managers import DateRangeQuerySetMixin, SchoolTermRelatedQuerySet
from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import get_site_preferences
class ValidityRangeQuerySet(QuerySet, DateRangeQuerySetMixin):
"""Custom query set for validity ranges."""
class ValidityRangeRelatedQuerySet(QuerySet):
"""Custom query set for all models related to validity ranges."""
def within_dates(self, start: date, end: date) -> "ValidityRangeRelatedQuerySet":
"""Filter for all objects within a date range."""
return self.filter(validity__date_start__lte=end, validity__date_end__gte=start)
def in_week(self, wanted_week: CalendarWeek) -> "ValidityRangeRelatedQuerySet":
"""Filter for all objects within a calendar week."""
return self.within_dates(wanted_week[0], wanted_week[6])
def on_day(self, day: date) -> "ValidityRangeRelatedQuerySet":
"""Filter for all objects on a certain day."""
return self.within_dates(day, day)
def for_validity_range(
self, validity_range: "ValidityRange"
) -> "ValidityRangeRelatedQuerySet":
return self.filter(validity_range=validity_range)
def for_current_or_all(self) -> "ValidityRangeRelatedQuerySet":
"""Get all objects related to current validity range.
If there is no current validity range, it will return all objects.
"""
from aleksis.apps.chronos.models import ValidityRange
current_validity_range = ValidityRange.current
if current_validity_range:
return self.for_validity_range(current_validity_range)
else:
return self
def for_current_or_none(self) -> Union["ValidityRangeRelatedQuerySet", None]:
"""Get all objects related to current validity range.
If there is no current validity range, it will return `None`.
"""
from aleksis.apps.chronos.models import ValidityRange
current_validity_range = ValidityRange.current
if current_validity_range:
return self.for_validity_range(current_validity_range)
else:
return None
class CurrentSiteManager(_CurrentSiteManager):
use_in_migrations = False
......@@ -108,8 +160,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
"""Filter for all lessons within a date range."""
return self.filter(
**{
self._period_path + "lesson__date_start__lte": start,
self._period_path + "lesson__date_end__gte": end,
self._period_path + "lesson__validity__date_start__lte": start,
self._period_path + "lesson__validity__date_end__gte": end,
}
)
......@@ -139,8 +191,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
return self.filter(
**{
self._period_path + "lesson__date_start__lte": now.date(),
self._period_path + "lesson__date_end__gte": now.date(),
self._period_path + "lesson__validity__date_start__lte": now.date(),
self._period_path + "lesson__validity__date_end__gte": now.date(),
self._period_path + "period__weekday": now.weekday(),
self._period_path + "period__time_start__lte": now.time(),
self._period_path + "period__time_end__gte": now.time(),
......@@ -294,7 +346,7 @@ class LessonSubstitutionQuerySet(LessonDataQuerySet):
)
class DateRangeQuerySet(models.QuerySet):
class DateRangeQuerySetMixin:
"""QuerySet with custom query methods for models with date and period ranges.
Filterable fields: date_start, date_end, period_from, period_to
......@@ -321,7 +373,7 @@ class DateRangeQuerySet(models.QuerySet):
)
class AbsenceQuerySet(DateRangeQuerySet):
class AbsenceQuerySet(SchoolTermRelatedQuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for absences."""
def absent_teachers(self):
......@@ -340,13 +392,13 @@ class AbsenceQuerySet(DateRangeQuerySet):
)
class HolidayQuerySet(DateRangeQuerySet):
class HolidayQuerySet(QuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for holidays."""
pass
class SupervisionQuerySet(models.QuerySet, WeekQuerySetMixin):
class SupervisionQuerySet(ValidityRangeRelatedQuerySet, WeekQuerySetMixin):
"""QuerySet with custom query methods for supervisions."""
def filter_by_weekday(self, weekday: int):
......@@ -443,7 +495,9 @@ class TimetableQuerySet(models.QuerySet):
return None
class EventQuerySet(DateRangeQuerySet, TimetableQuerySet):
class EventQuerySet(
SchoolTermRelatedQuerySet, DateRangeQuerySetMixin, TimetableQuerySet
):
"""QuerySet with custom query methods for events."""
def annotate_day(self, day: date):
......@@ -451,7 +505,9 @@ class EventQuerySet(DateRangeQuerySet, TimetableQuerySet):
return self.annotate(_date=models.Value(day, models.DateField()))
class ExtraLessonQuerySet(TimetableQuerySet, GroupByPeriodsMixin):
class ExtraLessonQuerySet(
SchoolTermRelatedQuerySet, TimetableQuerySet, GroupByPeriodsMixin
):
"""QuerySet with custom query methods for extra lessons."""
_multiple_rooms = False
......
from django.db import models
from django.utils.translation import gettext as _
from aleksis.core.managers import CurrentSiteManagerWithoutMigrations
from aleksis.core.mixins import ExtensibleModel
from .managers import ValidityRangeRelatedQuerySet
class ValidityRangeRelatedExtensibleModel(ExtensibleModel):
"""Add relation to validity range."""
objects = CurrentSiteManagerWithoutMigrations.from_queryset(
ValidityRangeRelatedQuerySet
)()
validity = models.ForeignKey(
"chronos.ValidityRange",
on_delete=models.CASCADE,
related_name="+",
verbose_name=_("Linked validity range"),
null=True,
blank=True,
)
class Meta:
abstract = True
......@@ -33,14 +33,86 @@ from aleksis.apps.chronos.managers import (
LessonSubstitutionQuerySet,
SupervisionQuerySet,
TeacherPropertiesMixin,
ValidityRangeQuerySet,
)
from aleksis.apps.chronos.mixins import ValidityRangeRelatedExtensibleModel
from aleksis.apps.chronos.util.format import format_m2m
from aleksis.core.mixins import ExtensibleModel
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 TimePeriod(ExtensibleModel):
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
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:
qs.exclude(pk=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 = list(enumerate(i18n_day_names_lazy()))
WEEKDAY_CHOICES_SHORT = list(enumerate(i18n_day_abbrs_lazy()))
......@@ -58,7 +130,7 @@ class TimePeriod(ExtensibleModel):
@classmethod
def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
periods = {}
for period in cls.objects.all():
for period in cls.objects.for_current_or_all().all():
periods[period.period] = (period.time_start, period.time_end)
return periods
......@@ -133,34 +205,51 @@ class TimePeriod(ExtensibleModel):
@classproperty
def period_min(cls) -> int:
return cls.objects.aggregate(period__min=Coalesce(Min("period"), 1)).get(
"period__min"
return (
cls.objects.for_current_or_all()
.aggregate(period__min=Coalesce(Min("period"), 1))
.get("period__min")
)
@classproperty
def period_max(cls) -> int:
return cls.objects.aggregate(period__max=Coalesce(Max("period"), 7)).get(
"period__max"
return (
cls.objects.for_current_or_all()
.aggregate(period__max=Coalesce(Max("period"), 7))
.get("period__max")
)
@classproperty
def time_min(cls) -> Optional[time]:
return cls.objects.aggregate(Min("time_start")).get("time_start__min")
return (
cls.objects.for_current_or_all()
.aggregate(Min("time_start"))
.get("time_start__min")
)
@classproperty
def time_max(cls) -> Optional[time]:
return cls.objects.aggregate(Max("time_end")).get("time_end__max")
return (
cls.objects.for_current_or_all()
.aggregate(Max("time_end"))
.get("time_end__max")
)
@classproperty
def weekday_min(cls) -> int:
return cls.objects.aggregate(weekday__min=Coalesce(Min("weekday"), 0)).get(
"weekday__min"
return (
cls.objects.for_current_or_all()
.aggregate(weekday__min=Coalesce(Min("weekday"), 0))
.get("weekday__min")
)
@classproperty
def weekday_max(cls) -> int:
return cls.objects.aggregate(weekday__max=Coalesce(Max("weekday"), 6)).get(
"weekday__max"
return (
cls.objects.for_current_or_all()
.aggregate(weekday__max=Coalesce(Max("weekday"), 6))
.get("weekday__max")
)
class Meta:
......@@ -204,7 +293,9 @@ class Room(ExtensibleModel):
verbose_name_plural = _("Rooms")
class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
class Lesson(
ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin
):
subject = models.ForeignKey(
"Subject",
on_delete=models.CASCADE,
......@@ -224,13 +315,14 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
"core.Group", related_name="lessons", verbose_name=_("Groups")
)
date_start = models.DateField(verbose_name=_("Start date"), null=True)
date_end = models.DateField(verbose_name=_("End date"), null=True)
def get_year(self, week: int) -> int:
year = self.validity.date_start.year
if week < int(self.validity.date_start.strftime("%V")):
year += 1
return year
def get_calendar_week(self, week: int):
year = self.date_start.year
if week < int(self.date_start.strftime("%V")):
year += 1
year = self.get_year(week)
return CalendarWeek(year=year, week=week)
......@@ -238,8 +330,7 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}"
class Meta:
ordering = ["date_start", "subject"]
indexes = [models.Index(fields=["date_start", "date_end"])]
ordering = ["validity__date_start", "subject"]
verbose_name = _("Lesson")
verbose_name_plural = _("Lessons")
......@@ -288,7 +379,7 @@ class LessonSubstitution(ExtensibleModel):
@property
def date(self):
week = CalendarWeek(week=self.week)
week = CalendarWeek(week=self.week, year=self.lesson_period.lesson.get_year())
return week[self.lesson_period.period.weekday]
def __str__(self):
......@@ -297,7 +388,7 @@ class LessonSubstitution(ExtensibleModel):
class Meta:
unique_together = [["lesson_period", "week"]]
ordering = [
"lesson_period__lesson__date_start",
"lesson_period__lesson__validity__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
......@@ -407,7 +498,7 @@ class LessonPeriod(ExtensibleModel):
class Meta:
ordering = [
"lesson__date_start",
"lesson__validity__date_start",
"period__weekday",
"period__period",
"lesson__subject",
......@@ -476,7 +567,7 @@ class AbsenceReason(ExtensibleModel):
verbose_name_plural = _("Absence reasons")
class Absence(ExtensibleModel):
class Absence(SchoolTermRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(AbsenceQuerySet)()
reason = models.ForeignKey(
......@@ -548,7 +639,7 @@ class Absence(ExtensibleModel):
verbose_name_plural = _("Absences")
class Exam(ExtensibleModel):
class Exam(SchoolTermRelatedExtensibleModel):
lesson = models.ForeignKey(
"Lesson",
on_delete=models.CASCADE,
......@@ -635,7 +726,7 @@ class SupervisionArea(ExtensibleModel):
verbose_name_plural = _("Supervision areas")
class Break(ExtensibleModel):
class Break(ValidityRangeRelatedExtensibleModel):
short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
name = models.CharField(verbose_name=_("Long name"), max_length=255)
......@@ -706,7 +797,7 @@ class Break(ExtensibleModel):
verbose_name_plural = _("Breaks")
class Supervision(ExtensibleModel):
class Supervision(ValidityRangeRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(SupervisionQuerySet)()
area = models.ForeignKey(
......@@ -780,7 +871,9 @@ class SupervisionSubstitution(ExtensibleModel):
verbose_name_plural = _("Supervision substitutions")
class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
class Event(
SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin
):
label_ = "event"
objects = CurrentSiteManager.from_queryset(EventQuerySet)()
......@@ -846,7 +939,7 @@ class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
verbose_name_plural = _("Events")
class ExtraLesson(ExtensibleModel, GroupPropertiesMixin):
class ExtraLesson(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin):
label_ = "extra_lesson"
objects = CurrentSiteManager.from_queryset(ExtraLessonQuerySet)()
......
......@@ -131,7 +131,10 @@ def build_timetable(
else:
week = date_ref
supervisions = (
Supervision.objects.all().annotate_week(week).filter_by_teacher(obj)
Supervision.objects.in_week(week)
.all()
.annotate_week(week)
.filter_by_teacher(obj)
)
if is_person:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment