diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py
index f75e9f611e0eb73f456c780bed9f961b67d260f6..9da4770d092663c4baa2c5a9e770677305ea0121 100644
--- a/aleksis/apps/chronos/admin.py
+++ b/aleksis/apps/chronos/admin.py
@@ -23,7 +23,6 @@ from .models import (
     SupervisionArea,
     SupervisionSubstitution,
     TimePeriod,
-    TimetableWidget,
     ValidityRange,
 )
 from .util.format import format_date_period, format_m2m
@@ -209,13 +208,6 @@ class TimePeriodAdmin(admin.ModelAdmin):
 admin.site.register(TimePeriod, TimePeriodAdmin)
 
 
-class TimetableWidgetAdmin(admin.ModelAdmin):
-    list_display = ("title", "active")
-
-
-admin.site.register(TimetableWidget, TimetableWidgetAdmin)
-
-
 class ValidityRangeAdmin(admin.ModelAdmin):
     list_display = ("__str__", "date_start", "date_end")
     list_display_links = ("__str__", "date_start", "date_end")
diff --git a/aleksis/apps/chronos/filters.py b/aleksis/apps/chronos/filters.py
deleted file mode 100644
index 5e231ef98ed73c3102288a09d8acabb7a39d0972..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/filters.py
+++ /dev/null
@@ -1,178 +0,0 @@
-from collections.abc import Sequence
-
-from django.db.models import Count, Q
-from django.forms import RadioSelect
-from django.utils.translation import gettext as _
-
-from django_filters import BooleanFilter, FilterSet, ModelMultipleChoiceFilter
-from django_select2.forms import ModelSelect2MultipleWidget
-from material import Layout, Row
-
-from aleksis.core.models import Group, Person, Room, SchoolTerm
-
-from .models import Break, Subject, SupervisionArea, TimePeriod
-
-
-class MultipleModelMultipleChoiceFilter(ModelMultipleChoiceFilter):
-    """Filter for filtering multiple fields with one input.
-
-    >>> multiple_filter = MultipleModelMultipleChoiceFilter(["room", "substitution_room"])
-    """
-
-    def filter(self, qs, value):  # noqa
-        if not value:
-            return qs
-
-        if self.is_noop(qs, value):
-            return qs
-
-        q = Q()
-        for v in set(value):
-            if v == self.null_value:
-                v = None
-            for field in self.lookup_fields:
-                q = q | Q(**{field: v})
-
-            qs = self.get_method(qs)(q)
-
-        return qs.distinct() if self.distinct else qs
-
-    def __init__(self, lookup_fields: Sequence[str], *args, **kwargs):
-        self.lookup_fields = lookup_fields
-        super().__init__(self, *args, **kwargs)
-
-
-class LessonPeriodFilter(FilterSet):
-    period = ModelMultipleChoiceFilter(queryset=TimePeriod.objects.all())
-    lesson__groups = ModelMultipleChoiceFilter(
-        queryset=Group.objects.all(),
-        widget=ModelSelect2MultipleWidget(
-            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            search_fields=[
-                "name__icontains",
-                "short_name__icontains",
-            ],
-        ),
-    )
-    room = MultipleModelMultipleChoiceFilter(
-        ["room", "current_substitution__room"],
-        queryset=Room.objects.all(),
-        label=_("Room"),
-        widget=ModelSelect2MultipleWidget(
-            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            search_fields=[
-                "name__icontains",
-                "short_name__icontains",
-            ],
-        ),
-    )
-    lesson__teachers = MultipleModelMultipleChoiceFilter(
-        ["lesson__teachers", "current_substitution__teachers"],
-        queryset=Person.objects.none(),
-        label=_("Teachers"),
-        widget=ModelSelect2MultipleWidget(
-            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            search_fields=[
-                "first_name__icontains",
-                "last_name__icontains",
-                "short_name__icontains",
-            ],
-        ),
-    )
-    lesson__subject = MultipleModelMultipleChoiceFilter(
-        ["lesson__subject", "current_substitution__subject"],
-        queryset=Subject.objects.all(),
-        label=_("Subject"),
-        widget=ModelSelect2MultipleWidget(
-            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            search_fields=[
-                "name__icontains",
-                "short_name__icontains",
-            ],
-        ),
-    )
-    substituted = BooleanFilter(
-        field_name="current_substitution",
-        label=_("Substitution status"),
-        lookup_expr="isnull",
-        exclude=True,
-        widget=RadioSelect(
-            choices=[
-                ("", _("All lessons")),
-                (True, _("Substituted")),
-                (False, _("Not substituted")),
-            ]
-        ),
-    )
-
-    def __init__(self, *args, **kwargs):
-        weekday = kwargs.pop("weekday")
-        super().__init__(*args, **kwargs)
-        self.filters["period"].queryset = TimePeriod.objects.filter(weekday=weekday)
-        self.filters["lesson__teachers"].queryset = (
-            Person.objects.annotate(
-                lessons_count=Count(
-                    "lessons_as_teacher",
-                    filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
-                    if SchoolTerm.current
-                    else Q(),
-                )
-            )
-            .filter(lessons_count__gt=0)
-            .order_by("short_name", "last_name")
-        )
-        self.form.layout = Layout(
-            Row("period", "lesson__groups", "room"),
-            Row("lesson__teachers", "lesson__subject", "substituted"),
-        )
-
-
-class SupervisionFilter(FilterSet):
-    break_item = ModelMultipleChoiceFilter(queryset=Break.objects.all())
-    area = ModelMultipleChoiceFilter(queryset=SupervisionArea.objects.all())
-    teacher = MultipleModelMultipleChoiceFilter(
-        ["teacher", "current_substitution__teacher"],
-        queryset=Person.objects.none(),
-        label=_("Teacher"),
-        widget=ModelSelect2MultipleWidget(
-            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            search_fields=[
-                "first_name__icontains",
-                "last_name__icontains",
-                "short_name__icontains",
-            ],
-        ),
-    )
-    substituted = BooleanFilter(
-        field_name="current_substitution",
-        label=_("Substitution status"),
-        lookup_expr="isnull",
-        exclude=True,
-        widget=RadioSelect(
-            choices=[
-                ("", _("All supervisions")),
-                (True, _("Substituted")),
-                (False, _("Not substituted")),
-            ]
-        ),
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.filters["break_item"].queryset = Break.objects.filter(supervisions__in=self.queryset)
-        self.filters["teacher"].queryset = (
-            Person.objects.annotate(
-                lessons_count=Count(
-                    "lessons_as_teacher",
-                    filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current)
-                    if SchoolTerm.current
-                    else Q(),
-                )
-            )
-            .filter(lessons_count__gt=0)
-            .order_by("short_name", "last_name")
-        )
-        self.form.layout = Layout(
-            Row("break_item", "area"),
-            Row("teacher", "substituted"),
-        )
diff --git a/aleksis/apps/chronos/forms.py b/aleksis/apps/chronos/forms.py
deleted file mode 100644
index 58664a45ecae7792e20b086450388aeecc0b0e24..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/forms.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from django import forms
-
-from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget
-from material import Layout
-
-from .models import AutomaticPlan, LessonSubstitution, SupervisionSubstitution
-from .util.chronos_helpers import get_teachers
-
-
-class LessonSubstitutionForm(forms.ModelForm):
-    """Form to manage substitutions."""
-
-    class Meta:
-        model = LessonSubstitution
-        fields = ["subject", "teachers", "room", "cancelled", "comment"]
-        widgets = {
-            "teachers": ModelSelect2MultipleWidget(
-                search_fields=[
-                    "first_name__icontains",
-                    "last_name__icontains",
-                    "short_name__icontains",
-                ],
-                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            ),
-        }
-
-    def __init__(self, request, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.request = request
-        self.fields["teachers"].queryset = get_teachers(request.user)
-
-
-class SupervisionSubstitutionForm(forms.ModelForm):
-    """Form to manage supervisions substitutions."""
-
-    class Meta:
-        model = SupervisionSubstitution
-        fields = ["teacher"]
-        widgets = {
-            "teacher": ModelSelect2Widget(
-                search_fields=[
-                    "first_name__icontains",
-                    "last_name__icontains",
-                    "short_name__icontains",
-                ],
-                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
-            ),
-        }
-
-    def __init__(self, request, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.request = request
-        self.fields["teacher"].queryset = get_teachers(request.user)
-
-
-class AutomaticPlanForm(forms.ModelForm):
-    layout = Layout("slug", "name", "number_of_days", "show_header_box")
-
-    class Meta:
-        model = AutomaticPlan
-        fields = ["slug", "name", "number_of_days", "show_header_box"]
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 7f01d7f67c9bff1458804e93a538daa340e014b3..c83c6db038c96e4c7d7221897076ce6c6b162313 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -14,7 +14,6 @@ from django.db import models
 from django.db.models import Max, Min, Q, QuerySet
 from django.db.models.functions import Coalesce
 from django.dispatch import receiver
-from django.forms import Media
 from django.http import HttpRequest
 from django.template.loader import render_to_string
 from django.urls import reverse
@@ -66,7 +65,7 @@ from aleksis.core.mixins import (
     GlobalPermissionModel,
     SchoolTermRelatedExtensibleModel,
 )
-from aleksis.core.models import CalendarEvent, DashboardWidget, Group, Person, Room, SchoolTerm
+from aleksis.core.models import CalendarEvent, Group, Person, Room, SchoolTerm
 from aleksis.core.util.core_helpers import get_site_preferences, has_person
 
 
@@ -618,45 +617,6 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
         verbose_name_plural = _("Lesson periods")
 
 
-class TimetableWidget(DashboardWidget):
-    template = "chronos/widget.html"
-
-    def get_context(self, request):
-        from aleksis.apps.chronos.util.build import build_timetable  # noqa
-
-        context = {"has_plan": True}
-        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
-
-        if has_person(request.user):
-            person = request.user.person
-            type_ = person.timetable_type
-
-            # Build timetable
-            timetable = build_timetable("person", person, wanted_day)
-
-            if type_ is None:
-                # If no student or teacher, redirect to all timetables
-                context["has_plan"] = False
-            else:
-                context["timetable"] = timetable
-                context["holiday"] = Holiday.on_day(wanted_day)
-                context["type"] = type_
-                context["day"] = wanted_day
-                context["periods"] = TimePeriod.get_times_dict()
-                context["smart"] = True
-        else:
-            context["has_plan"] = False
-
-        return context
-
-    media = Media(css={"all": ("css/chronos/timetable.css",)})
-
-    class Meta:
-        proxy = True
-        verbose_name = _("Timetable widget")
-        verbose_name_plural = _("Timetable widgets")
-
-
 class AbsenceReason(ExtensibleModel):
     short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True)
     name = models.CharField(verbose_name=_("Name"), blank=True, max_length=255)
diff --git a/aleksis/apps/chronos/tables.py b/aleksis/apps/chronos/tables.py
deleted file mode 100644
index 7a60b1f6254cfef5bfaf2a415c5bfcff58f6156c..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/tables.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from __future__ import annotations
-
-from django.utils.html import format_html
-from django.utils.translation import gettext_lazy as _
-
-import django_tables2 as tables
-from django_tables2.utils import A, Accessor
-
-from .models import LessonPeriod, Supervision
-
-
-def _title_attr_from_lesson_or_supervision_state(
-    record: LessonPeriod | Supervision | None = None,
-    table: LessonsTable | SupervisionsTable | None = None,
-) -> str:
-    """Return HTML title depending on lesson or supervision state."""
-    if record.get_substitution():
-        if hasattr(record.get_substitution(), "cancelled") and record.get_substitution().cancelled:
-            return _("Lesson cancelled")
-        else:
-            return _("Substituted")
-    else:
-        return ""
-
-
-class SubstitutionColumn(tables.Column):
-    def render(self, value, record: LessonPeriod | Supervision | None = None):
-        if record.get_substitution():
-            return (
-                format_html(
-                    "<s>{}</s> → {}",
-                    value,
-                    self.substitution_accessor.resolve(record.get_substitution()),
-                )
-                if self.substitution_accessor.resolve(record.get_substitution())
-                else format_html(
-                    "<s>{}</s>",
-                    value,
-                )
-            )
-        return value
-
-    def __init__(self, *args, **kwargs):
-        self.substitution_accessor = Accessor(kwargs.pop("substitution_accessor"))
-        super().__init__(*args, **kwargs)
-
-
-class LessonStatusColumn(tables.Column):
-    def render(self, record: LessonPeriod | Supervision | None = None):
-        if record.get_substitution():
-            return (
-                format_html(
-                    '<span class="new badge green">{}</span>',
-                    _("cancelled"),
-                )
-                if hasattr(record.get_substitution(), "cancelled")
-                and record.get_substitution().cancelled
-                else format_html(
-                    '<span class="new badge orange">{}</span>',
-                    _("substituted"),
-                )
-            )
-        return ""
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-
-class LessonsTable(tables.Table):
-    """Table for daily lessons and management of substitutions."""
-
-    class Meta:
-        attrs = {"class": "highlight, striped"}
-        row_attrs = {
-            "title": _title_attr_from_lesson_or_supervision_state,
-        }
-
-    period__period = tables.Column(accessor="period__period")
-    lesson__groups = tables.Column(accessor="lesson__group_names", verbose_name=_("Groups"))
-    status = LessonStatusColumn(verbose_name=_("Status"), empty_values=())
-    lesson__teachers = SubstitutionColumn(
-        accessor="lesson__teacher_names",
-        substitution_accessor="teacher_names",
-        verbose_name=_("Teachers"),
-    )
-    lesson__subject = SubstitutionColumn(
-        accessor="lesson__subject", substitution_accessor="subject"
-    )
-    room = SubstitutionColumn(accessor="room", substitution_accessor="room")
-    edit_substitution = tables.LinkColumn(
-        "edit_substitution",
-        args=[A("id"), A("_week")],
-        text=_("Substitution"),
-        attrs={"a": {"class": "btn-flat waves-effect waves-orange"}},
-        verbose_name=_("Manage substitution"),
-    )
-
-
-class SupervisionsTable(tables.Table):
-    """Table for daily supervisions and management of substitutions."""
-
-    class Meta:
-        attrs = {"class": "highlight, striped"}
-        row_attrs = {
-            "title": _title_attr_from_lesson_or_supervision_state,
-        }
-
-    break_item = tables.Column(accessor="break_item")
-    status = LessonStatusColumn(verbose_name=_("Status"), empty_values=())
-    area = tables.Column(accessor="area")
-    teacher = SubstitutionColumn(
-        accessor="teacher",
-        substitution_accessor="teacher",
-        verbose_name=_("Teachers"),
-    )
-    edit_substitution = tables.LinkColumn(
-        "edit_supervision_substitution",
-        args=[A("id"), A("_week")],
-        text=_("Substitution"),
-        attrs={"a": {"class": "btn-flat waves-effect waves-orange"}},
-        verbose_name=_("Manage substitution"),
-    )
diff --git a/aleksis/apps/chronos/templates/chronos/all.html b/aleksis/apps/chronos/templates/chronos/all.html
deleted file mode 100644
index 01b9770567f0458913db85cabc12bd31976518e5..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/all.html
+++ /dev/null
@@ -1,59 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends 'core/base.html' %}
-
-{% load i18n static %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}All timetables{% endblocktrans %}{% endblock %}
-{% block page_title %}{% trans "All timetables" %}{% endblock %}
-
-{% block content %}
-  <div class="row">
-    <div class="col s12 m4">
-      <h2>{% trans "Teachers" %}</h2>
-
-      {% for teacher in teachers %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'teacher' teacher.pk %}">
-          {{ teacher.short_name }}
-        </a>
-      {% empty %}
-        {% trans 'No teachers timetables available.' as message %}
-        {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %}
-      {% endfor %}
-    </div>
-
-    <div class="col s12 m4">
-      <h2>{% trans "Groups" %}</h2>
-
-      {% for class in classes %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'group' class.pk %}">
-          {{ class.short_name }}
-        </a>
-      {% empty %}
-        {% trans 'No group timetables available.' as message %}
-        {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %}
-      {% endfor %}
-    </div>
-
-    <div class="col s12 m4">
-      <h2>{% trans "Rooms" %}</h2>
-
-      {% for room in rooms %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'room' room.pk %}">
-          {{ room.short_name }}
-        </a>
-      {% empty %}
-        {% trans 'No room timetables available.' as message %}
-        {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %}
-      {% endfor %}
-    </div>
-  </div>
-
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/edit_substitution.html b/aleksis/apps/chronos/templates/chronos/edit_substitution.html
deleted file mode 100644
index 297d70139684f71d17b43f3bb4b4c875c985b9a7..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/edit_substitution.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load material_form i18n any_js %}
-
-{% block extra_head %}
-    {{ edit_substitution_form.media.css }}
-    {% include_css "select2-materialize" %}
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Edit substitution.{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Edit substitution{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  <p class="flow-text">{{ date }}: {{ lesson_period }}</p>
-  <form method="post">
-    {% csrf_token %}
-
-    {% form form=edit_substitution_form %}{% endform %}
-
-    {% include "core/partials/save_button.html" %}
-    {% if substitution %}
-      <a href="{% url 'delete_substitution' substitution.lesson_period.id substitution.week %}"
-         class="btn red waves-effect waves-light">
-        <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i> {% trans "Delete" %}
-      </a>
-    {% endif %}
-  </form>
-  {% include_js "select2-materialize" %}
-  {{ edit_substitution_form.media.js }}
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html b/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html
deleted file mode 100644
index 2ee637e87502a7e9bb45e7894e7bdbf70f056a5e..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load material_form i18n any_js %}
-
-{% block extra_head %}
-    {{ edit_supervision_substitution_form.media.css }}
-    {% include_css "select2-materialize" %}
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Edit substitution.{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Edit substitution{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  <p class="flow-text">{{ date }}: {{ supervision }}</p>
-  <form method="post">
-    {% csrf_token %}
-
-    {% form form=edit_supervision_substitution_form %}{% endform %}
-
-    {% include "core/partials/save_button.html" %}
-    {% if substitution %}
-      <a href="{% url 'delete_supervision_substitution' substitution.supervision.id week %}"
-         class="btn red waves-effect waves-light">
-        <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i> {% trans "Delete" %}
-      </a>
-    {% endif %}
-  </form>
-  {% include_js "select2-materialize" %}
-  {{ edit_supervision_substitution_form.media.js }}
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/lessons_day.html b/aleksis/apps/chronos/templates/chronos/lessons_day.html
deleted file mode 100644
index e8d0662fd9e16cff185bbd2fdca78cfdd5504fbf..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/lessons_day.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load i18n material_form any_js %}
-
-
-{% load render_table from django_tables2 %}
-
-{% block extra_head %}
-  {{ lesson_periods_filter.form.media.css }}
-  {% include_css "select2-materialize" %}
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Lessons{% endblocktrans %}{% endblock %}
-{% block no_page_title %}{% endblock %}
-
-{% block content %}
-  <script type="text/javascript">
-    var dest = Urls.lessonsDay();
-  </script>
-
-  <h2>{% trans "Filter lessons" %}</h2>
-  <form method="get">
-    {% form form=lesson_periods_filter.form %}{% endform %}
-    {% trans "Search" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="mdi:search" %}
-  </form>
-
-  <div class="row no-margin">
-    <div class="col s12 m6 l8 no-padding">
-      <h1>{% blocktrans %}Lessons{% endblocktrans %} {{ day|date:"l" }}, {{ day }}</h1>
-    </div>
-    <div class="col s12 m6 l4 no-padding">
-      {% include "chronos/partials/datepicker.html" %}
-    </div>
-  </div>
-
-  {% render_table lessons_table %}
-
-  {% include_js "select2-materialize" %}
-  {{ lesson_periods_filter.form.media.js }}
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/my_timetable.html b/aleksis/apps/chronos/templates/chronos/my_timetable.html
deleted file mode 100644
index 1408ec233161f4e14c2022a34447526f7cb3dbb2..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/my_timetable.html
+++ /dev/null
@@ -1,66 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends 'core/base.html' %}
-
-{% load i18n static %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}My timetable{% endblocktrans %}{% endblock %}
-{% block no_page_title %}{% endblock %}
-
-{% block content %}
-  <div class="row no-margin">
-    <div class="col s12">
-      <h1>
-        {% trans "My timetable" %} <i>{{ el }}</i>
-        <span class="badge new primary-color ">{% trans "SMART PLAN" %}</span>
-      </h1>
-      <a class="btn-flat waves-effect waves-light" href="{% url "timetable" super.type.value super.el.pk %}">
-        {% trans "Show week timetable for" %} {{ super.el.short_name }}
-      </a>
-    </div>
-  </div>
-
-  <div class="row nomargin hide-on-large-only">
-    <div class="col m12 s12 l6 xl4">
-      {% include "core/partials/announcements.html" with announcements=announcements %}
-    </div>
-  </div>
-
-  <div class="row nomargin hide-on-large-med-and-down">
-    <div class="col m12 s12 l6 xl4">
-      {% include "core/partials/announcements.html" with announcements=week_announcements %}
-    </div>
-  </div>
-
-    <div class="row">
-    <div class="col s12">
-      <div class="card timetable-title-card">
-        <div class="card-content">
-            <span class="card-title">
-                {% include "chronos/partials/datepicker.html" with display_date_only=1 %}
-                <span class="show-on-medium-and-down hide-on-large-only">
-                {% if weekday.date == today %}
-                    <br/> {% include "chronos/partials/today.html" %}
-                {% endif %}
-                </span>
-            </span>
-        </div>
-      </div>
-
-    </div>
-  </div>
-
-  <div class="row hide-on-large-only">
-    <div class="timetable-plan col s12 m12 xl4">
-      {#  Lessons #}
-      {% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %}
-    </div>
-  </div>
-  <div class="row timetable-plan hide-on-med-and-down">
-      {% include "chronos/partials/week_timetable.html" with timetable=week_timetable active_day=day today=today %}
-  </div>
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/substitutions.html b/aleksis/apps/chronos/templates/chronos/substitutions.html
deleted file mode 100644
index 42a19e63cc3d1620343734fa6880ac2c410061f0..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/substitutions.html
+++ /dev/null
@@ -1,94 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends 'core/base.html' %}
-
-{% load i18n static %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Substitutions{% endblocktrans %}{% endblock %}
-{% block no_page_title %}{% endblock %}
-
-{% block content %}
-  <div class="row no-margin">
-    <div class="col s10 m6 no-padding">
-      <h1>{% trans "Substitutions" %}</h1>
-    </div>
-    <div class="col s2 m6 right align-right print-icon">
-      <a class="waves-effect waves-teal btn-flat btn-flat-medium right"
-         href="{% url "substitutions_print_by_date" day.year day.month day.day %}" target="_blank">
-        <i class="material-icons iconify center" data-icon="mdi:printer-outline"></i>
-      </a>
-    </div>
-  </div>
-
-  <div class="row no-print">
-    <div class="col s12 m6 l8">
-      {% include "chronos/partials/headerbox.html" %}
-
-      {% include "core/partials/announcements.html" with announcements=announcements show_recipients=1 %}
-    </div>
-    <div class="col s12 m6 l4 no-padding">
-      {% include "chronos/partials/datepicker.html" %}
-    </div>
-  </div>
-
-  <h2 class="hide-on-small-and-down">{{ day|date:"l" }}, {{ day }}</h2>
-
-  <div class="table-container">
-    <table class="substitutions striped">
-      <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>{% trans "Teacher" %}</th>
-        <th>{% trans "Subject" %}</th>
-        <th>{% trans "Room" %}</th>
-        <th>{% trans "Notes" %}</th>
-        <th></th>
-      </tr>
-      </thead>
-      <tbody>
-      {% if not substitutions %}
-        <td colspan="7">
-          <p class="flow-text center">
-            {% blocktrans %}No substitutions available.{% endblocktrans %}
-          </p>
-        </td>
-      {% endif %}
-      {% for item in substitutions %}
-        <tr class="{% include "chronos/partials/subs/colour.html" with item=item %}">
-          {# TODO: Extend support for purple (events) #}
-          <td>
-            {% include "chronos/partials/subs/groups.html" with type=item.type el=item.el %}
-          </td>
-          <td>
-            {% include "chronos/partials/subs/period.html" with type=item.type el=item.el item=item %}
-          </td>
-          <td>
-            {% include "chronos/partials/subs/teachers.html" with type=item.type el=item.el %}
-          </td>
-          <td>
-            {% 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 %}
-          </td>
-          <td>
-            <span class="hide-on-med-and-up">
-              {% include "chronos/partials/subs/badge.html" with sub=item.el %}
-            </span>
-            {% include "chronos/partials/subs/comment.html" with el=item.el %}
-          </td>
-          <td class="hide-on-small-and-down">
-            {% include "chronos/partials/subs/badge.html" with sub=item.el %}
-          </td>
-        </tr>
-      {% endfor %}
-      </tbody>
-    </table>
-  </div>
-
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/supervisions_day.html b/aleksis/apps/chronos/templates/chronos/supervisions_day.html
deleted file mode 100644
index 8c28324ea74f5931e3eaff9603f77846c91cd723..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/supervisions_day.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load i18n material_form any_js %}
-
-
-{% load render_table from django_tables2 %}
-
-{% block extra_head %}
-  {{ supervisions_filter.form.media.css }}
-  {% include_css "select2-materialize" %}
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Lessons{% endblocktrans %}{% endblock %}
-{% block no_page_title %}{% endblock %}
-
-{% block content %}
-  <h2>{% trans "Filter supervisions" %}</h2>
-  <form method="get">
-    {% form form=supervisions_filter.form %}{% endform %}
-    {% trans "Search" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="mdi:search" %}
-  </form>
-
-  <div class="row no-margin">
-    <div class="col s12 m6 l8 no-padding">
-      <h1>{% blocktrans %}Supervisions{% endblocktrans %} {{ day|date:"l" }}, {{ day }}</h1>
-    </div>
-    <div class="col s12 m6 l4 no-padding">
-      {% include "chronos/partials/datepicker.html" %}
-    </div>
-  </div>
-
-  {% render_table supervisions_table %}
-
-  {% include_js "select2-materialize" %}
-  {{ supervisions_filter.form.media.js }}
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/timetable.html b/aleksis/apps/chronos/templates/chronos/timetable.html
deleted file mode 100644
index 3e253eba013e9d5873c65f38867d7830394fdb38..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/timetable.html
+++ /dev/null
@@ -1,135 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends 'core/base.html' %}
-
-{% load data_helpers rules static i18n %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Timetable{% endblocktrans %}{% endblock %}
-{% block no_page_title %}{% endblock %}
-{% block content %}
-
-  {% if smart %}
-    <script type="text/javascript" src="{% static "js/helper.js" %}"></script>
-    {{ week_select|json_script:"week_select" }}
-    <script type="text/javascript" src="{% static "js/chronos/week_select.js" %}"></script>
-  {% endif %}
-
-  <div class="row no-margin">
-    <div class="col s8 m6 l8 xl9">
-      <h1>
-        {% trans "Timetable" %} <i>{{ el }}</i>
-      </h1>
-
-      {# Show class teacher and deputy class teacher #}
-      {% if type.value == "group" and el.owners.all %}
-        <h2>{% trans "Group teachers:" %}
-          {% for teacher in el.owners.all %}
-            <span data-position="bottom" class="tooltipped"
-                  data-tooltip="{{ teacher }}">
-                            <a href="{% url "timetable" "teacher" teacher.pk %}">
-                                {{ teacher.short_name }}</a></span>{% if not forloop.last %},{% endif %}
-          {% endfor %}
-        </h2>
-      {% endif %}
-    </div>
-    <div class="col s4 m6 l4 xl3 right align-right no-print">
-      <a class="waves-effect waves-teal btn-flat btn-flat-medium right hide-on-small-and-down" href="{% url "timetable_print" type.value pk %}">
-        <i class="material-icons iconify center" data-icon="mdi:printer-outline"></i>
-      </a>
-    </div>
-  </div>
-  <div class="row">
-    {% if smart %}
-      {# Show if smart #}
-      {# Toggle button to regular and smart plan badge #}
-      <div class="row s12 m6 left">
-        <span class="badge new primary-color left smart-plan-badge">{% trans "SMART PLAN" %}</span>
-
-        <a class="waves-effect waves-light btn-flat no-print"
-           href="{% url "timetable_regular" type.value pk "regular" %}">
-          <i class="material-icons iconify left" data-icon="mdi:play-box-outline"></i>
-          {% trans "Show regular timetable" %}
-        </a>
-
-        {% has_perm "alsijil.view_week_rule" user as can_view_week_view %}
-        {% if is_alsijil_installed and can_view_week_view %}
-          <a class="waves-effect waves-light btn-flat no-print"
-            href="{% url "week_view_by_week" year=week.year week=week.week %}">
-            <i class="material-icons iconify left" data-icon="mdi:book-open"></i>
-            {% trans "View class register of this week" %}
-          </a> 
-        {% endif %}
-      </div>
-
-      {# Week select #}
-      {% include "chronos/partials/week_select.html" with wanted_week=week %}
-
-    {% else %}
-      {# Show if regular #}
-      <a class="waves-effect waves-light btn-flat no-print"
-         href="{% url "timetable" type.value pk %}">
-        <i class="material-icons iconify left" data-icon="mdi:play-box-outline"></i>
-        {% trans "Show SMART PLAN" %}
-      </a>
-
-      {% has_perm "alsijil.view_week_rule" user as can_view_week_view %}
-      {% if is_alsijil_installed and can_view_week_view %}
-        <a class="waves-effect waves-light btn-flat no-print"
-          href="{% url "week_view_by_week" year=week.year week=week.week %}">
-          <i class="material-icons iconify left" data-icon="mdi:book-open"></i>
-          {% trans "View class register of this week" %}
-        </a> 
-      {% endif %}
-    {% endif %}
-  </div>
-
-  {% include "core/partials/announcements.html" with announcements=announcements show_interval=1 %}
-
-  {# show full timetable on tablets, laptops and pcs #}
-  <div class="timetable-plan hide-on-small-and-down">
-    {% include "chronos/partials/week_timetable.html" %}
-  </div>
-
-  {# show 5 seperate ones on mobiles #}
-  <div class="timetable-plan hide-on-med-and-up">
-    {% for weekday in weekdays %}
-      <div class="card timetable-mobile-title-card">
-        <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 %}
-              {% endif %}
-        </div>
-      </div>
-      {% for row in timetable %}
-        <div class="row">
-          <div class="col s4">
-            {% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
-          </div>
-
-          {% for col in row.cols %}
-            {% if forloop.counter0 == weekday.key %}
-              <div class="col s8">
-                {# A lesson #}
-                {% if row.type == "period" %}
-                  {% include "chronos/partials/elements.html" with elements=col %}
-                {% else %}
-                  {% include "chronos/partials/supervision.html" with supervision=col %}
-                {% endif %}
-              </div>
-            {% endif %}
-          {% endfor %}
-        </div>
-      {% endfor %}
-    {% endfor %}
-  </div>
-{% endblock %}
diff --git a/aleksis/apps/chronos/templates/chronos/widget.html b/aleksis/apps/chronos/templates/chronos/widget.html
deleted file mode 100644
index 10d3c19aa14d1ec68a25d2f327d51ab3ef0a341a..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templates/chronos/widget.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% load i18n static humanize %}
-
-<div class="card">
-  <div class="card-content">
-    <span class="card-title">
-      {% blocktrans with day=day|naturalday:"l" %}
-        My timetable for {{ day }}
-      {% endblocktrans %}
-    </span>
-    <div class="timetable-plan">
-      {% if has_plan %}
-        {% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %}
-      {% else %}
-        <figure class="alert warning">
-          <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
-          {% blocktrans %}
-          There is no timetable linked to your person.
-          {% endblocktrans %}
-        </figure>
-      {% endif %}
-    </div>
-  </div>
-  {% if has_plan %}
-    <div class="card-action">
-      <a href="{% url "my_timetable" %}">{% trans "Go to smart plan" %}</a>
-    </div>
-  {% endif %}
-</div>
diff --git a/aleksis/apps/chronos/urls.py b/aleksis/apps/chronos/urls.py
index e4cf26197a1e45fefac0bc16021fc05196ddc109..7d55323042b9a23438975c589bd23967c3cbb396 100644
--- a/aleksis/apps/chronos/urls.py
+++ b/aleksis/apps/chronos/urls.py
@@ -3,78 +3,9 @@ from django.urls import path
 from . import views
 
 urlpatterns = [
-    path("", views.all_timetables, name="all_timetables"),
-    path("timetable/my/", views.my_timetable, name="my_timetable"),
-    path(
-        "timetable/my/<int:year>/<int:month>/<int:day>/",
-        views.my_timetable,
-        name="my_timetable_by_date",
-    ),
-    path("timetable/<str:type_>/<int:pk>/", views.timetable, name="timetable"),
-    path(
-        "timetable/<str:type_>/<int:pk>/<int:year>/<int:week>/",
-        views.timetable,
-        name="timetable_by_week",
-    ),
-    path(
-        "timetable/<str:type_>/<int:pk>/print/",
-        views.timetable,
-        {"is_print": True},
-        name="timetable_print",
-    ),
-    path(
-        "timetable/<str:type_>/<int:pk>/<str:regular>/",
-        views.timetable,
-        name="timetable_regular",
-    ),
-    path("lessons/", views.lessons_day, name="lessons_day"),
-    path(
-        "lessons/<int:year>/<int:month>/<int:day>/",
-        views.lessons_day,
-        name="lessons_day_by_date",
-    ),
-    path(
-        "lessons/<int:id_>/<int:week>/substitution/",
-        views.edit_substitution,
-        name="edit_substitution",
-    ),
-    path(
-        "lessons/<int:id_>/<int:week>/substitution/delete/",
-        views.delete_substitution,
-        name="delete_substitution",
-    ),
-    path("substitutions/", views.substitutions, name="substitutions"),
     path(
         "substitutions/print/",
-        views.substitutions,
-        {"is_print": True},
+        views.substitutions_print,
         name="substitutions_print",
     ),
-    path(
-        "substitutions/<int:year>/<int:month>/<int:day>/",
-        views.substitutions,
-        name="substitutions_by_date",
-    ),
-    path(
-        "substitutions/<int:year>/<int:month>/<int:day>/print/",
-        views.substitutions,
-        {"is_print": True},
-        name="substitutions_print_by_date",
-    ),
-    path("supervisions/", views.supervisions_day, name="supervisions_day"),
-    path(
-        "supervisions/<int:year>/<int:month>/<int:day>/",
-        views.supervisions_day,
-        name="supervisions_day_by_date",
-    ),
-    path(
-        "supervisions/<int:id_>/<int:week>/substitution/",
-        views.edit_supervision_substitution,
-        name="edit_supervision_substitution",
-    ),
-    path(
-        "supervisions/<int:id_>/<int:week>/substitution/delete/",
-        views.delete_supervision_substitution,
-        name="delete_supervision_substitution",
-    ),
 ]
diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py
index 8bd3307977dbe5a625df0223edf7a85044ca5f78..a45997ca580db9f8fa442be52d88054055e2661d 100644
--- a/aleksis/apps/chronos/views.py
+++ b/aleksis/apps/chronos/views.py
@@ -1,468 +1,25 @@
-from datetime import datetime
 from typing import Optional
 
-from django.apps import apps
-from django.db.models import FilteredRelation, Q
-from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
-from django.shortcuts import get_object_or_404, redirect, render
-from django.urls import reverse
-from django.utils import timezone
-from django.utils.translation import gettext as _
-from django.views.decorators.cache import never_cache
+from django.http import HttpRequest, HttpResponse
 
-import reversion
-from django_tables2 import RequestConfig
 from rules.contrib.views import permission_required
 
 from aleksis.core.decorators import pwa_cache
-from aleksis.core.models import Announcement
-from aleksis.core.util import messages
-from aleksis.core.util.core_helpers import has_person
 from aleksis.core.util.pdf import render_pdf
 
-from .filters import LessonPeriodFilter, SupervisionFilter
-from .forms import LessonSubstitutionForm, SupervisionSubstitutionForm
-from .managers import TimetableType
-from .models import Holiday, LessonPeriod, Supervision, TimePeriod
-from .tables import LessonsTable, SupervisionsTable
-from .util.build import build_timetable, build_weekdays
-from .util.change_tracker import TimetableDataChangeTracker
 from .util.chronos_helpers import (
-    get_classes,
-    get_el_by_pk,
-    get_rooms,
-    get_substitution_by_id,
     get_substitutions_context_data,
-    get_supervision_substitution_by_id,
-    get_teachers,
 )
-from .util.date import CalendarWeek, get_weeks_for_year, week_weekday_to_date
-from .util.js import date_unix
-
-
-@pwa_cache
-@permission_required("chronos.view_timetable_overview_rule")
-def all_timetables(request: HttpRequest) -> HttpResponse:
-    """View all timetables for persons, groups and rooms."""
-    context = {}
-
-    user = request.user
-    teachers, classes, rooms = get_teachers(user), get_classes(user), get_rooms(user)
-
-    context["teachers"] = teachers
-    context["classes"] = classes
-    context["rooms"] = rooms
-
-    return render(request, "chronos/all.html", context)
-
-
-@pwa_cache
-@permission_required("chronos.view_my_timetable_rule")
-def my_timetable(
-    request: HttpRequest,
-    year: Optional[int] = None,
-    month: Optional[int] = None,
-    day: Optional[int] = None,
-) -> HttpResponse:
-    """View personal timetable on a specified date."""
-    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(), datetime.now().time())
-
-    wanted_week = CalendarWeek.from_date(wanted_day)
-
-    if has_person(request.user):
-        person = request.user.person
-        type_ = person.timetable_type
-
-        # Build timetable
-        timetable = build_timetable("person", person, wanted_day)
-        week_timetable = build_timetable("person", person, wanted_week)
-
-        if type_ is None:
-            # If no student or teacher, redirect to all timetables
-            return redirect("all_timetables")
-
-        super_el = person.timetable_object
-
-        context["timetable"] = timetable
-        context["week_timetable"] = week_timetable
-        context["holiday"] = Holiday.on_day(wanted_day)
-        context["super"] = {"type": type_, "el": super_el}
-        context["type"] = type_
-        context["day"] = wanted_day
-        context["today"] = timezone.now().date()
-        context["week"] = wanted_week
-        context["periods"] = TimePeriod.get_times_dict()
-        context["smart"] = True
-        context["announcements"] = (
-            Announcement.for_timetables().on_date(wanted_day).for_person(person)
-        )
-        context["week_announcements"] = (
-            Announcement.for_timetables()
-            .within_days(wanted_week[0], wanted_week[6])
-            .for_person(person)
-        )
-        context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week)
-        context["weekdays_short"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week)
-        context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
-            wanted_day, "my_timetable_by_date"
-        )
-
-        return render(request, "chronos/my_timetable.html", context)
-    else:
-        return redirect("all_timetables")
-
-
-@pwa_cache
-@permission_required("chronos.view_timetable_rule", fn=get_el_by_pk)
-def timetable(
-    request: HttpRequest,
-    type_: str,
-    pk: int,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    regular: Optional[str] = None,
-    is_print: bool = False,
-) -> HttpResponse:
-    """View a selected timetable for a person, group or room."""
-    context = {}
-
-    is_smart = regular != "regular"
-
-    if is_print:
-        is_smart = False
-
-    el = get_el_by_pk(request, type_, pk, prefetch=True)
-
-    if isinstance(el, HttpResponseNotFound):
-        return HttpResponseNotFound()
-
-    type_ = TimetableType.from_string(type_)
-
-    if year and week:
-        wanted_week = CalendarWeek(year=year, week=week)
-    else:
-        wanted_week = TimePeriod.get_relevant_week_from_datetime()
-
-    # Build timetable
-    timetable = build_timetable(type_, el, wanted_week, with_holidays=is_smart)
-    context["timetable"] = timetable
-
-    # Add time periods
-    context["periods"] = TimePeriod.get_times_dict()
-
-    # Build lists with weekdays and corresponding dates (long and short variant)
-    context["weekdays"] = build_weekdays(
-        TimePeriod.WEEKDAY_CHOICES, wanted_week, with_holidays=is_smart
-    )
-    context["weekdays_short"] = build_weekdays(
-        TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week, with_holidays=is_smart
-    )
-
-    context["weeks"] = get_weeks_for_year(year=wanted_week.year)
-    context["week"] = wanted_week
-    context["type"] = type_
-    context["pk"] = pk
-    context["el"] = el
-    context["smart"] = is_smart
-    context["week_select"] = {
-        "year": wanted_week.year,
-        "dest": reverse(
-            "timetable_by_week",
-            args=[type_.value, pk, wanted_week.year, wanted_week.week],
-        )[::-1]
-        .replace(str(wanted_week.week)[::-1], "cw"[::-1], 1)
-        .replace(str(wanted_week.year)[::-1], "year"[::-1], 1)[::-1],
-    }
-
-    if is_smart:
-        start = wanted_week[TimePeriod.weekday_min]
-        stop = wanted_week[TimePeriod.weekday_max]
-        context["announcements"] = (
-            Announcement.for_timetables().relevant_for(el).within_days(start, stop)
-        )
-
-    week_prev = wanted_week - 1
-    week_next = wanted_week + 1
-
-    context["url_prev"] = reverse(
-        "timetable_by_week", args=[type_.value, pk, week_prev.year, week_prev.week]
-    )
-    context["url_next"] = reverse(
-        "timetable_by_week", args=[type_.value, pk, week_next.year, week_next.week]
-    )
-
-    if apps.is_installed("aleksis.apps.alsijil"):
-        context["is_alsijil_installed"] = True
-
-    if is_print:
-        context["back_url"] = reverse(
-            "timetable_by_week",
-            args=[type_.value, pk, wanted_week.year, wanted_week.week],
-        )
-        return render_pdf(request, "chronos/timetable_print.html", context)
-    else:
-        return render(request, "chronos/timetable.html", context)
-
-
-@pwa_cache
-@permission_required("chronos.view_lessons_day_rule")
-def lessons_day(
-    request: HttpRequest,
-    year: Optional[int] = None,
-    month: Optional[int] = None,
-    day: Optional[int] = None,
-) -> HttpResponse:
-    """View all lessons taking place on a specified day."""
-    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(), datetime.now().time())
-
-    # Get lessons
-    lesson_periods = LessonPeriod.objects.on_day(wanted_day)
-
-    # Get filter
-    lesson_periods_filter = LessonPeriodFilter(
-        request.GET,
-        queryset=lesson_periods.annotate(
-            current_substitution=FilteredRelation(
-                "substitutions",
-                condition=(
-                    Q(substitutions__week=wanted_day.isocalendar()[1], substitutions__year=year)
-                ),
-            )
-        ),
-        weekday=wanted_day.weekday(),
-    )
-    context["lesson_periods_filter"] = lesson_periods_filter
-
-    # Build table
-    lessons_table = LessonsTable(lesson_periods_filter.qs)
-    RequestConfig(request).configure(lessons_table)
-
-    context["lessons_table"] = lessons_table
-    context["day"] = wanted_day
-    context["lesson_periods"] = lesson_periods
-
-    context["datepicker"] = {
-        "date": date_unix(wanted_day),
-        "dest": reverse("lessons_day"),
-    }
-
-    context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
-        wanted_day, "lessons_day_by_date"
-    )
-
-    return render(request, "chronos/lessons_day.html", context)
-
-
-@never_cache
-@permission_required("chronos.edit_substitution_rule", fn=get_substitution_by_id)
-def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
-    """View a form to edit a substitution lessen."""
-    context = {}
-
-    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
-    wanted_week = lesson_period.lesson.get_calendar_week(week)
-    context["lesson_period"] = lesson_period
-    day = week_weekday_to_date(wanted_week, lesson_period.period.weekday)
-    context["date"] = day
-
-    lesson_substitution = get_substitution_by_id(request, id_, week)
-
-    if lesson_substitution:
-        edit_substitution_form = LessonSubstitutionForm(
-            request, request.POST or None, instance=lesson_substitution
-        )
-    else:
-        edit_substitution_form = LessonSubstitutionForm(
-            request,
-            request.POST or None,
-        )
-
-    context["substitution"] = lesson_substitution
-
-    if request.method == "POST" and edit_substitution_form.is_valid():
-        with reversion.create_revision(atomic=True):
-            TimetableDataChangeTracker()
-
-            lesson_substitution = edit_substitution_form.save(commit=False)
-            if not lesson_substitution.pk:
-                lesson_substitution.lesson_period = lesson_period
-                lesson_substitution.week = wanted_week.week
-                lesson_substitution.year = wanted_week.year
-            lesson_substitution.save()
-            edit_substitution_form.save_m2m()
-
-            messages.success(request, _("The substitution has been saved."))
-
-        return redirect("lessons_day_by_date", year=day.year, month=day.month, day=day.day)
-
-    context["edit_substitution_form"] = edit_substitution_form
-
-    return render(request, "chronos/edit_substitution.html", context)
-
-
-@permission_required("chronos.delete_substitution_rule", fn=get_substitution_by_id)
-def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
-    """Delete a substitution lesson.
-
-    Redirects back to substition list on success.
-    """
-    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
-    wanted_week = lesson_period.lesson.get_calendar_week(week)
-
-    get_substitution_by_id(request, id_, week).delete()
-
-    messages.success(request, _("The substitution has been deleted."))
-
-    date = wanted_week[lesson_period.period.weekday]
-    return redirect("lessons_day_by_date", year=date.year, month=date.month, day=date.day)
 
 
 @pwa_cache
 @permission_required("chronos.view_substitutions_rule")
-def substitutions(
+def substitutions_print(
     request: HttpRequest,
     year: Optional[int] = None,
     month: Optional[int] = None,
     day: Optional[int] = None,
-    is_print: bool = False,
 ) -> HttpResponse:
     """View all substitutions on a specified day."""
-    context = get_substitutions_context_data(request, year, month, day, is_print)
-    if not is_print:
-        return render(request, "chronos/substitutions.html", context)
-    else:
-        return render_pdf(request, "chronos/substitutions_print.html", context)
-
-
-@pwa_cache
-@permission_required("chronos.view_supervisions_day_rule")
-def supervisions_day(
-    request: HttpRequest,
-    year: Optional[int] = None,
-    month: Optional[int] = None,
-    day: Optional[int] = None,
-) -> HttpResponse:
-    """View all supervisions taking place on a specified day."""
-    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(), datetime.now().time())
-
-    # Get supervisions
-    supervisions = (
-        Supervision.objects.on_day(wanted_day)
-        .filter_by_weekday(wanted_day.weekday())
-        .order_by("break_item__before_period__period")
-    )
-
-    # Get filter
-    supervisions_filter = SupervisionFilter(
-        request.GET,
-        queryset=supervisions.annotate(
-            current_substitution=FilteredRelation(
-                "substitutions",
-                condition=(Q(substitutions__date=wanted_day)),
-            )
-        ),
-    )
-    context["supervisions_filter"] = supervisions_filter
-
-    # Build table
-    supervisions_table = SupervisionsTable(
-        supervisions_filter.qs.annotate_week(week=CalendarWeek.from_date(wanted_day))
-    )
-    RequestConfig(request).configure(supervisions_table)
-
-    context["supervisions_table"] = supervisions_table
-    context["day"] = wanted_day
-    context["supervisions"] = supervisions
-
-    context["datepicker"] = {
-        "date": date_unix(wanted_day),
-        "dest": reverse("supervisions_day"),
-    }
-
-    context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
-        wanted_day, "supervisions_day_by_date"
-    )
-
-    return render(request, "chronos/supervisions_day.html", context)
-
-
-@never_cache
-@permission_required("chronos.edit_supervision_substitution_rule")
-def edit_supervision_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
-    """View a form to edit a supervision substitution."""
-    context = {}
-
-    supervision = get_object_or_404(Supervision, pk=id_)
-    wanted_week = supervision.get_calendar_week(week)
-    context["week"] = week
-    context["supervision"] = supervision
-    date = week_weekday_to_date(wanted_week, supervision.break_item.weekday)
-    context["date"] = date
-
-    supervision_substitution = get_supervision_substitution_by_id(request, id_, date)
-
-    if supervision_substitution:
-        edit_supervision_substitution_form = SupervisionSubstitutionForm(
-            request, request.POST or None, instance=supervision_substitution
-        )
-    else:
-        edit_supervision_substitution_form = SupervisionSubstitutionForm(
-            request,
-            request.POST or None,
-        )
-
-    context["substitution"] = supervision_substitution
-
-    if request.method == "POST" and edit_supervision_substitution_form.is_valid():
-        with reversion.create_revision(atomic=True):
-            TimetableDataChangeTracker()
-
-            supervision_substitution = edit_supervision_substitution_form.save(commit=False)
-            if not supervision_substitution.pk:
-                supervision_substitution.supervision = supervision
-                supervision_substitution.date = date
-            supervision_substitution.save()
-            edit_supervision_substitution_form.save_m2m()
-
-            messages.success(request, _("The substitution has been saved."))
-
-        return redirect("supervisions_day_by_date", year=date.year, month=date.month, day=date.day)
-
-    context["edit_supervision_substitution_form"] = edit_supervision_substitution_form
-
-    return render(request, "chronos/edit_supervision_substitution.html", context)
-
-
-@permission_required("chronos.delete_supervision_substitution_rule")
-def delete_supervision_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
-    """Delete a supervision substitution.
-
-    Redirects back to supervision list on success.
-    """
-    supervision = get_object_or_404(Supervision, pk=id_)
-    wanted_week = supervision.get_calendar_week(week)
-    date = week_weekday_to_date(wanted_week, supervision.break_item.weekday)
-
-    get_supervision_substitution_by_id(request, id_, date).delete()
-
-    messages.success(request, _("The substitution has been deleted."))
-
-    return redirect("supervisions_day_by_date", year=date.year, month=date.month, day=date.day)
+    context = get_substitutions_context_data(request, year, month, day, is_print=True)
+    return render_pdf(request, "chronos/substitutions_print.html", context)