Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Chronos
  • sunweaver/AlekSIS-App-Chronos
  • sggua/AlekSIS-App-Chronos
  • tincmeKdenka/AlekSIS-App-Chronos
  • ligquamacti/AlekSIS-App-Chronos
  • 1crotatilhe/AlekSIS-App-Chronos
  • 1compluningi/AlekSIS-App-Chronos
  • starwardcarfi/AlekSIS-App-Chronos
  • ceohecholeg/AlekSIS-App-Chronos
  • 7quecontranchi/AlekSIS-App-Chronos
  • 8evsubcesza/AlekSIS-App-Chronos
  • unscinKibdzu/AlekSIS-App-Chronos
  • delucPchondmu/AlekSIS-App-Chronos
13 results
Show changes
Commits on Source (2)
...@@ -20,6 +20,7 @@ from .models import ( ...@@ -20,6 +20,7 @@ from .models import (
SupervisionSubstitution, SupervisionSubstitution,
TimePeriod, TimePeriod,
TimetableWidget, TimetableWidget,
ValidityRange,
) )
from .util.format import format_date_period, format_m2m from .util.format import format_date_period, format_m2m
...@@ -201,3 +202,11 @@ class TimetableWidgetAdmin(admin.ModelAdmin): ...@@ -201,3 +202,11 @@ class TimetableWidgetAdmin(admin.ModelAdmin):
admin.site.register(TimetableWidget, TimetableWidgetAdmin) 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 ...@@ -4,15 +4,67 @@ from typing import Optional, Union
from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager
from django.db import models 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 calendarweek import CalendarWeek
from aleksis.apps.chronos.util.date import week_weekday_from_date 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.models import Group, Person
from aleksis.core.util.core_helpers import get_site_preferences 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): class CurrentSiteManager(_CurrentSiteManager):
use_in_migrations = False use_in_migrations = False
...@@ -108,8 +160,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): ...@@ -108,8 +160,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
"""Filter for all lessons within a date range.""" """Filter for all lessons within a date range."""
return self.filter( return self.filter(
**{ **{
self._period_path + "lesson__date_start__lte": start, self._period_path + "lesson__validity__date_start__lte": start,
self._period_path + "lesson__date_end__gte": end, self._period_path + "lesson__validity__date_end__gte": end,
} }
) )
...@@ -137,8 +189,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): ...@@ -137,8 +189,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
return self.filter( return self.filter(
**{ **{
self._period_path + "lesson__date_start__lte": now.date(), self._period_path + "lesson__validity__date_start__lte": now.date(),
self._period_path + "lesson__date_end__gte": now.date(), self._period_path + "lesson__validity__date_end__gte": now.date(),
self._period_path + "period__weekday": now.weekday(), self._period_path + "period__weekday": now.weekday(),
self._period_path + "period__time_start__lte": now.time(), self._period_path + "period__time_start__lte": now.time(),
self._period_path + "period__time_end__gte": now.time(), self._period_path + "period__time_end__gte": now.time(),
...@@ -282,7 +334,7 @@ class LessonSubstitutionQuerySet(LessonDataQuerySet): ...@@ -282,7 +334,7 @@ class LessonSubstitutionQuerySet(LessonDataQuerySet):
) )
class DateRangeQuerySet(models.QuerySet): class DateRangeQuerySetMixin:
"""QuerySet with custom query methods for models with date and period ranges. """QuerySet with custom query methods for models with date and period ranges.
Filterable fields: date_start, date_end, period_from, period_to Filterable fields: date_start, date_end, period_from, period_to
...@@ -309,7 +361,7 @@ class DateRangeQuerySet(models.QuerySet): ...@@ -309,7 +361,7 @@ class DateRangeQuerySet(models.QuerySet):
) )
class AbsenceQuerySet(DateRangeQuerySet): class AbsenceQuerySet(SchoolTermRelatedQuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for absences.""" """QuerySet with custom query methods for absences."""
def absent_teachers(self): def absent_teachers(self):
...@@ -322,13 +374,13 @@ class AbsenceQuerySet(DateRangeQuerySet): ...@@ -322,13 +374,13 @@ class AbsenceQuerySet(DateRangeQuerySet):
return Person.objects.filter(absences__in=self).annotate(absences_count=Count("absences")) return Person.objects.filter(absences__in=self).annotate(absences_count=Count("absences"))
class HolidayQuerySet(DateRangeQuerySet): class HolidayQuerySet(QuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for holidays.""" """QuerySet with custom query methods for holidays."""
pass pass
class SupervisionQuerySet(models.QuerySet, WeekQuerySetMixin): class SupervisionQuerySet(ValidityRangeRelatedQuerySet, WeekQuerySetMixin):
"""QuerySet with custom query methods for supervisions.""" """QuerySet with custom query methods for supervisions."""
def filter_by_weekday(self, weekday: int): def filter_by_weekday(self, weekday: int):
...@@ -423,7 +475,9 @@ class TimetableQuerySet(models.QuerySet): ...@@ -423,7 +475,9 @@ class TimetableQuerySet(models.QuerySet):
return None return None
class EventQuerySet(DateRangeQuerySet, TimetableQuerySet): class EventQuerySet(
SchoolTermRelatedQuerySet, DateRangeQuerySetMixin, TimetableQuerySet
):
"""QuerySet with custom query methods for events.""" """QuerySet with custom query methods for events."""
def annotate_day(self, day: date): def annotate_day(self, day: date):
...@@ -431,7 +485,9 @@ class EventQuerySet(DateRangeQuerySet, TimetableQuerySet): ...@@ -431,7 +485,9 @@ class EventQuerySet(DateRangeQuerySet, TimetableQuerySet):
return self.annotate(_date=models.Value(day, models.DateField())) 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.""" """QuerySet with custom query methods for extra lessons."""
_multiple_rooms = False _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 ( ...@@ -33,14 +33,86 @@ from aleksis.apps.chronos.managers import (
LessonSubstitutionQuerySet, LessonSubstitutionQuerySet,
SupervisionQuerySet, SupervisionQuerySet,
TeacherPropertiesMixin, TeacherPropertiesMixin,
ValidityRangeQuerySet,
) )
from aleksis.apps.chronos.mixins import ValidityRangeRelatedExtensibleModel
from aleksis.apps.chronos.util.format import format_m2m 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.models import DashboardWidget, SchoolTerm
from aleksis.core.util.core_helpers import has_person 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 = list(enumerate(i18n_day_names_lazy()))
WEEKDAY_CHOICES_SHORT = list(enumerate(i18n_day_abbrs_lazy())) WEEKDAY_CHOICES_SHORT = list(enumerate(i18n_day_abbrs_lazy()))
...@@ -56,7 +128,7 @@ class TimePeriod(ExtensibleModel): ...@@ -56,7 +128,7 @@ class TimePeriod(ExtensibleModel):
@classmethod @classmethod
def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]: def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
periods = {} 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) periods[period.period] = (period.time_start, period.time_end)
return periods return periods
...@@ -127,27 +199,51 @@ class TimePeriod(ExtensibleModel): ...@@ -127,27 +199,51 @@ class TimePeriod(ExtensibleModel):
@classproperty @classproperty
def period_min(cls) -> int: 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 @classproperty
def period_max(cls) -> int: 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 @classproperty
def time_min(cls) -> Optional[time]: 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 @classproperty
def time_max(cls) -> Optional[time]: 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 @classproperty
def weekday_min(cls) -> int: 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 @classproperty
def weekday_max(cls) -> int: 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: class Meta:
unique_together = [["weekday", "period"]] unique_together = [["weekday", "period"]]
...@@ -186,7 +282,9 @@ class Room(ExtensibleModel): ...@@ -186,7 +282,9 @@ class Room(ExtensibleModel):
verbose_name_plural = _("Rooms") verbose_name_plural = _("Rooms")
class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): class Lesson(
ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin
):
subject = models.ForeignKey( subject = models.ForeignKey(
"Subject", on_delete=models.CASCADE, related_name="lessons", verbose_name=_("Subject"), "Subject", on_delete=models.CASCADE, related_name="lessons", verbose_name=_("Subject"),
) )
...@@ -198,13 +296,14 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): ...@@ -198,13 +296,14 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
) )
groups = models.ManyToManyField("core.Group", related_name="lessons", verbose_name=_("Groups")) groups = models.ManyToManyField("core.Group", related_name="lessons", verbose_name=_("Groups"))
date_start = models.DateField(verbose_name=_("Start date"), null=True) def get_year(self, week: int) -> int:
date_end = models.DateField(verbose_name=_("End date"), null=True) 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): def get_calendar_week(self, week: int):
year = self.date_start.year year = self.get_year(week)
if week < int(self.date_start.strftime("%V")):
year += 1
return CalendarWeek(year=year, week=week) return CalendarWeek(year=year, week=week)
...@@ -212,8 +311,7 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): ...@@ -212,8 +311,7 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}" return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}"
class Meta: class Meta:
ordering = ["date_start", "subject"] ordering = ["validity__date_start", "subject"]
indexes = [models.Index(fields=["date_start", "date_end"])]
verbose_name = _("Lesson") verbose_name = _("Lesson")
verbose_name_plural = _("Lessons") verbose_name_plural = _("Lessons")
...@@ -253,7 +351,7 @@ class LessonSubstitution(ExtensibleModel): ...@@ -253,7 +351,7 @@ class LessonSubstitution(ExtensibleModel):
@property @property
def date(self): 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] return week[self.lesson_period.period.weekday]
def __str__(self): def __str__(self):
...@@ -262,7 +360,7 @@ class LessonSubstitution(ExtensibleModel): ...@@ -262,7 +360,7 @@ class LessonSubstitution(ExtensibleModel):
class Meta: class Meta:
unique_together = [["lesson_period", "week"]] unique_together = [["lesson_period", "week"]]
ordering = [ ordering = [
"lesson_period__lesson__date_start", "lesson_period__lesson__validity__date_start",
"week", "week",
"lesson_period__period__weekday", "lesson_period__period__weekday",
"lesson_period__period__period", "lesson_period__period__period",
...@@ -362,7 +460,7 @@ class LessonPeriod(ExtensibleModel): ...@@ -362,7 +460,7 @@ class LessonPeriod(ExtensibleModel):
class Meta: class Meta:
ordering = [ ordering = [
"lesson__date_start", "lesson__validity__date_start",
"period__weekday", "period__weekday",
"period__period", "period__period",
"lesson__subject", "lesson__subject",
...@@ -427,7 +525,7 @@ class AbsenceReason(ExtensibleModel): ...@@ -427,7 +525,7 @@ class AbsenceReason(ExtensibleModel):
verbose_name_plural = _("Absence reasons") verbose_name_plural = _("Absence reasons")
class Absence(ExtensibleModel): class Absence(SchoolTermRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(AbsenceQuerySet)() objects = CurrentSiteManager.from_queryset(AbsenceQuerySet)()
reason = models.ForeignKey( reason = models.ForeignKey(
...@@ -499,7 +597,7 @@ class Absence(ExtensibleModel): ...@@ -499,7 +597,7 @@ class Absence(ExtensibleModel):
verbose_name_plural = _("Absences") verbose_name_plural = _("Absences")
class Exam(ExtensibleModel): class Exam(SchoolTermRelatedExtensibleModel):
lesson = models.ForeignKey( lesson = models.ForeignKey(
"Lesson", on_delete=models.CASCADE, related_name="exams", verbose_name=_("Lesson"), "Lesson", on_delete=models.CASCADE, related_name="exams", verbose_name=_("Lesson"),
) )
...@@ -583,7 +681,7 @@ class SupervisionArea(ExtensibleModel): ...@@ -583,7 +681,7 @@ class SupervisionArea(ExtensibleModel):
verbose_name_plural = _("Supervision areas") verbose_name_plural = _("Supervision areas")
class Break(ExtensibleModel): class Break(ValidityRangeRelatedExtensibleModel):
short_name = models.CharField(verbose_name=_("Short name"), max_length=255) short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
name = models.CharField(verbose_name=_("Long name"), max_length=255) name = models.CharField(verbose_name=_("Long name"), max_length=255)
...@@ -642,7 +740,7 @@ class Break(ExtensibleModel): ...@@ -642,7 +740,7 @@ class Break(ExtensibleModel):
verbose_name_plural = _("Breaks") verbose_name_plural = _("Breaks")
class Supervision(ExtensibleModel): class Supervision(ValidityRangeRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(SupervisionQuerySet)() objects = CurrentSiteManager.from_queryset(SupervisionQuerySet)()
area = models.ForeignKey( area = models.ForeignKey(
...@@ -708,7 +806,9 @@ class SupervisionSubstitution(ExtensibleModel): ...@@ -708,7 +806,9 @@ class SupervisionSubstitution(ExtensibleModel):
verbose_name_plural = _("Supervision substitutions") verbose_name_plural = _("Supervision substitutions")
class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): class Event(
SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin
):
label_ = "event" label_ = "event"
objects = CurrentSiteManager.from_queryset(EventQuerySet)() objects = CurrentSiteManager.from_queryset(EventQuerySet)()
...@@ -763,7 +863,7 @@ class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): ...@@ -763,7 +863,7 @@ class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
verbose_name_plural = _("Events") verbose_name_plural = _("Events")
class ExtraLesson(ExtensibleModel, GroupPropertiesMixin): class ExtraLesson(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin):
label_ = "extra_lesson" label_ = "extra_lesson"
objects = CurrentSiteManager.from_queryset(ExtraLessonQuerySet)() objects = CurrentSiteManager.from_queryset(ExtraLessonQuerySet)()
......
...@@ -124,7 +124,12 @@ def build_timetable( ...@@ -124,7 +124,12 @@ def build_timetable(
week = CalendarWeek.from_date(date_ref) week = CalendarWeek.from_date(date_ref)
else: else:
week = date_ref week = date_ref
supervisions = Supervision.objects.all().annotate_week(week).filter_by_teacher(obj) supervisions = (
Supervision.objects.in_week(week)
.all()
.annotate_week(week)
.filter_by_teacher(obj)
)
if is_person: if is_person:
supervisions.filter_by_weekday(date_ref.weekday()) supervisions.filter_by_weekday(date_ref.weekday())
......