diff --git a/aleksis/apps/chronos/frontend/index.js b/aleksis/apps/chronos/frontend/index.js
index 0414c125ea5c304433084070a4eb627bf9bad62e..4749ac593afcd244d9d4f06b992ef6dea99c0225 100644
--- a/aleksis/apps/chronos/frontend/index.js
+++ b/aleksis/apps/chronos/frontend/index.js
@@ -32,5 +32,21 @@ export default {
         fullWidth: true,
       },
     },
+    {
+      path: "substitutions/print/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "chronos.substitutions",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
+      },
+    },
+    {
+      path: "substitutions/print/:date/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "chronos.substitutionsByDate",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
+      },
+    },
   ],
 };
diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index 45aecf55b7553f0bd61c74cc4fee19e65f05b8d9..6b2f4c3cc7a6ec190624e8db5c69c3edd3c818d6 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -946,3 +946,11 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
     def not_amending(self) -> "LessonEventQuerySet":
         """Get all lesson events that are not amending other events."""
         return self.filter(amends__isnull=True)
+
+    def amending(self) -> "LessonEventQuerySet":
+        """Get all lesson events that are amending other events."""
+        return self.filter(amends__isnull=False)
+
+
+class SupervisionEventQuerySet(LessonEventQuerySet):
+    pass
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 35f4e0119151d0892c6bdc9433fd7686346d9ae7..3189999febab65467e7cd76ad6e273f438be089b 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -42,6 +42,7 @@ from aleksis.apps.chronos.managers import (
     LessonPeriodQuerySet,
     LessonSubstitutionManager,
     LessonSubstitutionQuerySet,
+    SupervisionEventQuerySet,
     SupervisionManager,
     SupervisionQuerySet,
     SupervisionSubstitutionManager,
@@ -1209,20 +1210,26 @@ class AutomaticPlan(LiveDocument):
     @property
     def current_start_day(self) -> date:
         """Get first day which should be shown in the PDF."""
-        return TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
+        from aleksis.apps.chronos.util.chronos_helpers import get_next_relevant_day
+
+        return get_next_relevant_day(timezone.now())
 
     @property
     def current_end_day(self) -> date:
         """Get last day which should be shown in the PDF."""
-        return self.current_start_day + timedelta(days=self.number_of_days - 1)
+        from aleksis.apps.chronos.util.chronos_helpers import get_next_relevant_day
+
+        day = self.current_start_day
+        for _i in range(self.number_of_days - 1):
+            day = get_next_relevant_day(day)
+        return day
 
     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
 
         context = get_substitutions_context_data(
-            request=None,
-            is_print=True,
+            wanted_day=date.today(),
             number_of_days=self.number_of_days,
             show_header_box=self.show_header_box,
         )
@@ -1248,11 +1255,16 @@ class AutomaticPlan(LiveDocument):
                     continue
 
                 # Check if the changed object is relevant for the time period of the PDF file
-                if isinstance(version.object, Event):
+                if not version.object.amends:
+                    return
+
+                if version.object.datetime_start:
+                    date_start = version.object.datetime_start.date()
+                    date_end = version.object.datetime_end.date()
+                else:
                     date_start = version.object.date_start
                     date_end = version.object.date_end
-                else:
-                    date_start = date_end = version.object.date
+
                 if date_start <= self.current_end_day and date_end >= self.current_start_day:
                     update = True
                     break
@@ -1537,9 +1549,15 @@ class LessonEvent(CalendarEvent):
 
     @classmethod
     def get_objects(
-        cls, request: HttpRequest | None = None, params: dict[str, any] | None = None, **kwargs
+        cls,
+        request: HttpRequest | None = None,
+        params: dict[str, any] | None = None,
+        no_effect: bool = False,
+        **kwargs,
     ) -> Iterable:
         """Return all objects that should be included in the calendar."""
+        if no_effect:
+            return super().get_objects(request, params, **kwargs)
         objs = (
             super()
             .get_objects(request, params, **kwargs)
@@ -1556,6 +1574,7 @@ class LessonEvent(CalendarEvent):
             type_ = params.get("type", None)
             not_amended = params.get("not_amended", False)
             not_amending = params.get("not_amending", False)
+            amending = params.get("amending", False)
             own = params.get("own", False)
 
             if not_amended:
@@ -1564,6 +1583,9 @@ class LessonEvent(CalendarEvent):
             if not_amending:
                 objs = objs.not_amending()
 
+            if amending:
+                objs = objs.amending()
+
             if request and "own" in params:
                 if own:
                     objs = objs.for_person(request.user.person)
@@ -1599,7 +1621,7 @@ class SupervisionEvent(LessonEvent):
     name = "supervision"
     verbose_name = _("Supervisions")
 
-    objects = RecurrencePolymorphicManager.from_queryset(LessonEventQuerySet)()
+    objects = RecurrencePolymorphicManager.from_queryset(SupervisionEventQuerySet)()
 
     @classmethod
     def value_title(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
@@ -1624,10 +1646,22 @@ class SupervisionEvent(LessonEvent):
         cls, request: HttpRequest | None = None, params: dict[str, any] | None = None, **kwargs
     ) -> Iterable:
         """Return all objects that should be included in the calendar."""
-        objs = super().get_objects(request, params, **kwargs).instance_of(cls)
+        objs = super().get_objects(request, params, no_effect=True, **kwargs)
         if params:
             obj_id = int(params.get("id", 0))
             type_ = params.get("type", None)
+            not_amended = params.get("not_amended", False)
+            not_amending = params.get("not_amending", False)
+            amending = params.get("amending", False)
+
+            if not_amended:
+                objs = objs.not_amended()
+
+            if not_amending:
+                objs = objs.not_amending()
+
+            if amending:
+                objs = objs.amending()
 
             if type_ and obj_id:
                 if type_ == "TEACHER":
diff --git a/aleksis/apps/chronos/preferences.py b/aleksis/apps/chronos/preferences.py
index d26653216f30acefac397ead20f97193546b0f0a..b3dc8362091693b313bda2d5f9493d8afa7e7701 100644
--- a/aleksis/apps/chronos/preferences.py
+++ b/aleksis/apps/chronos/preferences.py
@@ -1,13 +1,16 @@
 from datetime import time
 
+from django.core.exceptions import ValidationError
 from django.utils.translation import gettext_lazy as _
 
+from calendarweek.django import i18n_day_name_choices_lazy
 from colorfield.widgets import ColorWidget
 from dynamic_preferences.preferences import Section
 from dynamic_preferences.types import (
     BooleanPreference,
     IntegerPreference,
     ModelMultipleChoicePreference,
+    MultipleChoicePreference,
     StringPreference,
     TimePreference,
 )
@@ -52,6 +55,34 @@ class ShortenGroupsLimit(IntegerPreference):
     )
 
 
+@site_preferences_registry.register
+class SubstitutionsRelevantDays(MultipleChoicePreference):
+    """Relevant days which have substitution plans."""
+
+    section = chronos
+    name = "substitutions_relevant_days"
+    default = [0, 1, 2, 3, 4]
+    verbose_name = _("Relevant days for substitution plans")
+    required = True
+    choices = i18n_day_name_choices_lazy()
+
+    def validate(self, value):
+        for v in value:
+            if int(v) not in self.get_choice_values():
+                raise ValidationError(f"{v} is not a valid choice")
+
+
+@site_preferences_registry.register
+class SubstitutionsDayChangeTime(TimePreference):
+    """Time when substitution plans should switch to the next day."""
+
+    section = chronos
+    name = "substitutions_day_change_time"
+    default = time(18, 0)
+    verbose_name = _("Time when substitution plans switch to the next day")
+    required = True
+
+
 @site_preferences_registry.register
 class SubstitutionsPrintNumberOfDays(IntegerPreference):
     section = chronos
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index 85a0d1c4a6b7e4bdef21edcaf1a294aa22d473c3..34d58360ce18353231f56acccf1a00818f479fbb 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -1,13 +1,11 @@
 from rules import add_perm
 
 from aleksis.core.util.predicates import (
-    has_any_object,
     has_global_perm,
     has_object_perm,
     has_person,
 )
 
-from .models import LessonSubstitution
 from .util.predicates import has_any_timetable_object, has_room_timetable_perm, has_timetable_perm
 
 # View timetable overview
@@ -42,10 +40,7 @@ delete_substitution_predicate = has_person & (
 add_perm("chronos.delete_substitution_rule", delete_substitution_predicate)
 
 # View substitutions
-view_substitutions_predicate = has_person & (
-    has_global_perm("chronos.view_lessonsubstitution")
-    | has_any_object("chronos.view_lessonsubstitution", LessonSubstitution)
-)
+view_substitutions_predicate = has_person & (has_global_perm("chronos.view_lessonsubstitution"))
 add_perm("chronos.view_substitutions_rule", view_substitutions_predicate)
 
 # View all supervisions per day
diff --git a/aleksis/apps/chronos/schema/__init__.py b/aleksis/apps/chronos/schema/__init__.py
index 253f56f183a9391bebf1cc2c4b1322864a4de48b..279003489d17818eda4487867de799fb1eeef213 100644
--- a/aleksis/apps/chronos/schema/__init__.py
+++ b/aleksis/apps/chronos/schema/__init__.py
@@ -2,13 +2,13 @@ from datetime import timezone
 
 import graphene
 from graphene_django import DjangoObjectType
-from graphene_django_cud.mutations import (
-    DjangoBatchCreateMutation,
-    DjangoBatchDeleteMutation,
-    DjangoBatchPatchMutation,
-)
 
 from aleksis.core.models import Group, Person, Room
+from aleksis.core.schema.base import (
+    BaseBatchCreateMutation,
+    BaseBatchDeleteMutation,
+    BaseBatchPatchMutation,
+)
 
 from ..models import LessonEvent
 from ..util.chronos_helpers import get_groups, get_rooms, get_teachers
@@ -80,7 +80,7 @@ class DatetimeTimezoneMixin:
         return value
 
 
-class AmendLessonBatchCreateMutation(DatetimeTimezoneMixin, DjangoBatchCreateMutation):
+class AmendLessonBatchCreateMutation(DatetimeTimezoneMixin, BaseBatchCreateMutation):
     class Meta:
         model = LessonEvent
         permissions = ("chronos.edit_substitution_rule",)
@@ -98,12 +98,13 @@ class AmendLessonBatchCreateMutation(DatetimeTimezoneMixin, DjangoBatchCreateMut
 
     @classmethod
     def before_save(cls, root, info, input, created_objects):  # noqa: A002
+        super().before_save(root, info, input, created_objects)
         for obj in created_objects:
             obj.timezone = obj.amends.timezone
         return created_objects
 
 
-class AmendLessonBatchPatchMutation(DatetimeTimezoneMixin, DjangoBatchPatchMutation):
+class AmendLessonBatchPatchMutation(DatetimeTimezoneMixin, BaseBatchPatchMutation):
     class Meta:
         model = LessonEvent
         permissions = ("chronos.edit_substitution_rule",)
@@ -111,12 +112,13 @@ class AmendLessonBatchPatchMutation(DatetimeTimezoneMixin, DjangoBatchPatchMutat
 
     @classmethod
     def before_save(cls, root, info, input, updated_objects):  # noqa: A002
+        super().before_save(root, info, input, updated_objects)
         for obj in updated_objects:
             obj.timezone = obj.amends.timezone
         return updated_objects
 
 
-class AmendLessonBatchDeleteMutation(DjangoBatchDeleteMutation):
+class AmendLessonBatchDeleteMutation(BaseBatchDeleteMutation):
     class Meta:
         model = LessonEvent
         permissions = ("chronos.delete_substitution_rule",)
diff --git a/aleksis/apps/chronos/templates/chronos/partials/datepicker.html b/aleksis/apps/chronos/templates/chronos/partials/datepicker.html
deleted file mode 100644
index f7d49f34e44569009b9af35aec1b0f26571773f4..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/datepicker.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% load static %}
-
-{% if not display_date_only %}
-  <script type="text/javascript" src="{% static "js/helper.js" %}"></script>
-  {{ datepicker|json_script:"datepicker_data" }}
-  <script type="text/javascript" src="{% static "js/chronos/date_select.js" %}"></script>
-{% endif %}
-
-<div class="col s2 no-padding">
-  <a class="waves-effect waves-secondary btn-flat btn-flat-medium left" href="{{ url_prev }}">
-    <i class="material-icons iconify center" data-icon="mdi:chevron-left"></i>
-  </a>
-</div>
-
-{% if display_date_only %}
-  <div class="col s8">
-    <span class="card-title center-block" id="date">
-        {{ day|date:"l" }}, {{ day }}
-    </span>
-  </div>
-{% else %}
-  <div class="col s8 no-padding">
-    <input type="text" class="datepicker center-align" id="date">
-  </div>
-{% endif %}
-<div class="col s2 no-padding">
-  <a class="waves-effect waves-secondary btn-flat btn-flat-medium right" href="{{ url_next }}">
-    <i class="material-icons iconify center" data-icon="mdi:chevron-right"></i>
-  </a>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/elements.html b/aleksis/apps/chronos/templates/chronos/partials/elements.html
deleted file mode 100644
index a7df614d20a2bc7fb2c203d90b0473756294da0a..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/elements.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="card lesson-card {% if active_day and week_day == active_day %} z-depth-5 active {% endif %}">
-  <div class="card-content">
-    {% for element in elements %}
-      {% if element.label_ == "lesson_period" %}
-        {% include "chronos/partials/lesson.html" with lesson_period=element %}
-      {% elif element.label_ == "extra_lesson" and smart %}
-        {% include "chronos/partials/extra_lesson.html" with extra_lesson=element %}
-      {% elif element.label_ == "event" and smart %}
-        {% include "chronos/partials/event.html" with event=element %}
-      {% endif %}
-    {% endfor %}
-  </div>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/event.html b/aleksis/apps/chronos/templates/chronos/partials/event.html
deleted file mode 100644
index 3aa6b797639598c0e24051053f040fcd3f1aead4..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/event.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<div class="lesson-with-event">
-  <p>
-    {# Teacher or room > Display groups #}
-    {% if type.value == "teacher" or type.value == "room" %}
-      {% include "chronos/partials/groups.html" with groups=event.groups.all %}
-    {% endif %}
-
-    {# Class or room > Display teachers #}
-    {% if type.value == "room" or type.value == "group" %}
-      {% include "chronos/partials/teachers.html" with teachers=event.teachers.all %}
-    {% endif %}
-
-    {# Teacher or class > Display rooms #}
-    {% if type.value == "teacher" or type.value == "group" %}
-      {% for room in event.rooms.all %}
-        {% include "chronos/partials/room.html" with room=room %}{% if not forloop.last %},{% endif %}
-      {% endfor %}
-    {% endif %}
-
-    {% if type.value == "teacher" and not event.groups.all and not event.rooms.all and event.title %}
-      <em>{{ event.title }}</em>
-    {% elif type.value == "group" and not event.teachers.all and not event.groups.all and event.title %}
-      <em>{{ event.title }}</em>
-    {% elif type.value == "room" and not event.teachers.all and not event.groups.all and event.title %}
-      <em>{{ event.title }}</em>
-    {% elif event.title %}
-      <br/>
-      <small>
-        <em>{{ event.title }}</em>
-      </small>
-    {% endif %}
-  </p>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/extra_lesson.html b/aleksis/apps/chronos/templates/chronos/partials/extra_lesson.html
deleted file mode 100644
index 1113f011f0164be5a92f1dbe7ef3b32e8db07ed5..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/extra_lesson.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<div class="lesson-with-sub"
-     style="{% include "chronos/partials/subject_colour.html" with subject=extra_lesson.subject %}">
-  <p>
-    {# Teacher or room > Display groups #}
-    {% if type.value == "teacher" or type.value == "room" %}
-      {% include "chronos/partials/groups.html" with groups=extra_lesson.groups.all %}
-    {% endif %}
-
-    {# Class or room > Display teachers #}
-    {% if type.value == "room" or type.value == "group" %}
-      {% include "chronos/partials/teachers.html" with teachers=extra_lesson.teachers.all %}
-    {% endif %}
-
-    {% include "chronos/partials/subject.html" with subject=extra_lesson.subject %}
-
-    {# Teacher or class > Display rooms #}
-    {% if type.value == "teacher" or type.value == "group" %}
-      {% include "chronos/partials/room.html" with room=extra_lesson.room %}
-    {% endif %}
-
-    {% if extra_lesson.comment %}
-      <br/>
-      <small>
-        <em>{{ extra_lesson.comment }}</em>
-      </small>
-    {% endif %}
-  </p>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/group.html b/aleksis/apps/chronos/templates/chronos/partials/group.html
index d2e345d53df2bec9020b9de38139726744d1effe..3105150c7630d7d6fd550536366900d3ba3a3fa1 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/group.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/group.html
@@ -1,3 +1 @@
-<a href="{% url "timetable" "group" item.pk %}">
-  {{ item.short_name }}{% if not forloop.last %},{% endif %}
-</a>
+{{ item.short_name }}{% if not forloop.last %},{% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/groups.html b/aleksis/apps/chronos/templates/chronos/partials/groups.html
index fb85c6d4dce53da5aea4ecef805fb52ddab4eed9..61bf6927b350f89cb2f191acc593dcb2758fb055 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/groups.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/groups.html
@@ -1,5 +1,5 @@
 {% if groups.count == 1 and groups.0.parent_groups.all and request.site.preferences.chronos__use_parent_groups %}
-  {% include "chronos/partials/groups_part.html" with groups=groups.0.parent_groups.all no_collapsible=no_collapsible %}
+  {% include "chronos/partials/groups_part.html" with groups=groups.0.parent_groups.all %}
 {% else %}
   {% include "chronos/partials/groups_part.html" with groups=groups no_collapsible=no_collapsible %}
 {% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/groups_part.html b/aleksis/apps/chronos/templates/chronos/partials/groups_part.html
index edafcb1977d04a18fb2a53651cf4ad6def3f0b19..d4c8f9f2a71c066f61b4c0a1c71d44674b190f0a 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/groups_part.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/groups_part.html
@@ -1,7 +1,3 @@
-{% if groups.count > request.site.preferences.chronos__shorten_groups_limit and request.user.person.preferences.chronos__shorten_groups and not no_collapsible %}
-  {% include "components/text_collapsible.html" with template="chronos/partials/group.html" qs=groups %}
-{% else %}
-  {% for group in groups %}
-    {% include "chronos/partials/group.html" with item=group %}
-  {% endfor %}
-{% endif %}
+{% for group in groups %}
+  {% include "chronos/partials/group.html" with item=group %}
+{% endfor %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/headerbox.html b/aleksis/apps/chronos/templates/chronos/partials/headerbox.html
index 1ed1e7de07cdcf64b2afea35f93f40b74aca9bd2..c3862168a7f71127935987b99767fbed20d9ab79 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/headerbox.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/headerbox.html
@@ -1,53 +1,53 @@
 {% load i18n %}
 
 {% if affected_teachers or affected_groups or absent_teachers or absent_groups %}
-  <div class="{% if not print %}card{% endif %}">
-    <div class="{% if not print %}card-content{% endif %}">
+  <div>
+    <div>
       {% if absent_teachers %}
         <div class="row no-margin">
-          <div class="col {% if not print %}s12 m3{% else %}s3{% endif %}">
+          <div class="col">
             <strong class="truncate">
               {% trans "Absent teachers" %}
             </strong>
           </div>
-          <div class="col {% if not print %}s12 m9{% else %}s9{% endif %} black-text-a">
+          <div class="col">
             {% include "chronos/partials/teachers.html" with teachers=absent_teachers %}
           </div>
         </div>
       {% endif %}
       {% if absent_groups %}
         <div class="row no-margin">
-          <div class="col {% if not print %}s12 m3{% else %}s3{% endif %}">
+          <div class="col">
             <strong class="truncate">
               {% trans "Absent groups" %}
             </strong>
           </div>
-          <div class="col {% if not print %}s12 m9{% else %}s9{% endif %} black-text-a">
+          <div class="col">
             {% include "chronos/partials/groups.html" with groups=absent_groups no_collapsible=True %}
           </div>
         </div>
       {% endif %}
       {% if affected_teachers %}
         <div class="row no-margin">
-          <div class="col {% if not print %}s12 m3{% else %}s3{% endif %}">
+          <div class="col">
             <strong class="truncate">
               {% trans "Affected teachers" %}
             </strong>
           </div>
-          <div class="col {% if not print %}s12 m9{% else %}s9{% endif %} black-text-a">
+          <div class="col">
             {% include "chronos/partials/teachers.html" with teachers=affected_teachers %}
           </div>
         </div>
       {% endif %}
       {% if affected_groups %}
         <div class="row no-margin">
-          <div class="col {% if not print %}s12 m3{% else %}s3{% endif %}">
+          <div class="col">
             <strong class="truncate">
               {% trans "Affected groups" %}
             </strong>
           </div>
-          <div class="col {% if not print %}s12 m9{% else %}s9{% endif %} black-text-a">
-            {% include "chronos/partials/groups.html" with groups=affected_groups no_collapsible=True %}
+          <div class="col">
+            {% include "chronos/partials/groups.html" with groups=affected_groups %}
           </div>
         </div>
       {% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/holiday.html b/aleksis/apps/chronos/templates/chronos/partials/holiday.html
deleted file mode 100644
index 85fc42d4ffd50621da8fe04802b3c2d18a509b71..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/holiday.html
+++ /dev/null
@@ -1 +0,0 @@
-<span class="badge new blue center-align holiday-badge">{{ holiday.title }}</span>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/lesson.html b/aleksis/apps/chronos/templates/chronos/partials/lesson.html
deleted file mode 100644
index 3fa1f75788ae6433bc1bf2ef9983a06df8fb59ff..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/lesson.html
+++ /dev/null
@@ -1,103 +0,0 @@
-{% load i18n %}
-
-<div style="
-        {% with sub=lesson_period.get_substitution %}
-        {# Display background color only if lesson is not cancelled and it is not the old room #}
-        {% if not smart %}
-          {% include "chronos/partials/subject_colour.html" with subject=lesson_period.lesson.subject %}
-        {% elif not sub.cancelled and not lesson_period.get_substitution.cancelled_for_teachers and not lesson_period.replaced_by_event %}
-          {% if not type.value == "room" or lesson_period.room == lesson_period.get_room or lesson_period.get_room == el %}
-            {% if sub and sub.subject %}
-              {% include "chronos/partials/subject_colour.html" with subject=sub.subject %}
-            {% else %}
-              {% include "chronos/partials/subject_colour.html" with subject=lesson_period.lesson.subject %}
-            {% endif %}
-          {% endif %}
-        {% endif %}
-        {% endwith %}
-        "
-        {# Add CSS class for sub when it's a sub #}
-     class="{% if smart %}{% if lesson_period.get_substitution or lesson_period.replaced_by_event %}lesson-with-sub{% endif %}{% endif %}"
->
-  <p>
-    {% if lesson_period.replaced_by_event and smart %}
-      {% include "chronos/partials/groups.html" with groups=lesson_period.lesson.groups.all %}
-      {% include "chronos/partials/subject.html" with subject=lesson_period.lesson.subject %}
-      <br/>
-      <span class="badge new green">{% trans "Cancelled due to an event" %}</span>
-    {% elif lesson_period.get_substitution and smart %}
-      {% with sub=lesson_period.get_substitution %}
-        {# SUBSTITUTION #}
-        {% if type.value == "room" and lesson_period.room != lesson_period.get_room and lesson_period.get_room != el %}
-          {# When it's the old room, let it empty #}
-
-        {% elif sub.cancelled or sub.cancelled_for_teachers %}
-          {# When a badge (cancellation, etc.) exists, then display it with the teacher#}
-
-          {# Class or room > Display teacher #}
-          {% if type.value == "group" or type.value == "room" and lesson_period.lesson.teachers.all %}
-            {% include "chronos/partials/teachers.html" with teachers=lesson_period.lesson.teachers.all %}<br/>
-          {% endif %}
-
-          {# Badge #}
-          {% include "chronos/partials/subs/badge.html" with sub=sub %}
-
-        {% else %}
-          {# Display sub #}
-
-          {# Teacher or room > display classes #}
-          {% if type.value == "teacher" or type.value == "room" %}
-            {% include "chronos/partials/groups.html" with groups=lesson_period.lesson.groups.all %}
-          {% endif %}
-
-          {# Display teacher with tooltip #}
-          {% include "chronos/partials/subs/teachers.html" with type="substitution" el=sub %}
-
-          {# Display subject #}
-          {% include "chronos/partials/subs/subject.html" with type="substitution" el=sub %}
-
-          {# Teacher or class > display room #}
-          {% if type.value == "teacher" or type.value == "group" %}
-            {% include "chronos/partials/subs/room.html" with type="substitution" el=sub %}
-          {% endif %}
-        {% endif %}
-
-
-        {# Display the comment (e. g. work orders) #}
-        {% if sub.comment %}
-          <br>
-          <small>
-            <em>{{ sub.comment }}</em>
-          </small>
-        {% endif %}
-      {% endwith %}
-
-
-    {% else %}
-      {# Normal plan #}
-
-      {# Teacher or room > Display classes #}
-      {% if type.value == "teacher" or type.value == "room" %}
-        {#                            {{ element_container.element.classes }}#}
-        {% if lesson_period.lesson.groups %}
-          {% include "chronos/partials/groups.html" with groups=lesson_period.lesson.groups.all %}
-        {% endif %}
-      {% endif %}
-
-      {# Class or room > Display teacher #}
-      {% if type.value == "room" or type.value == "group" %}
-        {% include "chronos/partials/teachers.html" with teachers=lesson_period.lesson.teachers.all %}
-      {% endif %}
-
-      {# Display subject #}
-      {% include "chronos/partials/subject.html" with subject=lesson_period.lesson.subject %}
-
-      {# Teacher or class > Display room #}
-      {% if type.value == "teacher" or type.value == "group" %}
-        {% if lesson_period.room %}
-          {% include "chronos/partials/room.html" with room=lesson_period.room %}
-        {% endif %}
-      {% endif %}
-    {% endif %}
-  </p>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/lessons_col.html b/aleksis/apps/chronos/templates/chronos/partials/lessons_col.html
deleted file mode 100644
index 25ddedc47f422916b2e44f5419151e1a36ce8332..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/lessons_col.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% if holiday %}
-  <div class="row">
-    <div class="col s12">
-      <div class="card col s12 holiday-card">
-        <div class="card-content">
-          <p>
-            {% include "chronos/partials/holiday.html" with holiday=holiday %}<br/>
-          </p>
-        </div>
-      </div>
-    </div>
-  </div>
-{% else %}
-  {% for row in timetable %}
-    <div class="row">
-      <div class="col s4">
-        {% if row.type == "period" %}
-          {% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
-        {% endif %}
-      </div>
-      <div class="col s8">
-        {% if row.type == "period" %}
-          {% include "chronos/partials/elements.html" with elements=row.col %}
-        {% else %}
-          {% include "chronos/partials/supervision.html" with supervision=row.col %}
-        {% endif %}
-      </div>
-    </div>
-  {% endfor %}
-{% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/period_time.html b/aleksis/apps/chronos/templates/chronos/partials/period_time.html
deleted file mode 100644
index ebd76dab6393c83332e07c1b194248480846323f..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/period_time.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% load data_helpers %}
-
-<div class="card timetable-title-card">
-  <div class="card-content">
-
-    {# Lesson number #}
-    <span class="card-title left">
-                                {{ period }}.
-                            </span>
-
-    {# Time dimension of lesson #}
-    <div class="right timetable-time grey-text text-darken-2">
-      {% with period_obj=periods|get_dict:period %}
-        <span>{{ period_obj.0|time }}</span>
-        <br/>
-        <span>{{ period_obj.1|time }}</span>
-      {% endwith %}
-    </div>
-  </div>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/room.html b/aleksis/apps/chronos/templates/chronos/partials/room.html
deleted file mode 100644
index c96a7fd01975a86ac1bb66241a82872333d084bc..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/room.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% if room %}
-  <span class="tooltipped" data-position="bottom" data-tooltip="{{ room.name }}">
-    <a href="{% url "timetable" "room" room.pk %}">
-      {{ room.short_name }}
-    </a>
-  </span>
-{% endif %}
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 0000000000000000000000000000000000000000..e1f895c7d4aea8c03e02f71f0752e2a495e85c4a
--- /dev/null
+++ b/aleksis/apps/chronos/templates/chronos/partials/rooms.html
@@ -0,0 +1,3 @@
+{% for room in rooms %}
+  {{ room.short_name|default:room.name }}{% if not forloop.last %},{% endif %}
+{% endfor %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subject.html b/aleksis/apps/chronos/templates/chronos/partials/subject.html
index e326c75a2419b28880578a32a1ffea4002194847..efed11727cb1b824f238d0e33d08cd2036f60837 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subject.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subject.html
@@ -1,3 +1 @@
-<strong>
-  <span data-position="bottom" class="tooltipped" data-tooltip="{{ subject.name }}">{{ subject.short_name }}</span>
-</strong>
+{{ subject.short_name|default:subject.name }}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subject_colour.html b/aleksis/apps/chronos/templates/chronos/partials/subject_colour.html
deleted file mode 100644
index 4cead55119b2c4b799c13018dc175c2fe414f059..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/subject_colour.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% if subject.colour_fg %}
-  color: {{ subject.colour_fg }};
-{% endif %}
-{% if subject.colour_bg and subject.colour_bg != subject.colour_fg %}
-  background-color: {{ subject.colour_bg }};
-{% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
index c04c9dd331eeb4f271ae5e7c212b43a776c71a51..f0210546a37921a76c78a2e2d100f6b01092f1a9 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
@@ -1,7 +1,5 @@
 {% load i18n %}
 
-{% if sub.cancelled %}
+{% if el.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 833a24b2080d1ecb555ba94c8159fd8550ede7ce..06ccab2698661fd484ce14c9a06adb57551f225c 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 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 d1a4da990b388458a9de5614acd17d988c4aac09..447054384f19929af4872de104df01401254be2a 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/groups.html
@@ -1,5 +1,11 @@
-{% 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>
+  &nbsp;→&nbsp;
+  <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 ef1d2838373b1537444aa6f2248ddc4c0e644267..9aa39762a4a0b88b58351b33ab0b02cc759b63b1 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/period.html
@@ -1,19 +1,14 @@
+{% load i18n %}
 <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 }}.
+  {% if el.REFERENCE_OBJECT.slot_number_start and el.REFERENCE_OBJECT.slot_number_end %}
+    {% if el.REFERENCE_OBJECT.slot_number_start == el.REFERENCE_OBJECT.slot_number_end %}
+      {{ el.REFERENCE_OBJECT.slot_number_start }}.
     {% else %}
-      {{ el.period_from_on_day }}.–{{ el.period_to_on_day }}.
+      {{ el.REFERENCE_OBJECT.slot_number_start }}.–{{ el.REFERENCE_OBJECT.slot_number_end }}.
     {% endif %}
-  {% elif type == "supervision_substitution" %}
-    {% with break=el.supervision.break_item %}
-      {{ break.after_period_number }}./{{ break.before_period_number }}.
-    {% endwith %}
+  {% elif el.DTSTART.dt.time %}
+    {{ el.DTSTART.dt.time }}–{{ el.DTEND.dt.time }}
+  {% else %}
+    {% trans "all day" %}
   {% endif %}
 </strong>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
deleted file mode 100644
index 94f2d3574992d23dffe3d6f9b03ffdd3e75cb042..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% 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 %}
-{% 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 0000000000000000000000000000000000000000..6a1ad0dedf556584323a97c9d4168497af81af35
--- /dev/null
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/rooms.html
@@ -0,0 +1,11 @@
+{% 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>
+  &nbsp;→&nbsp;
+  <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 1b7a3c5ce4e0ba6f286778adfb846d5006b9e2cb..033d7b745a36aa3d859ad95b79e705b9ce4cfb87 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
@@ -1,28 +1,21 @@
 {% 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 %}
-  {% endif %}
-{% elif type == "supervision_substitution" %}
+{% if el.name == "supervision" %}
   {% trans "Supervision" %}
-{% elif type == "extra_lesson" %}
+{% elif not el.amends.subject and not el.subject %}
+  {% if el.amends.title %}
+    <s>{{ el.amends.title }}</s>
+  {% endif %}
+  {% if el.title %}
+    <s>{{ el.title }}</s>
+  {% endif %}
+{% elif el.cancelled %}
+  <s>{% include "chronos/partials/subject.html" with subject=el.amends.subject %}</s>
+{% elif el.subject and el.amends.subject %}
+  <s>{% include "chronos/partials/subject.html" with subject=el.amends.subject %}</s>
+  &nbsp;→&nbsp;
+  <strong>{% include "chronos/partials/subject.html" with subject=el.subject %}</strong>
+{% 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 4fa80d8fc7f82d8cae15010083ad4b93222bba41..9e08eeb5aca391263f0112010d58f58766af5f6a 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/partials/supervision.html b/aleksis/apps/chronos/templates/chronos/partials/supervision.html
deleted file mode 100644
index 0fff8f5cfef6c5fa1e897c0705210ce3d1a3ca54..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/supervision.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{% load i18n %}
-
-<div class="card lesson-card supervision-card {% if active_day and week_day == active_day %} z-depth-5 active {% endif %}">
-  <div class="card-content">
-    {% if supervision %}
-      <div style="
-              {% if supervision.area.colour_fg %}
-                color: {{ supervision.area.colour_fg }};
-              {% endif %}
-              {% if supervision.area.colour_bg and supervision.area.colour_bg != supervision.area.colour_fg %}
-                background-color: {{ supervision.area.colour_bg }};
-              {% endif %}
-              " class="{% if supervision.get_substitution and smart %}lesson-with-sub{% endif %}">
-        <p>
-          <strong>{% trans "Supervision" %}</strong>
-          <span data-position="bottom" class="tooltipped"
-                data-tooltip="{{ supervision.area.name }}" title="{{ supervision.area.name }}">
-            {{ supervision.area.short_name }}
-          </span>
-          {% if supervision.get_substitution and smart %}
-            {% include "chronos/partials/subs/teachers.html" with type="supervision_substitution" el=supervision.get_substitution %}
-          {% elif type == "supervision_area" %}
-            {% include "chronos/partials/teachers.html" with teachers=supervision.teachers %}
-          {% endif %}
-        </p>
-      </div>
-    {% endif %}
-  </div>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/teachers.html b/aleksis/apps/chronos/templates/chronos/partials/teachers.html
index d7ec97197f1309c651d0a8c07b0d1e387ecaade8..f506b4b14287cb35d8f4127cd21aafdd223f3287 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/teachers.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/teachers.html
@@ -1,8 +1,3 @@
 {% for teacher in teachers %}
-  <span data-position="bottom" class="tooltipped"
-        data-tooltip="{{ teacher }}">
-      <a href="{% url "timetable" "teacher" teacher.pk %}">
-          {{ teacher.short_name }}{% if not forloop.last %},{% endif %}
-      </a>
-  </span>
+  {{ teacher.short_name|default:teacher.full_name }}{% if not forloop.last %},{% endif %}
 {% endfor %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/today.html b/aleksis/apps/chronos/templates/chronos/partials/today.html
deleted file mode 100644
index 8f3897c26b5de1d4c69d825d828e8df6630cf682..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/today.html
+++ /dev/null
@@ -1,2 +0,0 @@
-{% load i18n %}
-<span class="badge new orange center-align holiday-badge">{% trans "Today" %}</span>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/week_select.html b/aleksis/apps/chronos/templates/chronos/partials/week_select.html
deleted file mode 100644
index e9b57b84ceb1ce5443676cb9e6711640bb7bebd9..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/week_select.html
+++ /dev/null
@@ -1,47 +0,0 @@
-{% load i18n %}
-
-<div class="col s12 m6 right">
-  <div class="col s2 no-print">
-    <a class="waves-effect waves-teal btn-flat btn-flat-medium right" href="{{ url_prev }}">
-      <i class="material-icons iconify center" data-icon="mdi:chevron-left"></i>
-    </a>
-  </div>
-
-  <div class="input-field col s8 no-margin hide-on-med-and-up">
-    <select id="calendar-week-1">
-      {% for week in weeks %}
-        <option value="{{ week.week }}" {% if week == wanted_week %}
-                selected {% endif %}>{% trans "CW" %} {{ week.week }}
-          ({{ week.0|date:"SHORT_DATE_FORMAT" }}–{{ week.6|date:"SHORT_DATE_FORMAT" }})
-        </option>
-      {% endfor %}
-    </select>
-  </div>
-
-  <div class="input-field col s8 no-margin hide-on-med-and-down">
-    <select id="calendar-week-2">
-      {% for week in weeks %}
-        <option value="{{ week.week }}" {% if week == wanted_week %}
-                selected {% endif %}>{% trans "CW" %} {{ week.week }}
-          ({{ week.0|date:"j.n." }}–{{ week.6|date:"SHORT_DATE_FORMAT" }})
-        </option>
-      {% endfor %}
-    </select>
-  </div>
-
-  <div class="input-field col s8 no-margin hide-on-small-and-down hide-on-large-only">
-    <select id="calendar-week-3">
-      {% for week in weeks %}
-        <option value="{{ week.week }}" {% if week == wanted_week %}
-                selected {% endif %}>{% trans "CW" %} {{ week.week }} ({{ week.0|date:"j.n." }}–{{ week.6|date:"j.n." }})
-        </option>
-      {% endfor %}
-    </select>
-  </div>
-
-  <div class="col s2 no-print">
-    <a class="waves-effect waves-teal btn-flat btn-flat-medium left" href="{{ url_next }}">
-      <i class="material-icons iconify center" data-icon="mdi:chevron-right"></i>
-    </a>
-  </div>
-</div>
diff --git a/aleksis/apps/chronos/templates/chronos/partials/week_timetable.html b/aleksis/apps/chronos/templates/chronos/partials/week_timetable.html
deleted file mode 100644
index 22030da954fd3f96ccf324e4ef7b50ab76862835..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/partials/week_timetable.html
+++ /dev/null
@@ -1,71 +0,0 @@
-{#  Week days #}
-<div class="row">
-    <div class="col {% if active_day %}s1{% else %}s2{% endif %}">
-
-    </div>
-    {# Show short weekdays on tablets #}
-    {% for weekday in weekdays_short %}
-    <div class="col s2 hide-on-large-only">
-        <div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
-            <div class="card-content">
-              <span class="card-title">
-                {{ weekday.name }}
-              </span>
-                {% if smart %}
-                {{ weekday.date }}
-                {% if weekday.holiday %}
-                <br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
-                {% endif %}
-                {% if weekday.date == today %}
-                    <br/> {% include "chronos/partials/today.html" %}
-                {% endif %}
-                {% endif %}
-            </div>
-        </div>
-    </div>
-    {% endfor %}
-
-    {# Show long weekdays elsewere #}
-    {% for weekday in weekdays %}
-    <div class="col {% if weekday.date == active_day %} s3 {% else %} s2 {% endif %} hide-on-med-only">
-        <div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
-            <div class="card-content">
-              <span class="card-title">
-                {{ weekday.name }}
-              </span>
-                {% if smart %}
-                {{ weekday.date }}
-                {% if weekday.holiday %}
-                <br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
-                {% endif %}
-                {% if weekday.date == today %}
-                    <br/> {% include "chronos/partials/today.html" %}
-                {% endif %}
-                {% endif %}
-            </div>
-        </div>
-    </div>
-    {% endfor %}
-</div>
-
-{#  Lessons #}
-{% for row in timetable %}
-<div class="row">
-    <div class="col {% if active_day %}s1{% else %}s2{% endif %}">
-        {% if row.type == "period" %}
-        {% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
-        {% endif %}
-    </div>
-
-    {% for col in row.cols %}
-    {# A lesson #}
-    <div class="col {% if forloop.counter0 == active_day.weekday %} s3 {% else %} s2 {% endif %}">
-        {% if row.type == "period" %}
-        {% include "chronos/partials/elements.html" with elements=col week_day=forloop.counter0 active_day=active_day.weekday %}
-        {% else %}
-        {% include "chronos/partials/supervision.html" with supervision=col %}
-        {% endif %}
-    </div>
-    {% endfor %}
-</div>
-{% endfor %}
\ No newline at end of file
diff --git a/aleksis/apps/chronos/templates/chronos/substitutions_print.html b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
index 743a2234e990bbafff8343e3ec7108e89995991c..04dc4cf24a5a171fa0638a9074693d025b1ccd02 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 affected_teachers=c.affected_teachers affected_groups=c.affected_groups %}
 
     <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,30 +47,30 @@
 
       <tbody>
       {% for item in c.substitutions %}
-        {% ifchanged item.el.lesson_period.lesson.groups_to_show_names %}
+        {% ifchanged item.el.REFERENCE_OBJECT.group_names %}
           </tbody>
           <tbody class="{% cycle "striped" "not-striped" %}">
         {% endifchanged %}
 
-        <tr class="{% include "chronos/partials/subs/colour.html" with item=item %}">
+        <tr class="{% include "chronos/partials/subs/colour.html" with el=item.el.REFERENCE_OBJECT %}">
           <td>
-            {% include "chronos/partials/subs/groups.html" with type=item.type el=item.el %}
+            {% include "chronos/partials/subs/groups.html" with el=item.el.REFERENCE_OBJECT %}
           </td>
           <td>
-            {% include "chronos/partials/subs/period.html" with type=item.type el=item.el item=item %}
+            {% include "chronos/partials/subs/period.html" with el=item.el %}
           </td>
           <td>
-            {% include "chronos/partials/subs/teachers.html" with type=item.type el=item.el %}
+            {% include "chronos/partials/subs/teachers.html" with el=item.el.REFERENCE_OBJECT %}
           </td>
           <td>
-            {% include "chronos/partials/subs/subject.html" with type=item.type el=item.el %}
+            {% include "chronos/partials/subs/subject.html" with  el=item.el.REFERENCE_OBJECT %}
           </td>
           <td>
-            {% include "chronos/partials/subs/room.html" with type=item.type el=item.el %}
+            {% include "chronos/partials/subs/rooms.html" with  el=item.el.REFERENCE_OBJECT %}
           </td>
           <td>
-            {% include "chronos/partials/subs/badge.html" with sub=item.el %}
-            {% include "chronos/partials/subs/comment.html" with el=item.el %}
+            {% include "chronos/partials/subs/badge.html" with el=item.el.REFERENCE_OBJECT %}
+            {% include "chronos/partials/subs/comment.html" with el=item.el.REFERENCE_OBJECT %}
           </td>
         </tr>
       {% endfor %}
diff --git a/aleksis/apps/chronos/templates/chronos/timetable_print.html b/aleksis/apps/chronos/templates/chronos/timetable_print.html
deleted file mode 100644
index 4769673610019ad98538d11db579a9cbe6c4601c..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/timetable_print.html
+++ /dev/null
@@ -1,55 +0,0 @@
-{% extends 'core/base_print.html' %}
-
-{% load data_helpers static i18n %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable_print.css' %}">
-{% endblock %}
-
-{% block page_title %}
-  {% trans "Timetable" %} <i>{{ el.short_name }}</i>
-{% endblock %}
-{% block content %}
-  <div class="timetable-plan">
-    {#  Week days #}
-    <div class="row">
-      <div class="col s2">
-
-      </div>
-      {% for weekday in weekdays_short %}
-        <div class="col s2">
-          <div class="card timetable-title-card">
-            <div class="card-content">
-              <span class="card-title">
-                {{ weekday.name }}
-              </span>
-            </div>
-          </div>
-        </div>
-      {% endfor %}
-    </div>
-
-    {% for row in timetable %}
-      <div class="row">
-        <div class="col s2">
-          {% if row.type == "period" %}
-            {% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
-          {% endif %}
-        </div>
-
-        {% for col in row.cols %}
-          {# A lesson #}
-          <div class="col s2">
-            {% if row.type == "period" %}
-              {% include "chronos/partials/elements.html" with elements=col %}
-            {% else %}
-              {% include "chronos/partials/supervision.html" with supervision=col %}
-            {% endif %}
-          </div>
-        {% endfor %}
-      </div>
-    {% endfor %}
-  </div>
-
-{% endblock %}
diff --git a/aleksis/apps/chronos/urls.py b/aleksis/apps/chronos/urls.py
index 7d55323042b9a23438975c589bd23967c3cbb396..0e3a3e7f10bf87613a2614fbd299ced106be0d96 100644
--- a/aleksis/apps/chronos/urls.py
+++ b/aleksis/apps/chronos/urls.py
@@ -8,4 +8,9 @@ urlpatterns = [
         views.substitutions_print,
         name="substitutions_print",
     ),
+    path(
+        "substitutions/print/<str:day>/",
+        views.substitutions_print,
+        name="substitutions_print",
+    ),
 ]
diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
index 625998db7a20e293f0f8edc3e4f04f4109c3c9db..dd24a0452b93b3e34785e71f5ccdd5b58b908baa 100644
--- a/aleksis/apps/chronos/util/build.py
+++ b/aleksis/apps/chronos/util/build.py
@@ -1,5 +1,5 @@
 from collections import OrderedDict
-from datetime import date
+from datetime import date, datetime, time
 from typing import Union
 
 from django.apps import apps
@@ -7,9 +7,11 @@ from django.apps import apps
 from calendarweek import CalendarWeek
 
 from aleksis.apps.chronos.managers import TimetableType
+from aleksis.apps.chronos.models import SupervisionEvent
 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")
@@ -380,96 +382,57 @@ def build_timetable(
     return rows
 
 
-def build_substitutions_list(wanted_day: date) -> list[dict]:
+def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:
     rows = []
-
-    subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
-        "lesson_period__lesson__groups", "lesson_period__period"
+    affected_teachers = set()
+    affected_groups = set()
+
+    lesson_events = LessonEvent.get_single_events(
+        datetime.combine(wanted_day, time.min),
+        datetime.combine(wanted_day, time.max),
+        params={"amending": True},
+        with_reference_object=True,
     )
 
-    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
+    for lesson_event in lesson_events:
+        affected_teachers.update(lesson_event["REFERENCE_OBJECT"].teachers.all())
+        affected_teachers.update(lesson_event["REFERENCE_OBJECT"].amends.teachers.all())
+        affected_groups.update(lesson_event["REFERENCE_OBJECT"].groups.all())
+        affected_groups.update(lesson_event["REFERENCE_OBJECT"].amends.groups.all())
 
         row = {
             "type": "substitution",
-            "sort_a": sort_a,
-            "sort_b": str(sub.lesson_period.period.period),
-            "el": sub,
-            "start_period": start_period if start_period else sub.lesson_period.period.period,
-            "end_period": sub.lesson_period.period.period,
+            "sort_a": lesson_event["REFERENCE_OBJECT"].group_names,
+            "sort_b": str(lesson_event["DTSTART"]),
+            "el": lesson_event,
         }
 
-        if start_period:
-            start_period = None
-
         rows.append(row)
 
-    # Get supervision substitutions
-    super_subs = SupervisionSubstitution.objects.filter(date=wanted_day)
+    supervision_events = SupervisionEvent.get_single_events(
+        datetime.combine(wanted_day, time.min),
+        datetime.combine(wanted_day, time.max),
+        params={"amending": True},
+        with_reference_object=True,
+    )
+    print(supervision_events)
+
+    for supervision_event in supervision_events:
+        affected_teachers.update(supervision_event["REFERENCE_OBJECT"].teachers.all())
+        affected_teachers.update(supervision_event["REFERENCE_OBJECT"].amends.teachers.all())
 
-    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,
+            "sort_a": "Z",
+            "sort_b": str(supervision_event["DTSTART"]),
+            "el": supervision_event,
         }
-        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)
+    rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
 
-    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
-    def sorter(row: dict):
-        return row["sort_a"] + row["sort_b"]
-
-    rows.sort(key=sorter)
-
-    return rows
+    return rows, affected_teachers, affected_groups
 
 
 def build_weekdays(
diff --git a/aleksis/apps/chronos/util/change_tracker.py b/aleksis/apps/chronos/util/change_tracker.py
index c9b8afb1b94c9bd5630a50b36d36e08ba58d31fd..669f5c4ee3f0cd6077189e019f58954ad3adf0f1 100644
--- a/aleksis/apps/chronos/util/change_tracker.py
+++ b/aleksis/apps/chronos/util/change_tracker.py
@@ -1,9 +1,4 @@
-from typing import Any, Optional
-
 from django.contrib.contenttypes.models import ContentType
-from django.db import transaction
-from django.db.models import Model
-from django.db.models.signals import m2m_changed, post_save, pre_delete
 from django.dispatch import Signal, receiver
 
 from celery import shared_task
@@ -11,126 +6,9 @@ from reversion.models import Revision
 
 
 def _get_substitution_models():
-    from aleksis.apps.chronos.models import (
-        Event,
-        ExtraLesson,
-        LessonSubstitution,
-        SupervisionSubstitution,
-    )
-
-    return [LessonSubstitution, Event, ExtraLesson, SupervisionSubstitution]
-
-
-class TimetableChange:
-    """A change to timetable models."""
-
-    def __init__(
-        self,
-        instance: Model,
-        changed_fields: Optional[dict[str, Any]] = None,
-        created: bool = False,
-        deleted: bool = False,
-    ):
-        self.instance = instance
-        self.changed_fields = changed_fields or {}
-        self.created = created
-        self.deleted = deleted
-
-
-class TimetableDataChangeTracker:
-    """Helper class for tracking changes in timetable models by using signals."""
-
-    @classmethod
-    def get_models(cls) -> list[type[Model]]:
-        """Return all models that should be tracked."""
-        from aleksis.apps.chronos.models import (
-            Event,
-            ExtraLesson,
-            LessonSubstitution,
-            SupervisionSubstitution,
-        )
-
-        return [LessonSubstitution, Event, ExtraLesson, SupervisionSubstitution]
-
-    def __init__(self):
-        self.changes = {}
-        self.m2m_fields = {}
-
-        if transaction.get_autocommit():
-            raise RuntimeError("Cannot use change tracker outside of transaction")
-
-        for model in self.get_models():
-            post_save.connect(self._handle_save, sender=model, weak=False)
-            pre_delete.connect(self._handle_delete, sender=model, weak=False)
-
-            # Register signals for all relevant m2m fields
-            m2m_fields = {
-                getattr(model, f.name).through: f
-                for f in model._meta.get_fields()
-                if f.many_to_many
-            }
-            self.m2m_fields.update(m2m_fields)
-            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)
-
-    def get_instance_key(self, instance: Model) -> str:
-        """Get unique string key for an instance."""
-        return f"{instance._meta.model_name}_{instance.id}"
-
-    def _add_change(self, change: TimetableChange):
-        """Add a change to the list of changes and update, if necessary."""
-        key = self.get_instance_key(change.instance)
-        if key not in self.changes or change.deleted or change.created:
-            self.changes[key] = change
-        else:
-            self.changes[key].changed_fields.update(change.changed_fields)
-
-    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):
-        """Handle the delete signal."""
-        change = TimetableChange(instance, deleted=True)
-        self._add_change(change)
-
-    def _handle_m2m_changed(
-        self,
-        sender: type[Model],
-        instance: Model,
-        action: str,
-        model: type[Model],
-        pk_set: set,
-        **kwargs,
-    ):
-        """Handle the m2m_changed signal."""
-        if action in ["pre_add", "pre_remove", "pre_clear"]:
-            field_name = self.m2m_fields[sender].name
-            current_value = list(getattr(instance, field_name).all())
-            if self.get_instance_key(instance) in self.changes:
-                change = self.changes[self.get_instance_key(instance)]
-                if field_name in change.changed_fields:
-                    current_value = None
-
-            if current_value is not None:
-                change = TimetableChange(instance, changed_fields={field_name: current_value})
-                self._add_change(change)
-
-    def close(self):
-        """Disconnect signals and send change signal."""
-        for model in self.get_models():
-            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():
-            m2m_changed.disconnect(self._handle_m2m_changed, sender=through_model)
+    from aleksis.apps.chronos.models import LessonEvent, SupervisionEvent
 
-        timetable_data_changed.send(sender=self, changes=self.changes)
+    return [LessonEvent, SupervisionEvent]
 
 
 chronos_revision_created = Signal()
diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
index 8e1640150383c7c00cfb145c74c9b8750c7a0c29..8e70ceae4b383dfcd72eb3aad80afd12d2f08175 100644
--- a/aleksis/apps/chronos/util/chronos_helpers.py
+++ b/aleksis/apps/chronos/util/chronos_helpers.py
@@ -1,11 +1,9 @@
-from datetime import datetime, timedelta
+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 django.utils import timezone
 
 from guardian.core import ObjectPermissionChecker
 
@@ -15,15 +13,12 @@ from aleksis.core.util.predicates import check_global_permission
 
 from ..managers import TimetableType
 from ..models import (
-    Absence,
     LessonPeriod,
     LessonSubstitution,
     Supervision,
     SupervisionSubstitution,
-    TimePeriod,
 )
 from .build import build_substitutions_list
-from .js import date_unix
 
 if TYPE_CHECKING:
     from django.contrib.auth import get_user_model
@@ -160,23 +155,13 @@ def get_rooms(user: "User"):
 
 
 def get_substitutions_context_data(
-    request: Optional[HttpRequest] = None,
-    year: Optional[int] = None,
-    month: Optional[int] = None,
-    day: Optional[int] = None,
-    is_print: bool = False,
+    wanted_day: date,
     number_of_days: Optional[int] = None,
     show_header_box: Optional[bool] = None,
 ):
     """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"]
     )
@@ -187,50 +172,41 @@ def get_substitutions_context_data(
     )
     day_contexts = {}
 
-    if is_print:
-        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))
-    else:
-        day_contexts = {wanted_day: {"day": wanted_day}}
+    day = get_next_relevant_day(wanted_day)
+    for _i in range(day_number):
+        day_contexts[day] = {"day": day}
 
-    for day in day_contexts:
-        subs = build_substitutions_list(day)
+        subs, affected_teachers, affected_groups = build_substitutions_list(day)
         day_contexts[day]["substitutions"] = subs
 
-        day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day)
+        day_contexts[day]["announcements"] = Announcement.objects.on_date(day)
 
         if show_header_box:
-            subs = LessonSubstitution.objects.on_day(day).order_by(
-                "lesson_period__lesson__groups", "lesson_period__period"
+            day_contexts[day]["affected_teachers"] = sorted(
+                affected_teachers, key=lambda t: t.short_name or t.full_name
             )
-            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]
-        context["datepicker"] = {
-            "date": date_unix(wanted_day),
-            "dest": reverse("substitutions"),
-        }
+        day = get_next_relevant_day(day + timedelta(days=1))
 
-        context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
-            wanted_day, "substitutions_by_date"
-        )
+    context["days"] = day_contexts
+
+    return context
+
+
+def get_next_relevant_day(current: datetime | date) -> date:
+    """Get next relevant day for substitution plans."""
+    relevant_days = get_site_preferences()["chronos__substitutions_relevant_days"]
+    change_time = get_site_preferences()["chronos__substitutions_day_change_time"]
 
+    if isinstance(current, datetime):
+        current_day = current.date()
+        if current.time() > change_time:
+            current_day += timedelta(days=1)
     else:
-        context["days"] = day_contexts
+        current_day = current
 
-    return context
+    while str(current_day.weekday()) not in relevant_days:
+        current_day += timedelta(days=1)
+
+    return current_day
diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py
index a45997ca580db9f8fa442be52d88054055e2661d..84d093728e50825f90358084909d828f48cc7d71 100644
--- a/aleksis/apps/chronos/views.py
+++ b/aleksis/apps/chronos/views.py
@@ -1,10 +1,10 @@
+from datetime import date, datetime
 from typing import Optional
 
 from django.http import HttpRequest, HttpResponse
 
 from rules.contrib.views import permission_required
 
-from aleksis.core.decorators import pwa_cache
 from aleksis.core.util.pdf import render_pdf
 
 from .util.chronos_helpers import (
@@ -12,14 +12,12 @@ from .util.chronos_helpers import (
 )
 
 
-@pwa_cache
 @permission_required("chronos.view_substitutions_rule")
 def substitutions_print(
     request: HttpRequest,
-    year: Optional[int] = None,
-    month: Optional[int] = None,
-    day: Optional[int] = None,
+    day: Optional[str] = None,
 ) -> HttpResponse:
     """View all substitutions on a specified day."""
-    context = get_substitutions_context_data(request, year, month, day, is_print=True)
+    day = datetime.strptime(day, "%Y-%m-%d").date() if day else date.today()
+    context = get_substitutions_context_data(day)
     return render_pdf(request, "chronos/substitutions_print.html", context)