Skip to content
Snippets Groups Projects
  • magicfelix's avatar
    c9a56bb2
    Adapt substitution PDF to new data model · c9a56bb2
    magicfelix authored and Jonathan Weth's avatar Jonathan Weth committed
    diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
    index 35f4e01..f348c00 100644
    --- a/aleksis/apps/chronos/models.py
    +++ b/aleksis/apps/chronos/models.py
    @@ -1221,6 +1221,7 @@ class AutomaticPlan(LiveDocument):
             from aleksis.apps.chronos.util.chronos_helpers import get_substitutions_context_data  # noqa
    
             context = get_substitutions_context_data(
    +            wanted_day=date.today(),
                 request=None,
                 is_print=True,
                 number_of_days=self.number_of_days,
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/rooms.html b/aleksis/apps/chronos/templates/chronos/partials/rooms.html
    new file mode 100644
    index 0000000..1e42988
    --- /dev/null
    +++ b/aleksis/apps/chronos/templates/chronos/partials/rooms.html
    @@ -0,0 +1,8 @@
    +{% for room in rooms %}
    +  <span data-position="bottom" class="tooltipped"
    +        data-tooltip="{{ room }}">
    +      <a href="{% url "timetable" "room" room.pk %}">
    +          {{ room.short_name }}{% if not forloop.last %},{% endif %}
    +      </a>
    +  </span>
    +{% endfor %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    index c04c9dd..c807244 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    @@ -2,6 +2,4 @@
    
     {% if sub.cancelled %}
       <span class="badge new green">{% trans "Cancelled" %}</span>
    -{% elif item.el.cancelled_for_teachers %}
    -  <span class="badge new green">{% trans "Cancelled for teachers" %}</span>
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html b/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    index 833a24b..e59b37f 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    @@ -1,11 +1,5 @@
    -{% if item.type == "substitution" %}
    -  {% if item.el.cancelled or item.el.cancelled_for_teachers %}
    -    green-text
    -  {% else %}
    -    black-text
    -  {% endif %}
    -{% elif item.type == "supervision_substitution" %}
    -  blue-text
    -{% elif item.type == "event" %}
    -  purple-text
    +{% if item.el.cancelled %}
    +  green-text
    +{% else %}
    +  black-text
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html b/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    index d1a4da9..079e0f0 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    @@ -1,5 +1,15 @@
    -{% if type == "substitution" %}
    -  {% include "chronos/partials/groups.html" with groups=el.lesson_period.lesson.groups.all %}
    -{% elif type == "extra_lesson" or type == "event" %}
    +{% if el.cancelled and el.amends.groups.all %}
    +  {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
    +{% elif el.groups.all and el.amends.groups.all %}
    +  <s>
    +    {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/groups.html" with groups=el.groups.all %}
    +  </strong>
    +{% elif el.groups.all and not el.amends.groups.all %}
       {% include "chronos/partials/groups.html" with groups=el.groups.all %}
    +{% elif el.amends.groups.all %}
    +  {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/period.html b/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    index ef1d283..4ba7706 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    @@ -1,19 +1,7 @@
     <strong>
    -  {% if type == "substitution" and item.start_period == item.end_period %}
    -    {{ el.lesson_period.period.period }}.
    -  {% elif type == "substitution"  %}
    -    {{ item.start_period }}.–{{ item.end_period }}.
    -  {% elif type == "extra_lesson" %}
    -    {{ el.period.period }}.
    -  {% elif type == "event" %}
    -    {% if el.period_from_on_day == el.period_to_on_day %}
    -      {{ el.period_from_on_day }}.
    -    {% else %}
    -      {{ el.period_from_on_day }}.–{{ el.period_to_on_day }}.
    -    {% endif %}
    -  {% elif type == "supervision_substitution" %}
    -    {% with break=el.supervision.break_item %}
    -      {{ break.after_period_number }}./{{ break.before_period_number }}.
    -    {% endwith %}
    +  {% if el.datetime_start %}
    +    {{ el.datetime_start.time }} - {{ el.datetime_end.time }}
    +  {% elif el.date_start %}
    +    {{ el.date_start }} - {{ el.date_end }}
       {% endif %}
     </strong>
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    index 94f2d35..123faba 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    @@ -1,39 +1,15 @@
    -{% if type == "substitution" %}
    -  {% if el.cancelled or el.cancelled_for_teachers %}
    -    {# Cancelled lesson: no room #}
    -  {% elif el.room and el.lesson_period.room %}
    -    {# New and old room available #}
    -    <span class="tooltipped" data-position="bottom"
    -          data-tooltip="{{ el.lesson_period.room.name }} → {{ el.room.name }}"
    -          title="{{ el.lesson_period.room.name }} → {{ el.room.name }}">
    -      <a href="{% url "timetable" "room" el.lesson_period.room.pk %}">
    -          <s>{{ el.lesson_period.room.short_name }}</s>
    -      </a>
    -      →
    -      <a href="{% url "timetable" "room" el.room.pk %}">
    -          <strong>{{ el.room.short_name }}</strong>
    -      </a>
    -    </span>
    -  {% elif el.room and not el.lesson_period.room %}
    -    {# Only new room available #}
    -    {% include "chronos/partials/room.html" with room=el.room %}
    -  {% elif not el.room and not el.lesson_period.room %}
    -    {# Nothing to view #}
    -  {% else %}
    -    {# Only old room available #}
    -    {% include "chronos/partials/room.html" with room=el.lesson_period.room %}
    -  {% endif %}
    -{% elif type == "supervision_substitution" %}
    -  {% with supervision=el.supervision %}
    -    <span data-position="bottom" class="tooltipped"
    -          data-tooltip="{{ supervision.area.name }}" title="{{ supervision.area.name }}">
    -      {{ supervision.area.short_name }}
    -    </span>
    -  {% endwith %}
    -{% elif type == "extra_lesson" %}
    -  {% include "chronos/partials/room.html" with room=el.room %}
    -{% elif type == "event" %}
    -  {% for room in el.rooms.all %}
    -    {% include "chronos/partials/room.html" with room=room %}{% if not forloop.last %},{% endif %}
    -  {% endfor %}
    +{% if el.cancelled and el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% elif el.rooms.all and el.amends.rooms.all %}
    +  <s>
    +    {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +  </strong>
    +{% elif el.rooms.all and not el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +{% elif el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html b/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html
    new file mode 100644
    index 0000000..123faba
    --- /dev/null
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html
    @@ -0,0 +1,15 @@
    +{% if el.cancelled and el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% elif el.rooms.all and el.amends.rooms.all %}
    +  <s>
    +    {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +  </strong>
    +{% elif el.rooms.all and not el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +{% elif el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    index 1b7a3c5..dcbfa4b 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    @@ -1,28 +1,28 @@
     {% load i18n %}
    
    -{% if type == "substitution" %}
    -  {% if not el.lesson_period.lesson.subject and not el.subject %}
    -  {% elif el.cancelled or el.cancelled_for_teachers %}
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
    -    <s>{{ el.lesson_period.lesson.subject.short_name }}</s>
    -  </span>
    -  {% elif el.subject and el.lesson_period.lesson.subject %}
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
    -    <s>{{ el.lesson_period.lesson.subject.short_name }}</s>
    -  </span>
    -    →
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.name }}">
    -    <strong>{{ el.subject.short_name }}</strong>
    -  </span>
    -  {% elif el.subject and not el.lesson_period.lesson.subject %}
    -    {% include "chronos/partials/subject.html" with subject=el.subject %}
    -  {% else %}
    -    {% include "chronos/partials/subject.html" with subject=el.lesson_period.lesson.subject %}
    +{% if not el.amends.subject and not el.subject %}
    +  {% if el.amends.title %}
    +    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.amends.title }}">
    +    <s>{{ el.amends.title }}</s>
       {% endif %}
    -{% elif type == "supervision_substitution" %}
    -  {% trans "Supervision" %}
    -{% elif type == "extra_lesson" %}
    +  {% if el.title %}
    +    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.title }}">
    +    <s>{{ el.title }}</s>
    +  {% endif %}
    +{% elif el.cancelled %}
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.short_name }}">
    +  <s>{{ el.subject.short_name }}</s>
    +</span>
    +{% elif el.subject and el.amends.subject %}
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.amends.subject.name }}">
    +  <s>{{ el.amends.subject.short_name }}</s>
    +</span>
    +  →
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.name }}">
    +  <strong>{{ el.subject.short_name }}</strong>
    +</span>
    +{% elif el.subject and not el.amends.subject %}
       {% include "chronos/partials/subject.html" with subject=el.subject %}
    -{% elif type == "event" %}
    -  {% trans "Event" %}
    +{% else %}
    +  {% include "chronos/partials/subject.html" with subject=el.amends.subject %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    index 4fa80d8..9e08eeb 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    @@ -1,27 +1,15 @@
    -{% if type == "substitution" %}
    -  {% if el.cancelled and el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -  {% elif el.teachers.all and el.lesson_period.lesson.teachers.all %}
    -    <s>
    -      {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -    </s>
    -    →
    -    <strong>
    -      {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    -    </strong>
    -  {% elif el.teachers.all and not el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    -  {% elif el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -  {% endif %}
    -{% elif type == "supervision_substitution" %}
    +{% if el.cancelled and el.amends.teachers.all %}
    +  {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
    +{% elif el.teachers.all and el.amends.teachers.all %}
       <s>
    -    {% include "chronos/partials/teachers.html" with teachers=el.supervision.teachers %}
    +    {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
       </s>
       →
       <strong>
    -    {% include "chronos/partials/teachers.html" with teachers=el.teachers %}
    +    {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
       </strong>
    -{% elif type == "extra_lesson" or type == "event" %}
    +{% elif el.teachers.all and not el.amends.teachers.all %}
       {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    +{% elif el.amends.teachers.all %}
    +  {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/substitutions_print.html b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    index 743a223..10243a1 100644
    --- a/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    +++ b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    @@ -18,13 +18,13 @@
    
         {% include "core/partials/announcements.html" with announcements=c.announcements show_recipients=1 %}
    
    -    {% include "chronos/partials/headerbox.html" with affected_teachers=c.affected_teachers affected_groups=c.affected_groups absent_teachers=c.absent_teachers absent_groups=c.absent_groups print=1 %}
    +    {% include "chronos/partials/headerbox.html" with absent_teachers=c.absent_teachers absent_groups=c.absent_groups print=1 %}
    
         <table class="substitutions">
           <thead>
           <tr>
    -        <th><i class="material-icons iconify center" data-icon="mdi:account-multiple-outline"></i></th>
    -        <th><i class="material-icons iconify center" data-icon="mdi:clock-outline"></i></th>
    +        <th>{% blocktrans %}Groups{% endblocktrans %}</th>
    +        <th>{% blocktrans %}Time{% endblocktrans %}</th>
             <th>{% blocktrans %}Teachers{% endblocktrans %}</th>
             <th>{% blocktrans %}Subject{% endblocktrans %}</th>
             <th>{% blocktrans %}Room{% endblocktrans %}</th>
    @@ -47,7 +47,7 @@
    
           <tbody>
           {% for item in c.substitutions %}
    -        {% ifchanged item.el.lesson_period.lesson.groups_to_show_names %}
    +        {% ifchanged item.el.group_names %}
               </tbody>
               <tbody class="{% cycle "striped" "not-striped" %}">
             {% endifchanged %}
    @@ -66,7 +66,7 @@
                 {% include "chronos/partials/subs/subject.html" with type=item.type el=item.el %}
               </td>
               <td>
    -            {% include "chronos/partials/subs/room.html" with type=item.type el=item.el %}
    +            {% include "chronos/partials/subs/rooms.html" with type=item.type el=item.el %}
               </td>
               <td>
                 {% include "chronos/partials/subs/badge.html" with sub=item.el %}
    diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
    index 625998d..7946a3e 100644
    --- a/aleksis/apps/chronos/util/build.py
    +++ b/aleksis/apps/chronos/util/build.py
    @@ -3,6 +3,7 @@ from datetime import date
     from typing import Union
    
     from django.apps import apps
    +from django.db.models import Q
    
     from calendarweek import CalendarWeek
    
    @@ -10,6 +11,7 @@ from aleksis.apps.chronos.managers import TimetableType
     from aleksis.core.models import Group, Person, Room
    
     LessonPeriod = apps.get_model("chronos", "LessonPeriod")
    +LessonEvent = apps.get_model("chronos", "LessonEvent")
     TimePeriod = apps.get_model("chronos", "TimePeriod")
     Break = apps.get_model("chronos", "Break")
     Supervision = apps.get_model("chronos", "Supervision")
    @@ -383,84 +385,25 @@ def build_timetable(
     def build_substitutions_list(wanted_day: date) -> list[dict]:
         rows = []
    
    -    subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
    -        "lesson_period__lesson__groups", "lesson_period__period"
    +    subs = (
    +        LessonEvent.objects.exclude(amends=None)
    +        .filter(Q(datetime_start__date=wanted_day) | Q(date_start=wanted_day))
    +        .order_by("datetime_start", "date_start")
         )
    
    -    start_period = None
         for i, sub in enumerate(subs):
    -        if not sub.cancelled_for_teachers:
    -            sort_a = sub.lesson_period.lesson.groups_to_show_names
    -        else:
    -            sort_a = f"Z.{sub.lesson_period.lesson.teacher_names}"
    -
    -        # Get next substitution
    -        next_sub = subs[i + 1] if i + 1 < len(subs) else None
    -
    -        # Check if next substitution is equal with this substitution
    -        if (
    -            next_sub
    -            and sub.comment == next_sub.comment
    -            and sub.cancelled == next_sub.cancelled
    -            and sub.subject == next_sub.subject
    -            and sub.room == next_sub.room
    -            and sub.lesson_period.lesson == next_sub.lesson_period.lesson
    -            and set(sub.teachers.all()) == set(next_sub.teachers.all())
    -        ):
    -            if not start_period:
    -                start_period = sub.lesson_period.period.period
    -            continue
    +        sort_a = sub.group_names
    +
    +        # FIXME? Looks hacky. sub.amends returns a CalendarEvent, but a LessonEvent is needed
    +        sub.amends = LessonEvent.objects.get(pk=sub.amends.pk)
    
             row = {
                 "type": "substitution",
                 "sort_a": sort_a,
    -            "sort_b": str(sub.lesson_period.period.period),
    +            "sort_b": str(sub.datetime_start if sub.datetime_start else sub.date_start),
                 "el": sub,
    -            "start_period": start_period if start_period else sub.lesson_period.period.period,
    -            "end_period": sub.lesson_period.period.period,
    -        }
    -
    -        if start_period:
    -            start_period = None
    -
    -        rows.append(row)
    -
    -    # Get supervision substitutions
    -    super_subs = SupervisionSubstitution.objects.filter(date=wanted_day)
    -
    -    for super_sub in super_subs:
    -        row = {
    -            "type": "supervision_substitution",
    -            "sort_a": f"Z.{super_sub.teacher}",
    -            "sort_b": str(super_sub.supervision.break_item.after_period_number),
    -            "el": super_sub,
    -        }
    -        rows.append(row)
    -
    -    # Get extra lessons
    -    extra_lessons = ExtraLesson.objects.on_day(wanted_day)
    -
    -    for extra_lesson in extra_lessons:
    -        row = {
    -            "type": "extra_lesson",
    -            "sort_a": str(extra_lesson.group_names),
    -            "sort_b": str(extra_lesson.period.period),
    -            "el": extra_lesson,
             }
    -        rows.append(row)
    -
    -    # Get events
    -    events = Event.objects.on_day(wanted_day).annotate_day(wanted_day)
    
    -    for event in events:
    -        sort_a = event.group_names if event.groups.all() else f"Z.{event.teacher_names}"
    -
    -        row = {
    -            "type": "event",
    -            "sort_a": sort_a,
    -            "sort_b": str(event.period_from_on_day),
    -            "el": event,
    -        }
             rows.append(row)
    
         # Sort all items
    diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
    index 8e16401..4c36d3a 100644
    --- a/aleksis/apps/chronos/util/chronos_helpers.py
    +++ b/aleksis/apps/chronos/util/chronos_helpers.py
    @@ -1,4 +1,4 @@
    -from datetime import datetime, timedelta
    +from datetime import date, datetime, timedelta
     from typing import TYPE_CHECKING, Optional
    
     from django.db.models import Count, Q
    @@ -160,10 +160,8 @@ def get_rooms(user: "User"):
    
     def get_substitutions_context_data(
    +    wanted_day: date,
         request: Optional[HttpRequest] = None,
    -    year: Optional[int] = None,
    -    month: Optional[int] = None,
    -    day: Optional[int] = None,
         is_print: bool = False,
         number_of_days: Optional[int] = None,
         show_header_box: Optional[bool] = None,
    @@ -171,12 +169,6 @@ def get_substitutions_context_data(
         """Get context data for the substitutions table."""
         context = {}
    
    -    if day:
    -        wanted_day = timezone.datetime(year=year, month=month, day=day).date()
    -        wanted_day = TimePeriod.get_next_relevant_day(wanted_day)
    -    else:
    -        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), timezone.now().time())
    -
         day_number = (
             number_of_days or get_site_preferences()["chronos__substitutions_print_number_of_days"]
         )
    @@ -191,7 +183,7 @@ def get_substitutions_context_data(
             next_day = wanted_day
             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))
    +            next_day = next_day + timedelta(days=1)
         else:
             day_contexts = {wanted_day: {"day": wanted_day}}
    
    @@ -202,22 +194,9 @@ def get_substitutions_context_data(
             day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day)
    
             if show_header_box:
    -            subs = LessonSubstitution.objects.on_day(day).order_by(
    -                "lesson_period__lesson__groups", "lesson_period__period"
    -            )
                 absences = Absence.objects.on_day(day)
                 day_contexts[day]["absent_teachers"] = absences.absent_teachers()
                 day_contexts[day]["absent_groups"] = absences.absent_groups()
    -            day_contexts[day]["affected_teachers"] = subs.affected_teachers()
    -            affected_groups = subs.affected_groups()
    -            if get_site_preferences()["chronos__affected_groups_parent_groups"]:
    -                groups_with_parent_groups = affected_groups.filter(parent_groups__isnull=False)
    -                groups_without_parent_groups = affected_groups.filter(parent_groups__isnull=True)
    -                affected_groups = Group.objects.filter(
    -                    Q(child_groups__pk__in=groups_with_parent_groups.values_list("pk", flat=True))
    -                    | Q(pk__in=groups_without_parent_groups.values_list("pk", flat=True))
    -                ).distinct()
    -            day_contexts[day]["affected_groups"] = affected_groups
    
         if not is_print:
             context = day_contexts[wanted_day]
    @@ -226,10 +205,6 @@ def get_substitutions_context_data(
                 "dest": reverse("substitutions"),
             }
    
    -        context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
    -            wanted_day, "substitutions_by_date"
    -        )
    -
         else:
             context["days"] = day_contexts
    
    diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py
    index a45997c..ee01e35 100644
    --- a/aleksis/apps/chronos/views.py
    +++ b/aleksis/apps/chronos/views.py
    @@ -1,3 +1,4 @@
    +from datetime import date, datetime
     from typing import Optional
    
     from django.http import HttpRequest, HttpResponse
    Verified
    c9a56bb2
    History
    Adapt substitution PDF to new data model
    magicfelix authored and Jonathan Weth's avatar Jonathan Weth committed
    diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
    index 35f4e01..f348c00 100644
    --- a/aleksis/apps/chronos/models.py
    +++ b/aleksis/apps/chronos/models.py
    @@ -1221,6 +1221,7 @@ class AutomaticPlan(LiveDocument):
             from aleksis.apps.chronos.util.chronos_helpers import get_substitutions_context_data  # noqa
    
             context = get_substitutions_context_data(
    +            wanted_day=date.today(),
                 request=None,
                 is_print=True,
                 number_of_days=self.number_of_days,
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/rooms.html b/aleksis/apps/chronos/templates/chronos/partials/rooms.html
    new file mode 100644
    index 0000000..1e42988
    --- /dev/null
    +++ b/aleksis/apps/chronos/templates/chronos/partials/rooms.html
    @@ -0,0 +1,8 @@
    +{% for room in rooms %}
    +  <span data-position="bottom" class="tooltipped"
    +        data-tooltip="{{ room }}">
    +      <a href="{% url "timetable" "room" room.pk %}">
    +          {{ room.short_name }}{% if not forloop.last %},{% endif %}
    +      </a>
    +  </span>
    +{% endfor %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    index c04c9dd..c807244 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
    @@ -2,6 +2,4 @@
    
     {% if sub.cancelled %}
       <span class="badge new green">{% trans "Cancelled" %}</span>
    -{% elif item.el.cancelled_for_teachers %}
    -  <span class="badge new green">{% trans "Cancelled for teachers" %}</span>
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html b/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    index 833a24b..e59b37f 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/colour.html
    @@ -1,11 +1,5 @@
    -{% if item.type == "substitution" %}
    -  {% if item.el.cancelled or item.el.cancelled_for_teachers %}
    -    green-text
    -  {% else %}
    -    black-text
    -  {% endif %}
    -{% elif item.type == "supervision_substitution" %}
    -  blue-text
    -{% elif item.type == "event" %}
    -  purple-text
    +{% if item.el.cancelled %}
    +  green-text
    +{% else %}
    +  black-text
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html b/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    index d1a4da9..079e0f0 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
    @@ -1,5 +1,15 @@
    -{% if type == "substitution" %}
    -  {% include "chronos/partials/groups.html" with groups=el.lesson_period.lesson.groups.all %}
    -{% elif type == "extra_lesson" or type == "event" %}
    +{% if el.cancelled and el.amends.groups.all %}
    +  {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
    +{% elif el.groups.all and el.amends.groups.all %}
    +  <s>
    +    {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/groups.html" with groups=el.groups.all %}
    +  </strong>
    +{% elif el.groups.all and not el.amends.groups.all %}
       {% include "chronos/partials/groups.html" with groups=el.groups.all %}
    +{% elif el.amends.groups.all %}
    +  {% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/period.html b/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    index ef1d283..4ba7706 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
    @@ -1,19 +1,7 @@
     <strong>
    -  {% if type == "substitution" and item.start_period == item.end_period %}
    -    {{ el.lesson_period.period.period }}.
    -  {% elif type == "substitution"  %}
    -    {{ item.start_period }}.–{{ item.end_period }}.
    -  {% elif type == "extra_lesson" %}
    -    {{ el.period.period }}.
    -  {% elif type == "event" %}
    -    {% if el.period_from_on_day == el.period_to_on_day %}
    -      {{ el.period_from_on_day }}.
    -    {% else %}
    -      {{ el.period_from_on_day }}.–{{ el.period_to_on_day }}.
    -    {% endif %}
    -  {% elif type == "supervision_substitution" %}
    -    {% with break=el.supervision.break_item %}
    -      {{ break.after_period_number }}./{{ break.before_period_number }}.
    -    {% endwith %}
    +  {% if el.datetime_start %}
    +    {{ el.datetime_start.time }} - {{ el.datetime_end.time }}
    +  {% elif el.date_start %}
    +    {{ el.date_start }} - {{ el.date_end }}
       {% endif %}
     </strong>
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    index 94f2d35..123faba 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
    @@ -1,39 +1,15 @@
    -{% if type == "substitution" %}
    -  {% if el.cancelled or el.cancelled_for_teachers %}
    -    {# Cancelled lesson: no room #}
    -  {% elif el.room and el.lesson_period.room %}
    -    {# New and old room available #}
    -    <span class="tooltipped" data-position="bottom"
    -          data-tooltip="{{ el.lesson_period.room.name }} → {{ el.room.name }}"
    -          title="{{ el.lesson_period.room.name }} → {{ el.room.name }}">
    -      <a href="{% url "timetable" "room" el.lesson_period.room.pk %}">
    -          <s>{{ el.lesson_period.room.short_name }}</s>
    -      </a>
    -      →
    -      <a href="{% url "timetable" "room" el.room.pk %}">
    -          <strong>{{ el.room.short_name }}</strong>
    -      </a>
    -    </span>
    -  {% elif el.room and not el.lesson_period.room %}
    -    {# Only new room available #}
    -    {% include "chronos/partials/room.html" with room=el.room %}
    -  {% elif not el.room and not el.lesson_period.room %}
    -    {# Nothing to view #}
    -  {% else %}
    -    {# Only old room available #}
    -    {% include "chronos/partials/room.html" with room=el.lesson_period.room %}
    -  {% endif %}
    -{% elif type == "supervision_substitution" %}
    -  {% with supervision=el.supervision %}
    -    <span data-position="bottom" class="tooltipped"
    -          data-tooltip="{{ supervision.area.name }}" title="{{ supervision.area.name }}">
    -      {{ supervision.area.short_name }}
    -    </span>
    -  {% endwith %}
    -{% elif type == "extra_lesson" %}
    -  {% include "chronos/partials/room.html" with room=el.room %}
    -{% elif type == "event" %}
    -  {% for room in el.rooms.all %}
    -    {% include "chronos/partials/room.html" with room=room %}{% if not forloop.last %},{% endif %}
    -  {% endfor %}
    +{% if el.cancelled and el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% elif el.rooms.all and el.amends.rooms.all %}
    +  <s>
    +    {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +  </strong>
    +{% elif el.rooms.all and not el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +{% elif el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html b/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html
    new file mode 100644
    index 0000000..123faba
    --- /dev/null
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html
    @@ -0,0 +1,15 @@
    +{% if el.cancelled and el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% elif el.rooms.all and el.amends.rooms.all %}
    +  <s>
    +    {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +  </s>
    +  →
    +  <strong>
    +    {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +  </strong>
    +{% elif el.rooms.all and not el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
    +{% elif el.amends.rooms.all %}
    +  {% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
    +{% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    index 1b7a3c5..dcbfa4b 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
    @@ -1,28 +1,28 @@
     {% load i18n %}
    
    -{% if type == "substitution" %}
    -  {% if not el.lesson_period.lesson.subject and not el.subject %}
    -  {% elif el.cancelled or el.cancelled_for_teachers %}
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
    -    <s>{{ el.lesson_period.lesson.subject.short_name }}</s>
    -  </span>
    -  {% elif el.subject and el.lesson_period.lesson.subject %}
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
    -    <s>{{ el.lesson_period.lesson.subject.short_name }}</s>
    -  </span>
    -    →
    -    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.name }}">
    -    <strong>{{ el.subject.short_name }}</strong>
    -  </span>
    -  {% elif el.subject and not el.lesson_period.lesson.subject %}
    -    {% include "chronos/partials/subject.html" with subject=el.subject %}
    -  {% else %}
    -    {% include "chronos/partials/subject.html" with subject=el.lesson_period.lesson.subject %}
    +{% if not el.amends.subject and not el.subject %}
    +  {% if el.amends.title %}
    +    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.amends.title }}">
    +    <s>{{ el.amends.title }}</s>
       {% endif %}
    -{% elif type == "supervision_substitution" %}
    -  {% trans "Supervision" %}
    -{% elif type == "extra_lesson" %}
    +  {% if el.title %}
    +    <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.title }}">
    +    <s>{{ el.title }}</s>
    +  {% endif %}
    +{% elif el.cancelled %}
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.short_name }}">
    +  <s>{{ el.subject.short_name }}</s>
    +</span>
    +{% elif el.subject and el.amends.subject %}
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.amends.subject.name }}">
    +  <s>{{ el.amends.subject.short_name }}</s>
    +</span>
    +  →
    +  <span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.name }}">
    +  <strong>{{ el.subject.short_name }}</strong>
    +</span>
    +{% elif el.subject and not el.amends.subject %}
       {% include "chronos/partials/subject.html" with subject=el.subject %}
    -{% elif type == "event" %}
    -  {% trans "Event" %}
    +{% else %}
    +  {% include "chronos/partials/subject.html" with subject=el.amends.subject %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    index 4fa80d8..9e08eeb 100644
    --- a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    +++ b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
    @@ -1,27 +1,15 @@
    -{% if type == "substitution" %}
    -  {% if el.cancelled and el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -  {% elif el.teachers.all and el.lesson_period.lesson.teachers.all %}
    -    <s>
    -      {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -    </s>
    -    →
    -    <strong>
    -      {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    -    </strong>
    -  {% elif el.teachers.all and not el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    -  {% elif el.lesson_period.lesson.teachers.all %}
    -    {% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
    -  {% endif %}
    -{% elif type == "supervision_substitution" %}
    +{% if el.cancelled and el.amends.teachers.all %}
    +  {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
    +{% elif el.teachers.all and el.amends.teachers.all %}
       <s>
    -    {% include "chronos/partials/teachers.html" with teachers=el.supervision.teachers %}
    +    {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
       </s>
       →
       <strong>
    -    {% include "chronos/partials/teachers.html" with teachers=el.teachers %}
    +    {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
       </strong>
    -{% elif type == "extra_lesson" or type == "event" %}
    +{% elif el.teachers.all and not el.amends.teachers.all %}
       {% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
    +{% elif el.amends.teachers.all %}
    +  {% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
     {% endif %}
    diff --git a/aleksis/apps/chronos/templates/chronos/substitutions_print.html b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    index 743a223..10243a1 100644
    --- a/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    +++ b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
    @@ -18,13 +18,13 @@
    
         {% include "core/partials/announcements.html" with announcements=c.announcements show_recipients=1 %}
    
    -    {% include "chronos/partials/headerbox.html" with affected_teachers=c.affected_teachers affected_groups=c.affected_groups absent_teachers=c.absent_teachers absent_groups=c.absent_groups print=1 %}
    +    {% include "chronos/partials/headerbox.html" with absent_teachers=c.absent_teachers absent_groups=c.absent_groups print=1 %}
    
         <table class="substitutions">
           <thead>
           <tr>
    -        <th><i class="material-icons iconify center" data-icon="mdi:account-multiple-outline"></i></th>
    -        <th><i class="material-icons iconify center" data-icon="mdi:clock-outline"></i></th>
    +        <th>{% blocktrans %}Groups{% endblocktrans %}</th>
    +        <th>{% blocktrans %}Time{% endblocktrans %}</th>
             <th>{% blocktrans %}Teachers{% endblocktrans %}</th>
             <th>{% blocktrans %}Subject{% endblocktrans %}</th>
             <th>{% blocktrans %}Room{% endblocktrans %}</th>
    @@ -47,7 +47,7 @@
    
           <tbody>
           {% for item in c.substitutions %}
    -        {% ifchanged item.el.lesson_period.lesson.groups_to_show_names %}
    +        {% ifchanged item.el.group_names %}
               </tbody>
               <tbody class="{% cycle "striped" "not-striped" %}">
             {% endifchanged %}
    @@ -66,7 +66,7 @@
                 {% include "chronos/partials/subs/subject.html" with type=item.type el=item.el %}
               </td>
               <td>
    -            {% include "chronos/partials/subs/room.html" with type=item.type el=item.el %}
    +            {% include "chronos/partials/subs/rooms.html" with type=item.type el=item.el %}
               </td>
               <td>
                 {% include "chronos/partials/subs/badge.html" with sub=item.el %}
    diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
    index 625998d..7946a3e 100644
    --- a/aleksis/apps/chronos/util/build.py
    +++ b/aleksis/apps/chronos/util/build.py
    @@ -3,6 +3,7 @@ from datetime import date
     from typing import Union
    
     from django.apps import apps
    +from django.db.models import Q
    
     from calendarweek import CalendarWeek
    
    @@ -10,6 +11,7 @@ from aleksis.apps.chronos.managers import TimetableType
     from aleksis.core.models import Group, Person, Room
    
     LessonPeriod = apps.get_model("chronos", "LessonPeriod")
    +LessonEvent = apps.get_model("chronos", "LessonEvent")
     TimePeriod = apps.get_model("chronos", "TimePeriod")
     Break = apps.get_model("chronos", "Break")
     Supervision = apps.get_model("chronos", "Supervision")
    @@ -383,84 +385,25 @@ def build_timetable(
     def build_substitutions_list(wanted_day: date) -> list[dict]:
         rows = []
    
    -    subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
    -        "lesson_period__lesson__groups", "lesson_period__period"
    +    subs = (
    +        LessonEvent.objects.exclude(amends=None)
    +        .filter(Q(datetime_start__date=wanted_day) | Q(date_start=wanted_day))
    +        .order_by("datetime_start", "date_start")
         )
    
    -    start_period = None
         for i, sub in enumerate(subs):
    -        if not sub.cancelled_for_teachers:
    -            sort_a = sub.lesson_period.lesson.groups_to_show_names
    -        else:
    -            sort_a = f"Z.{sub.lesson_period.lesson.teacher_names}"
    -
    -        # Get next substitution
    -        next_sub = subs[i + 1] if i + 1 < len(subs) else None
    -
    -        # Check if next substitution is equal with this substitution
    -        if (
    -            next_sub
    -            and sub.comment == next_sub.comment
    -            and sub.cancelled == next_sub.cancelled
    -            and sub.subject == next_sub.subject
    -            and sub.room == next_sub.room
    -            and sub.lesson_period.lesson == next_sub.lesson_period.lesson
    -            and set(sub.teachers.all()) == set(next_sub.teachers.all())
    -        ):
    -            if not start_period:
    -                start_period = sub.lesson_period.period.period
    -            continue
    +        sort_a = sub.group_names
    +
    +        # FIXME? Looks hacky. sub.amends returns a CalendarEvent, but a LessonEvent is needed
    +        sub.amends = LessonEvent.objects.get(pk=sub.amends.pk)
    
             row = {
                 "type": "substitution",
                 "sort_a": sort_a,
    -            "sort_b": str(sub.lesson_period.period.period),
    +            "sort_b": str(sub.datetime_start if sub.datetime_start else sub.date_start),
                 "el": sub,
    -            "start_period": start_period if start_period else sub.lesson_period.period.period,
    -            "end_period": sub.lesson_period.period.period,
    -        }
    -
    -        if start_period:
    -            start_period = None
    -
    -        rows.append(row)
    -
    -    # Get supervision substitutions
    -    super_subs = SupervisionSubstitution.objects.filter(date=wanted_day)
    -
    -    for super_sub in super_subs:
    -        row = {
    -            "type": "supervision_substitution",
    -            "sort_a": f"Z.{super_sub.teacher}",
    -            "sort_b": str(super_sub.supervision.break_item.after_period_number),
    -            "el": super_sub,
    -        }
    -        rows.append(row)
    -
    -    # Get extra lessons
    -    extra_lessons = ExtraLesson.objects.on_day(wanted_day)
    -
    -    for extra_lesson in extra_lessons:
    -        row = {
    -            "type": "extra_lesson",
    -            "sort_a": str(extra_lesson.group_names),
    -            "sort_b": str(extra_lesson.period.period),
    -            "el": extra_lesson,
             }
    -        rows.append(row)
    -
    -    # Get events
    -    events = Event.objects.on_day(wanted_day).annotate_day(wanted_day)
    
    -    for event in events:
    -        sort_a = event.group_names if event.groups.all() else f"Z.{event.teacher_names}"
    -
    -        row = {
    -            "type": "event",
    -            "sort_a": sort_a,
    -            "sort_b": str(event.period_from_on_day),
    -            "el": event,
    -        }
             rows.append(row)
    
         # Sort all items
    diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
    index 8e16401..4c36d3a 100644
    --- a/aleksis/apps/chronos/util/chronos_helpers.py
    +++ b/aleksis/apps/chronos/util/chronos_helpers.py
    @@ -1,4 +1,4 @@
    -from datetime import datetime, timedelta
    +from datetime import date, datetime, timedelta
     from typing import TYPE_CHECKING, Optional
    
     from django.db.models import Count, Q
    @@ -160,10 +160,8 @@ def get_rooms(user: "User"):
    
     def get_substitutions_context_data(
    +    wanted_day: date,
         request: Optional[HttpRequest] = None,
    -    year: Optional[int] = None,
    -    month: Optional[int] = None,
    -    day: Optional[int] = None,
         is_print: bool = False,
         number_of_days: Optional[int] = None,
         show_header_box: Optional[bool] = None,
    @@ -171,12 +169,6 @@ def get_substitutions_context_data(
         """Get context data for the substitutions table."""
         context = {}
    
    -    if day:
    -        wanted_day = timezone.datetime(year=year, month=month, day=day).date()
    -        wanted_day = TimePeriod.get_next_relevant_day(wanted_day)
    -    else:
    -        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), timezone.now().time())
    -
         day_number = (
             number_of_days or get_site_preferences()["chronos__substitutions_print_number_of_days"]
         )
    @@ -191,7 +183,7 @@ def get_substitutions_context_data(
             next_day = wanted_day
             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))
    +            next_day = next_day + timedelta(days=1)
         else:
             day_contexts = {wanted_day: {"day": wanted_day}}
    
    @@ -202,22 +194,9 @@ def get_substitutions_context_data(
             day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day)
    
             if show_header_box:
    -            subs = LessonSubstitution.objects.on_day(day).order_by(
    -                "lesson_period__lesson__groups", "lesson_period__period"
    -            )
                 absences = Absence.objects.on_day(day)
                 day_contexts[day]["absent_teachers"] = absences.absent_teachers()
                 day_contexts[day]["absent_groups"] = absences.absent_groups()
    -            day_contexts[day]["affected_teachers"] = subs.affected_teachers()
    -            affected_groups = subs.affected_groups()
    -            if get_site_preferences()["chronos__affected_groups_parent_groups"]:
    -                groups_with_parent_groups = affected_groups.filter(parent_groups__isnull=False)
    -                groups_without_parent_groups = affected_groups.filter(parent_groups__isnull=True)
    -                affected_groups = Group.objects.filter(
    -                    Q(child_groups__pk__in=groups_with_parent_groups.values_list("pk", flat=True))
    -                    | Q(pk__in=groups_without_parent_groups.values_list("pk", flat=True))
    -                ).distinct()
    -            day_contexts[day]["affected_groups"] = affected_groups
    
         if not is_print:
             context = day_contexts[wanted_day]
    @@ -226,10 +205,6 @@ def get_substitutions_context_data(
                 "dest": reverse("substitutions"),
             }
    
    -        context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
    -            wanted_day, "substitutions_by_date"
    -        )
    -
         else:
             context["days"] = day_contexts
    
    diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py
    index a45997c..ee01e35 100644
    --- a/aleksis/apps/chronos/views.py
    +++ b/aleksis/apps/chronos/views.py
    @@ -1,3 +1,4 @@
    +from datetime import date, datetime
     from typing import Optional
    
     from django.http import HttpRequest, HttpResponse
chronos_helpers.py 6.20 KiB
from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, Optional

from django.db.models import Count, Q
from django.http import HttpRequest, HttpResponseNotFound
from django.shortcuts import get_object_or_404
from django.urls import reverse

from guardian.core import ObjectPermissionChecker

from aleksis.core.models import Announcement, Group, Person, Room
from aleksis.core.util.core_helpers import get_site_preferences
from aleksis.core.util.predicates import check_global_permission

from ..managers import TimetableType
from ..models import (
    Absence,
    LessonPeriod,
    LessonSubstitution,
    Supervision,
    SupervisionSubstitution,
)
from .build import build_substitutions_list
from .js import date_unix

if TYPE_CHECKING:
    from django.contrib.auth import get_user_model

    User = get_user_model()  # noqa


def get_el_by_pk(
    request: HttpRequest,
    type_: str,
    pk: int,
    prefetch: bool = False,
    *args,
    **kwargs,
):
    if type_ == TimetableType.GROUP.value:
        return get_object_or_404(
            Group.objects.prefetch_related("owners", "parent_groups") if prefetch else Group,
            pk=pk,
        )
    elif type_ == TimetableType.TEACHER.value:
        return get_object_or_404(Person, pk=pk)
    elif type_ == TimetableType.ROOM.value:
        return get_object_or_404(Room, pk=pk)
    else:
        return HttpResponseNotFound()


def get_substitution_by_id(request: HttpRequest, id_: int, week: int):
    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
    wanted_week = lesson_period.lesson.get_calendar_week(week)

    return LessonSubstitution.objects.filter(
        week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period
    ).first()


def get_supervision_substitution_by_id(request: HttpRequest, id_: int, date: datetime.date):
    supervision = get_object_or_404(Supervision, pk=id_)

    return SupervisionSubstitution.objects.filter(date=date, supervision=supervision).first()


def get_teachers(user: "User"):
    """Get the teachers whose timetables are allowed to be seen by current user."""
    checker = ObjectPermissionChecker(user)

    teachers = (
        Person.objects.annotate(lessons_count=Count("lesson_events_as_teacher"))
        .filter(lessons_count__gt=0)
        .order_by("short_name", "last_name")
    )

    if not check_global_permission(user, "chronos.view_all_person_timetables"):
        checker.prefetch_perms(teachers)

        wanted_teachers = set()

        for teacher in teachers:
            if checker.has_perm("core.view_person_timetable", teacher):
                wanted_teachers.add(teacher.pk)

        teachers = teachers.filter(Q(pk=user.person.pk) | Q(pk__in=wanted_teachers))

    teachers = teachers.distinct()

    return teachers


def get_groups(user: "User"):
    """Get the groups whose timetables are allowed to be seen by current user."""
    checker = ObjectPermissionChecker(user)

    groups = (
        Group.objects.for_current_school_term_or_all()
        .annotate(
            lessons_count=Count("lesson_events"),
            child_lessons_count=Count("child_groups__lesson_events"),
        )
        .filter(Q(lessons_count__gt=0) | Q(child_lessons_count__gt=0))
    )

    group_types = get_site_preferences()["chronos__group_types_timetables"]

    if group_types:
        groups = groups.filter(group_type__in=group_types)

    groups = groups.order_by("short_name", "name")

    if not check_global_permission(user, "chronos.view_all_group_timetables"):
        checker.prefetch_perms(groups)

        wanted_classes = set()

        for _class in groups:
            if checker.has_perm("core.view_group_timetable", _class):
                wanted_classes.add(_class.pk)

        groups = groups.filter(
            Q(pk__in=wanted_classes) | Q(members=user.person) | Q(owners=user.person)
        )
        if user.person.primary_group:
            groups = groups.filter(Q(pk=user.person.primary_group.pk))

    groups = groups.distinct()

    return groups


def get_rooms(user: "User"):
    """Get the rooms whose timetables are allowed to be seen by current user."""
    checker = ObjectPermissionChecker(user)

    rooms = (
        Room.objects.annotate(lessons_count=Count("lesson_events"))
        .filter(lessons_count__gt=0)
        .order_by("short_name", "name")
    )

    if not check_global_permission(user, "chronos.view_all_room_timetables"):
        checker.prefetch_perms(rooms)

        wanted_rooms = set()

        for room in rooms:
            if checker.has_perm("core.view_room_timetable", room):
                wanted_rooms.add(room.pk)

        rooms = rooms.filter(Q(pk__in=wanted_rooms))

    rooms = rooms.distinct()

    return rooms


def get_substitutions_context_data(
    wanted_day: date,
    request: Optional[HttpRequest] = None,
    is_print: bool = False,
    number_of_days: Optional[int] = None,
    show_header_box: Optional[bool] = None,
):
    """Get context data for the substitutions table."""
    context = {}

    day_number = (
        number_of_days or get_site_preferences()["chronos__substitutions_print_number_of_days"]
    )
    show_header_box = (
        show_header_box
        if show_header_box is not None
        else get_site_preferences()["chronos__substitutions_show_header_box"]
    )
    day_contexts = {}

    if is_print:
        next_day = wanted_day
        for _i in range(day_number):
            day_contexts[next_day] = {"day": next_day}
            next_day = next_day + timedelta(days=1)
    else:
        day_contexts = {wanted_day: {"day": wanted_day}}

    for day in day_contexts:
        subs = build_substitutions_list(day)
        day_contexts[day]["substitutions"] = subs

        day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day)

        if show_header_box:
            absences = Absence.objects.on_day(day)
            day_contexts[day]["absent_teachers"] = absences.absent_teachers()
            day_contexts[day]["absent_groups"] = absences.absent_groups()

    if not is_print:
        context = day_contexts[wanted_day]
        context["datepicker"] = {
            "date": date_unix(wanted_day),
            "dest": reverse("substitutions"),
        }

    else:
        context["days"] = day_contexts

    return context