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 (
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,
}
)
......@@ -137,8 +189,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(),
......@@ -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.
Filterable fields: date_start, date_end, period_from, period_to
......@@ -309,7 +361,7 @@ class DateRangeQuerySet(models.QuerySet):
)
class AbsenceQuerySet(DateRangeQuerySet):
class AbsenceQuerySet(SchoolTermRelatedQuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for absences."""
def absent_teachers(self):
......@@ -322,13 +374,13 @@ class AbsenceQuerySet(DateRangeQuerySet):
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."""
pass
class SupervisionQuerySet(models.QuerySet, WeekQuerySetMixin):
class SupervisionQuerySet(ValidityRangeRelatedQuerySet, WeekQuerySetMixin):
"""QuerySet with custom query methods for supervisions."""
def filter_by_weekday(self, weekday: int):
......@@ -423,7 +475,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):
......@@ -431,7 +485,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()))
......@@ -56,7 +128,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
......@@ -127,27 +199,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:
unique_together = [["weekday", "period"]]
......@@ -186,7 +282,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, related_name="lessons", verbose_name=_("Subject"),
)
......@@ -198,13 +296,14 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
)
groups = models.ManyToManyField("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)
......@@ -212,8 +311,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")
......@@ -253,7 +351,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):
......@@ -262,7 +360,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",
......@@ -362,7 +460,7 @@ class LessonPeriod(ExtensibleModel):
class Meta:
ordering = [
"lesson__date_start",
"lesson__validity__date_start",
"period__weekday",
"period__period",
"lesson__subject",
......@@ -427,7 +525,7 @@ class AbsenceReason(ExtensibleModel):
verbose_name_plural = _("Absence reasons")
class Absence(ExtensibleModel):
class Absence(SchoolTermRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(AbsenceQuerySet)()
reason = models.ForeignKey(
......@@ -499,7 +597,7 @@ class Absence(ExtensibleModel):
verbose_name_plural = _("Absences")
class Exam(ExtensibleModel):
class Exam(SchoolTermRelatedExtensibleModel):
lesson = models.ForeignKey(
"Lesson", on_delete=models.CASCADE, related_name="exams", verbose_name=_("Lesson"),
)
......@@ -583,7 +681,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)
......@@ -642,7 +740,7 @@ class Break(ExtensibleModel):
verbose_name_plural = _("Breaks")
class Supervision(ExtensibleModel):
class Supervision(ValidityRangeRelatedExtensibleModel):
objects = CurrentSiteManager.from_queryset(SupervisionQuerySet)()
area = models.ForeignKey(
......@@ -708,7 +806,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)()
......@@ -763,7 +863,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)()
......
......@@ -124,7 +124,12 @@ def build_timetable(
week = CalendarWeek.from_date(date_ref)
else:
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:
supervisions.filter_by_weekday(date_ref.weekday())
......