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

Merge branch 'check/update-tox-ini' into 'master'

Update tox.ini

See merge request !316
parents 36d20af8 41bb9da6
No related branches found
No related tags found
1 merge request!316Update tox.ini
Pipeline #163822 failed
Pipeline: AlekSIS

#164859

    ......@@ -73,6 +73,9 @@ docs/_build/
    /static/
    /whoosh_index/
    .vite
    .dev-js/.yarn
    .dev-js/.pnp.cjs
    .dev-js/.pnp.loader.mjs
    # Lock files
    poetry.lock
    ......
    ......@@ -89,3 +89,6 @@ yarn.lock
    # Do not check/reformat generated files
    aleksis/core/util/licenses.json
    .vite/
    .pnp.cjs
    .pnp.loader.mjs
    from typing import Sequence
    from collections.abc import Sequence
    from django.db.models import Count, Q
    from django.forms import RadioSelect
    ......
    from collections.abc import Iterable
    from datetime import date, datetime, timedelta
    from enum import Enum
    from typing import Dict, Iterable, List, Optional, Union
    from typing import TYPE_CHECKING, Optional, Union
    from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager
    from django.db import models
    ......@@ -15,6 +16,9 @@ from aleksis.core.managers import DateRangeQuerySetMixin, SchoolTermRelatedQuery
    from aleksis.core.models import Group, Person
    from aleksis.core.util.core_helpers import get_site_preferences
    if TYPE_CHECKING:
    from .models import Holiday, LessonPeriod, Room, ValidityRange
    class ValidityRangeQuerySet(QuerySet, DateRangeQuerySetMixin):
    """Custom query set for validity ranges."""
    ......@@ -402,7 +406,7 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
    return lesson_periods
    def group_by_validity(self) -> Dict["ValidityRange", List["LessonPeriod"]]:
    def group_by_validity(self) -> dict["ValidityRange", list["LessonPeriod"]]:
    """Group lesson periods by validity range as dictionary."""
    lesson_periods_by_validity = {}
    for lesson_period in self:
    ......@@ -635,7 +639,7 @@ class AbsenceQuerySet(DateRangeQuerySetMixin, SchoolTermRelatedQuerySet):
    class HolidayQuerySet(QuerySet, DateRangeQuerySetMixin):
    """QuerySet with custom query methods for holidays."""
    def get_all_days(self) -> List[date]:
    def get_all_days(self) -> list[date]:
    """Get all days included in the selected holidays."""
    holiday_days = []
    for holiday in self:
    ......
    ......@@ -2,22 +2,19 @@
    from __future__ import annotations
    import os
    from collections.abc import Iterable, Iterator
    from datetime import date, datetime, time, timedelta
    from itertools import chain
    from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
    from typing import Any
    from django.contrib.contenttypes.models import ContentType
    from django.core.exceptions import ValidationError
    from django.core.files.base import ContentFile, File
    from django.core.files.storage import default_storage
    from django.core.validators import MinValueValidator
    from django.db import models
    from django.db.models import Max, Min, Q
    from django.db.models.functions import Coalesce
    from django.dispatch import receiver
    from django.forms import Media
    from django.template.loader import render_to_string
    from django.urls import reverse
    from django.utils import timezone
    from django.utils.formats import date_format
    ......@@ -26,8 +23,6 @@ from django.utils.translation import gettext_lazy as _
    from cache_memoize import cache_memoize
    from calendarweek.django import CalendarWeek, i18n_day_abbr_choices_lazy, i18n_day_name_choices_lazy
    from celery.result import allow_join_result
    from celery.states import SUCCESS
    from colorfield.fields import ColorField
    from model_utils import FieldTracker
    from reversion.models import Revision, Version
    ......@@ -69,7 +64,6 @@ from aleksis.core.mixins import (
    )
    from aleksis.core.models import DashboardWidget, Group, Room, SchoolTerm
    from aleksis.core.util.core_helpers import has_person
    from aleksis.core.util.pdf import generate_pdf_from_template
    class ValidityRange(ExtensibleModel):
    ......@@ -93,7 +87,7 @@ class ValidityRange(ExtensibleModel):
    @classmethod
    @cache_memoize(3600)
    def get_current(cls, day: Optional[date] = None):
    def get_current(cls, day: date | None = None):
    if not day:
    day = timezone.now().date()
    try:
    ......@@ -110,12 +104,11 @@ class ValidityRange(ExtensibleModel):
    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."))
    if self.school_term and (
    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:
    ......@@ -158,14 +151,14 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    return f"{self.get_weekday_display()}, {self.period}."
    @classmethod
    def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
    def get_times_dict(cls) -> dict[int, tuple[datetime, datetime]]:
    periods = {}
    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:
    def get_date(self, week: CalendarWeek | None = None) -> date:
    if isinstance(week, CalendarWeek):
    wanted_week = week
    else:
    ......@@ -176,37 +169,26 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    return wanted_week[self.weekday]
    def get_datetime_start(
    self, date_ref: Optional[Union[CalendarWeek, int, date]] = None
    ) -> datetime:
    def get_datetime_start(self, date_ref: CalendarWeek | int | date | None = None) -> datetime:
    """Get datetime of lesson start in a specific week."""
    if isinstance(date_ref, date):
    day = date_ref
    else:
    day = self.get_date(date_ref)
    day = date_ref if isinstance(date_ref, date) else self.get_date(date_ref)
    return datetime.combine(day, self.time_start)
    def get_datetime_end(
    self, date_ref: Optional[Union[CalendarWeek, int, date]] = None
    ) -> datetime:
    def get_datetime_end(self, date_ref: CalendarWeek | int | date | None = None) -> datetime:
    """Get datetime of lesson end in a specific week."""
    if isinstance(date_ref, date):
    day = date_ref
    else:
    day = self.get_date(date_ref)
    day = date_ref if isinstance(date_ref, date) else self.get_date(date_ref)
    return datetime.combine(day, self.time_end)
    @classmethod
    def get_next_relevant_day(
    cls, day: Optional[date] = None, time: Optional[time] = None, prev: bool = False
    cls, day: date | None = None, time: time | None = 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)
    if time is not None and cls.time_max and not prev and time > cls.time_max:
    day += timedelta(days=1)
    cw = CalendarWeek.from_date(day)
    ......@@ -226,7 +208,7 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    return day
    @classmethod
    def get_relevant_week_from_datetime(cls, when: Optional[datetime] = None) -> CalendarWeek:
    def get_relevant_week_from_datetime(cls, when: datetime | None = None) -> CalendarWeek:
    """Return currently relevant week depending on current date and time."""
    if not when:
    when = timezone.now()
    ......@@ -236,15 +218,15 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    week = CalendarWeek.from_date(day)
    if cls.weekday_max and day.weekday() > cls.weekday_max:
    week += 1
    elif cls.time_max and time > cls.time_max and day.weekday() == cls.weekday_max:
    if (cls.weekday_max and day.weekday() > cls.weekday_max) or (
    cls.time_max and time > cls.time_max and day.weekday() == cls.weekday_max
    ):
    week += 1
    return week
    @classmethod
    def get_prev_next_by_day(cls, day: date, url: str) -> Tuple[str, str]:
    def get_prev_next_by_day(cls, day: date, url: str) -> tuple[str, str]:
    """Build URLs for previous/next day."""
    day_prev = cls.get_next_relevant_day(day - timedelta(days=1), prev=True)
    day_next = cls.get_next_relevant_day(day + timedelta(days=1))
    ......@@ -255,7 +237,7 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    return url_prev, url_next
    @classmethod
    def from_period(cls, period: int, day: date) -> "TimePeriod":
    def from_period(cls, period: int, day: date) -> TimePeriod:
    """Get `TimePeriod` object for a period on a specific date.
    This will respect the relation to validity ranges.
    ......@@ -282,12 +264,12 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    @classproperty
    @cache_memoize(3600)
    def time_min(cls) -> Optional[time]:
    def time_min(cls) -> time | None:
    return cls.objects.for_current_or_all().aggregate(Min("time_start")).get("time_start__min")
    @classproperty
    @cache_memoize(3600)
    def time_max(cls) -> Optional[time]:
    def time_max(cls) -> time | None:
    return cls.objects.for_current_or_all().aggregate(Max("time_end")).get("time_end__max")
    @classproperty
    ......@@ -310,7 +292,7 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
    @classproperty
    @cache_memoize(3600)
    def period_choices(cls) -> List[Tuple[Union[str, int], str]]:
    def period_choices(cls) -> list[tuple[str | int, str]]:
    """Build choice list of periods for usage within Django."""
    time_periods = (
    cls.objects.filter(weekday=cls.weekday_min)
    ......@@ -449,7 +431,7 @@ class LessonSubstitution(ExtensibleModel, TeacherPropertiesMixin, WeekRelatedMix
    default=False, verbose_name=_("Cancelled for teachers?")
    )
    comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
    comment = models.TextField(verbose_name=_("Comment"), blank=True)
    def clean(self) -> None:
    if self.subject and self.cancelled:
    ......@@ -524,7 +506,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    verbose_name=_("Room"),
    )
    def get_substitution(self, week: Optional[CalendarWeek] = None) -> LessonSubstitution:
    def get_substitution(self, week: CalendarWeek | None = None) -> LessonSubstitution:
    wanted_week = week or self.week or CalendarWeek()
    # We iterate over all substitutions because this can make use of
    ......@@ -535,7 +517,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    return substitution
    return None
    def get_subject(self) -> Optional[Subject]:
    def get_subject(self) -> Subject | None:
    sub = self.get_substitution()
    if sub and sub.subject:
    return sub.subject
    ......@@ -549,7 +531,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    else:
    return self.lesson.teachers
    def get_room(self) -> Optional[Room]:
    def get_room(self) -> Room | None:
    if self.get_substitution() and self.get_substitution().room:
    return self.get_substitution().room
    else:
    ......@@ -578,7 +560,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    return LessonPeriod.objects.filter(lesson__in=self.lesson._equal_lessons)
    @property
    def next(self) -> "LessonPeriod":
    def next(self) -> LessonPeriod: # noqa
    """Get next lesson period of this lesson.
    .. warning::
    ......@@ -587,7 +569,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    return self._equal_lesson_periods.next_lesson(self)
    @property
    def prev(self) -> "LessonPeriod":
    def prev(self) -> LessonPeriod:
    """Get previous lesson period of this lesson.
    .. warning::
    ......@@ -596,7 +578,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
    return self._equal_lesson_periods.next_lesson(self, -1)
    def is_replaced_by_event(
    self, events: Iterable[Event], groups: Optional[Iterable[Group]] = None
    self, events: Iterable[Event], groups: Iterable[Group] | None = None
    ) -> bool:
    """Check if this lesson period is replaced by an event."""
    groups_of_event = set(chain(*[event.groups.all() for event in events]))
    ......@@ -683,7 +665,7 @@ class TimetableWidget(DashboardWidget):
    class AbsenceReason(ExtensibleModel):
    short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
    name = models.CharField(verbose_name=_("Name"), blank=True, null=True, max_length=255)
    name = models.CharField(verbose_name=_("Name"), blank=True, max_length=255)
    def __str__(self):
    if self.name:
    ......@@ -754,7 +736,7 @@ class Absence(SchoolTermRelatedExtensibleModel):
    null=True,
    related_name="+",
    )
    comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
    comment = models.TextField(verbose_name=_("Comment"), blank=True)
    def __str__(self):
    if self.teacher:
    ......@@ -813,7 +795,7 @@ class Holiday(ExtensibleModel):
    title = models.CharField(verbose_name=_("Title"), max_length=255)
    date_start = models.DateField(verbose_name=_("Start date"), null=True)
    date_end = models.DateField(verbose_name=_("End date"), null=True)
    comments = models.TextField(verbose_name=_("Comments"), blank=True, null=True)
    comments = models.TextField(verbose_name=_("Comments"), blank=True)
    def get_days(self) -> Iterator[date]:
    delta = self.date_end - self.date_start
    ......@@ -821,7 +803,7 @@ class Holiday(ExtensibleModel):
    yield self.date_start + timedelta(days=i)
    @classmethod
    def on_day(cls, day: date) -> Optional["Holiday"]:
    def on_day(cls, day: date) -> Holiday | None:
    holidays = cls.objects.on_day(day)
    if holidays.exists():
    return holidays[0]
    ......@@ -829,7 +811,7 @@ class Holiday(ExtensibleModel):
    return None
    @classmethod
    def in_week(cls, week: CalendarWeek) -> Dict[int, Optional["Holiday"]]:
    def in_week(cls, week: CalendarWeek) -> dict[int, Holiday | None]:
    per_weekday = {}
    holidays = Holiday.objects.in_week(week)
    ......@@ -920,7 +902,7 @@ class Break(ValidityRangeRelatedExtensibleModel):
    return self.before_period.time_start if self.before_period else None
    @classmethod
    def get_breaks_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
    def get_breaks_dict(cls) -> dict[int, tuple[datetime, datetime]]:
    breaks = {}
    for break_ in cls.objects.all():
    breaks[break_.before_period_number] = break_
    ......@@ -972,9 +954,7 @@ class Supervision(ValidityRangeRelatedExtensibleModel, WeekAnnotationMixin):
    return CalendarWeek(year=year, week=week)
    def get_substitution(
    self, week: Optional[CalendarWeek] = None
    ) -> Optional[SupervisionSubstitution]:
    def get_substitution(self, week: CalendarWeek | None = None) -> SupervisionSubstitution | None:
    wanted_week = week or self.week or CalendarWeek()
    # We iterate over all substitutions because this can make use of
    # prefetching when this model is loaded from outside, in contrast
    ......@@ -1048,7 +1028,7 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
    objects = EventManager.from_queryset(EventQuerySet)()
    title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True, null=True)
    title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True)
    date_start = models.DateField(verbose_name=_("Start date"), null=True)
    date_end = models.DateField(verbose_name=_("End date"), null=True)
    ......@@ -1219,7 +1199,7 @@ class ExtraLesson(
    verbose_name=_("Room"),
    )
    comment = models.CharField(verbose_name=_("Comment"), blank=True, null=True, max_length=255)
    comment = models.CharField(verbose_name=_("Comment"), blank=True, max_length=255)
    exam = models.ForeignKey(
    "Exam",
    ......@@ -1293,7 +1273,7 @@ class AutomaticPlan(LiveDocument):
    """Get last day which should be shown in the PDF."""
    return self.current_start_day + timedelta(days=self.number_of_days - 1)
    def get_context_data(self) -> Dict[str, Any]:
    def get_context_data(self) -> dict[str, Any]:
    """Get context data for generating the substitutions PDF."""
    from aleksis.apps.chronos.util.chronos_helpers import get_substitutions_context_data # noqa
    ......
    from __future__ import annotations
    from typing import Optional, Union
    from django.utils.html import format_html
    from django.utils.translation import gettext_lazy as _
    ......@@ -12,8 +10,8 @@ from .models import LessonPeriod, Supervision
    def _title_attr_from_lesson_or_supervision_state(
    record: Optional[Union[LessonPeriod, Supervision]] = None,
    table: Optional[Union[LessonsTable, SupervisionsTable]] = None,
    record: LessonPeriod | Supervision | None = None,
    table: LessonsTable | SupervisionsTable | None = None,
    ) -> str:
    """Return HTML title depending on lesson or supervision state."""
    if record.get_substitution():
    ......@@ -26,7 +24,7 @@ def _title_attr_from_lesson_or_supervision_state(
    class SubstitutionColumn(tables.Column):
    def render(self, value, record: Optional[Union[LessonPeriod, Supervision]] = None):
    def render(self, value, record: LessonPeriod | Supervision | None = None):
    if record.get_substitution():
    return (
    format_html(
    ......@@ -48,7 +46,7 @@ class SubstitutionColumn(tables.Column):
    class LessonStatusColumn(tables.Column):
    def render(self, record: Optional[Union[LessonPeriod, Supervision]] = None):
    def render(self, record: LessonPeriod | Supervision | None = None):
    if record.get_substitution():
    return (
    format_html(
    ......
    from collections import OrderedDict
    from datetime import date
    from typing import List, Tuple, Union
    from typing import Union
    from django.apps import apps
    ......@@ -113,10 +113,7 @@ def build_timetable(
    # Get events
    events = Event.objects
    if is_week:
    events = events.in_week(date_ref)
    else:
    events = events.on_day(date_ref)
    events = events.in_week(date_ref) if is_week else events.on_day(date_ref)
    events = events.only(
    "id",
    ......@@ -161,19 +158,13 @@ def build_timetable(
    # If daily timetable for person, skip other weekdays
    continue
    if weekday == weekday_from:
    # If start day, use start period
    period_from = period_from_first_weekday
    else:
    # If not start day, use min period
    period_from = TimePeriod.period_min
    # If start day, use start period else use min period
    period_from = (
    period_from_first_weekday if weekday == weekday_from else TimePeriod.period_min
    )
    if weekday == weekday_to:
    # If end day, use end period
    period_to = period_to_last_weekday
    else:
    # If not end day, use max period
    period_to = TimePeriod.period_max
    # If end day, use end period else use max period
    period_to = period_to_last_weekday if weekday == weekday_to else TimePeriod.periox_max
    for period in range(period_from, period_to + 1):
    # The following events are possibly replacing some lesson periods
    ......@@ -203,10 +194,7 @@ def build_timetable(
    if type_ == TimetableType.TEACHER:
    # Get matching supervisions
    if not is_week:
    week = CalendarWeek.from_date(date_ref)
    else:
    week = date_ref
    week = CalendarWeek.from_date(date_ref) if not is_week else date_ref
    supervisions = (
    Supervision.objects.in_week(week)
    .all()
    ......@@ -275,9 +263,8 @@ def build_timetable(
    if (
    period in supervisions_per_period_after
    and weekday not in holidays_per_weekday
    ):
    if weekday in supervisions_per_period_after[period]:
    col = supervisions_per_period_after[period][weekday]
    ) and weekday in supervisions_per_period_after[period]:
    col = supervisions_per_period_after[period][weekday]
    cols.append(col)
    row["cols"] = cols
    ......@@ -336,7 +323,7 @@ def build_timetable(
    )
    lesson_period.replaced_by_event = replaced_by_event
    if not replaced_by_event or (
    replaced_by_event and not type_ == TimetableType.GROUP
    replaced_by_event and type_ != TimetableType.GROUP
    ):
    col.append(lesson_period)
    ......@@ -393,7 +380,7 @@ def build_timetable(
    return rows
    def build_substitutions_list(wanted_day: date) -> List[dict]:
    def build_substitutions_list(wanted_day: date) -> list[dict]:
    rows = []
    subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
    ......@@ -466,10 +453,7 @@ def build_substitutions_list(wanted_day: date) -> List[dict]:
    events = Event.objects.on_day(wanted_day).annotate_day(wanted_day)
    for event in events:
    if event.groups.all():
    sort_a = event.group_names
    else:
    sort_a = f"Z.{event.teacher_names}"
    sort_a = event.group_names if event.groups.all() else f"Z.{event.teacher_names}"
    row = {
    "type": "event",
    ......@@ -489,8 +473,8 @@ def build_substitutions_list(wanted_day: date) -> List[dict]:
    def build_weekdays(
    base: List[Tuple[int, str]], wanted_week: CalendarWeek, with_holidays: bool = True
    ) -> List[dict]:
    base: list[tuple[int, str]], wanted_week: CalendarWeek, with_holidays: bool = True
    ) -> list[dict]:
    if with_holidays:
    holidays_per_weekday = Holiday.in_week(wanted_week)
    ......
    from typing import Any, Optional, Type
    from typing import Any, Optional
    from django.contrib.contenttypes.models import ContentType
    from django.db import transaction
    ......@@ -41,7 +41,7 @@ class TimetableDataChangeTracker:
    """Helper class for tracking changes in timetable models by using signals."""
    @classmethod
    def get_models(cls) -> list[Type[Model]]:
    def get_models(cls) -> list[type[Model]]:
    """Return all models that should be tracked."""
    from aleksis.apps.chronos.models import (
    Event,
    ......@@ -70,7 +70,7 @@ class TimetableDataChangeTracker:
    if f.many_to_many
    }
    self.m2m_fields.update(m2m_fields)
    for through_model, field in m2m_fields.items():
    for through_model, _field in m2m_fields.items():
    m2m_changed.connect(self._handle_m2m_changed, sender=through_model, weak=False)
    transaction.on_commit(self.close)
    ......@@ -87,24 +87,24 @@ class TimetableDataChangeTracker:
    else:
    self.changes[key].changed_fields.update(change.changed_fields)
    def _handle_save(self, sender: Type[Model], instance: Model, created: bool, **kwargs):
    def _handle_save(self, sender: type[Model], instance: Model, created: bool, **kwargs):
    """Handle the save signal."""
    change = TimetableChange(instance, created=created)
    if not created:
    change.changed_fields = instance.tracker.changed()
    self._add_change(change)
    def _handle_delete(self, sender: Type[Model], instance: Model, **kwargs):
    def _handle_delete(self, sender: type[Model], instance: Model, **kwargs):
    """Handle the delete signal."""
    change = TimetableChange(instance, deleted=True)
    self._add_change(change)
    def _handle_m2m_changed(
    self,
    sender: Type[Model],
    sender: type[Model],
    instance: Model,
    action: str,
    model: Type[Model],
    model: type[Model],
    pk_set: set,
    **kwargs,
    ):
    ......@@ -127,7 +127,7 @@ class TimetableDataChangeTracker:
    post_save.disconnect(self._handle_save, sender=model)
    pre_delete.disconnect(self._handle_delete, sender=model)
    for through_model, field in self.m2m_fields.items():
    for through_model, _field in self.m2m_fields.items():
    m2m_changed.disconnect(self._handle_m2m_changed, sender=through_model)
    timetable_data_changed.send(sender=self, changes=self.changes)
    ......
    ......@@ -190,7 +190,7 @@ def get_substitutions_context_data(
    if is_print:
    next_day = wanted_day
    for i in range(day_number):
    for _i in range(day_number):
    day_contexts[next_day] = {"day": next_day}
    next_day = TimePeriod.get_next_relevant_day(next_day + timedelta(days=1))
    else:
    ......
    from datetime import date
    from typing import List, Tuple
    from django.utils import timezone
    from calendarweek import CalendarWeek
    def week_weekday_from_date(when: date) -> Tuple[CalendarWeek, int]:
    def week_weekday_from_date(when: date) -> tuple[CalendarWeek, int]:
    """Return a tuple of week and weekday from a given date."""
    return (CalendarWeek.from_date(when), when.weekday())
    ......@@ -21,7 +20,7 @@ def week_period_to_date(week: CalendarWeek, period) -> date:
    return period.get_date(week)
    def get_weeks_for_year(year: int) -> List[CalendarWeek]:
    def get_weeks_for_year(year: int) -> list[CalendarWeek]:
    """Generate all weeks for one year."""
    weeks = []
    ......
    from datetime import date
    from typing import TYPE_CHECKING
    from django.utils.formats import date_format
    if TYPE_CHECKING:
    from ..models import TimePeriod
    def format_m2m(f, attr: str = "short_name") -> str:
    """Join a attribute of all elements of a ManyToManyField."""
    ......
    import zoneinfo
    from datetime import datetime, timedelta
    from typing import Union
    from urllib.parse import urljoin
    ......@@ -10,6 +9,8 @@ from django.utils.formats import date_format
    from django.utils.translation import gettext_lazy as _
    from django.utils.translation import ngettext
    import zoneinfo
    from aleksis.core.models import Notification, Person
    from aleksis.core.util.core_helpers import get_site_preferences
    ......@@ -17,7 +18,7 @@ from ..models import Event, ExtraLesson, LessonSubstitution, SupervisionSubstitu
    def send_notifications_for_object(
    instance: Union[ExtraLesson, LessonSubstitution, Event, SupervisionSubstitution]
    instance: Union[ExtraLesson, LessonSubstitution, Event, SupervisionSubstitution],
    ):
    """Send notifications for a change object."""
    recipients = []
    ......@@ -27,10 +28,7 @@ def send_notifications_for_object(
    recipients += Person.objects.filter(
    member_of__in=instance.lesson_period.lesson.groups.all()
    )
    elif isinstance(instance, Event):
    recipients += instance.teachers.all()
    recipients += Person.objects.filter(member_of__in=instance.groups.all())
    elif isinstance(instance, ExtraLesson):
    elif isinstance(instance, (Event, ExtraLesson)):
    recipients += instance.teachers.all()
    recipients += Person.objects.filter(member_of__in=instance.groups.all())
    elif isinstance(instance, SupervisionSubstitution):
    ......
    ......@@ -291,22 +291,21 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse
    context["substitution"] = lesson_substitution
    if request.method == "POST":
    if edit_substitution_form.is_valid():
    with reversion.create_revision(atomic=True):
    tracker = TimetableDataChangeTracker()
    if request.method == "POST" and edit_substitution_form.is_valid():
    with reversion.create_revision(atomic=True):
    TimetableDataChangeTracker()
    lesson_substitution = edit_substitution_form.save(commit=False)
    if not lesson_substitution.pk:
    lesson_substitution.lesson_period = lesson_period
    lesson_substitution.week = wanted_week.week
    lesson_substitution.year = wanted_week.year
    lesson_substitution.save()
    edit_substitution_form.save_m2m()
    lesson_substitution = edit_substitution_form.save(commit=False)
    if not lesson_substitution.pk:
    lesson_substitution.lesson_period = lesson_period
    lesson_substitution.week = wanted_week.week
    lesson_substitution.year = wanted_week.year
    lesson_substitution.save()
    edit_substitution_form.save_m2m()
    messages.success(request, _("The substitution has been saved."))
    messages.success(request, _("The substitution has been saved."))
    return redirect("lessons_day_by_date", year=day.year, month=day.month, day=day.day)
    return redirect("lessons_day_by_date", year=day.year, month=day.month, day=day.day)
    context["edit_substitution_form"] = edit_substitution_form
    ......@@ -432,23 +431,20 @@ def edit_supervision_substitution(request: HttpRequest, id_: int, week: int) ->
    context["substitution"] = supervision_substitution
    if request.method == "POST":
    if edit_supervision_substitution_form.is_valid():
    with reversion.create_revision(atomic=True):
    tracker = TimetableDataChangeTracker()
    if request.method == "POST" and edit_supervision_substitution_form.is_valid():
    with reversion.create_revision(atomic=True):
    TimetableDataChangeTracker()
    supervision_substitution = edit_supervision_substitution_form.save(commit=False)
    if not supervision_substitution.pk:
    supervision_substitution.supervision = supervision
    supervision_substitution.date = date
    supervision_substitution.save()
    edit_supervision_substitution_form.save_m2m()
    supervision_substitution = edit_supervision_substitution_form.save(commit=False)
    if not supervision_substitution.pk:
    supervision_substitution.supervision = supervision
    supervision_substitution.date = date
    supervision_substitution.save()
    edit_supervision_substitution_form.save_m2m()
    messages.success(request, _("The substitution has been saved."))
    messages.success(request, _("The substitution has been saved."))
    return redirect(
    "supervisions_day_by_date", year=date.year, month=date.month, day=date.day
    )
    return redirect("supervisions_day_by_date", year=date.year, month=date.month, day=date.day)
    context["edit_supervision_substitution_form"] = edit_supervision_substitution_form
    ......
    {
    "name": "aleksis-builddeps",
    "version": "1.0.0",
    "dependencies": {
    "@intlify/eslint-plugin-vue-i18n": "^2.0.0",
    "eslint": "^8.26.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-vue": "^9.7.0",
    "prettier": "^2.8.1",
    "stylelint": "^14.14.0",
    "stylelint-config-prettier": "^9.0.3",
    "stylelint-config-standard": "^29.0.0"
    }
    }
    ......@@ -4,11 +4,12 @@ skip_missing_interpreters = true
    envlist = py39,py310,py311
    [testenv]
    allowlist_externals = poetry
    allowlist_externals =
    poetry
    yarnpkg
    skip_install = true
    envdir = {toxworkdir}/globalenv
    commands_pre =
    poetry install
    poetry install --all-extras
    poetry run aleksis-admin vite build
    poetry run aleksis-admin collectstatic --no-input
    commands =
    ......@@ -22,14 +23,17 @@ setenv =
    TEST_HOST = {env:TEST_HOST:172.17.0.1}
    [testenv:lint]
    commands_pre =
    poetry install --only=dev
    yarnpkg --cwd=.dev-js
    commands =
    poetry run black --check --diff aleksis/
    poetry run isort -c --diff --stdout aleksis/
    poetry run flake8 {posargs} aleksis/
    poetry run sh -c "aleksis-admin yarn run prettier --check --ignore-path={toxinidir}/.prettierignore {toxinidir}"
    poetry run sh -c "aleksis-admin yarn run eslint {toxinidir}/aleksis/**/*/frontend/**/*.{js,vue} --config={toxinidir}/.eslintrc.js --resolve-plugins-relative-to=."
    poetry run ruff check {posargs} aleksis/
    yarnpkg --cwd=.dev-js run prettier --ignore-path={toxinidir}/.prettierignore {posargs} --check ..
    yarnpkg --cwd=.dev-js run eslint ../aleksis/**/*/frontend/**/*.{js,vue} --config={toxinidir}/.eslintrc.js --resolve-plugins-relative-to=.
    [testenv:security]
    commands_pre =
    poetry install --all-extras
    commands =
    poetry show --no-dev
    poetry run safety check --full-report
    ......@@ -41,33 +45,25 @@ commands_pre =
    commands = poetry build
    [testenv:docs]
    commands_pre =
    poetry install --with docs
    commands = poetry run make -C docs/ html {posargs}
    [testenv:reformat]
    commands_pre =
    poetry install --only=dev
    yarnpkg --cwd=.dev-js
    commands =
    poetry run isort aleksis/
    poetry run black aleksis/
    poetry run sh -c "aleksis-admin yarn run prettier --write --ignore-path={toxinidir}/.prettierignore {toxinidir}"
    poetry run ruff format aleksis/
    yarnpkg --cwd=.dev-js run prettier --ignore-path={toxinidir}/.prettierignore --write ..
    [testenv:makemessages]
    commands_pre =
    poetry install
    commands =
    poetry run aleksis-admin makemessages --no-wrap -e html,txt,py,email -i static -l ar -l de_DE -l fr -l nb_NO -l tr_TR -l la -l uk -l ru
    poetry run aleksis-admin makemessages --no-wrap -d djangojs -i **/node_modules -l ar -l de_DE -l fr -l nb_NO -l tr_TR -l la -l uk -l ru
    [flake8]
    max_line_length = 100
    exclude = migrations,tests
    ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05
    [isort]
    profile = black
    line_length = 100
    default_section = THIRDPARTY
    known_first_party = aleksis
    known_django = django
    skip = migrations
    sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
    [pytest]
    DJANGO_SETTINGS_MODULE = aleksis.core.settings
    junit_family = legacy
    ......
    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