diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5389b8747a7f76495756ad47fda11d9e410319cf..902a10933a40914e7119fec6733cbf139cd7f1d2 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,15 +6,35 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
-Unreleased
-----------
+`4.0.0.dev8`_ - 2024-10-17
+--------------------------
+
+Upgrade notice
+~~~~~~~~~~~~~~
+
+If you're updating from 3.x, there is a migration path to use.
+Therefore, please install ``AlekSIS-App-Lesrooster`` which now
+includes parts of the legacy Chronos and the migration path.
 
 Added
 ~~~~~
 
 * New timetable interface based on calendar system.
+* Dialog for fast changing lessons and creating substitutions in the calendar. 
 * [Dev] LessonEvent and SupervisionEvent basing on calendar system.
 
+Changed
+~~~~~~~
+
+* Substitution table was updated to new frontend.
+* Substitution PDF was updated to new timetable interface and 
+  therefore has a slightly different look.
+
+Removed
+~~~~~~~
+
+* Legacy timetable frontend.
+
 `3.0.2`_ - 2023-09-10
 ---------------------
 
@@ -411,3 +431,4 @@ Fixed
 .. _3.0: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0
 .. _3.0.1: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0.1
 .. _3.0.2: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0.2
+.. _4.0.0.dev8: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/4.0.0.dev8
diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py
index 9da4770d092663c4baa2c5a9e770677305ea0121..2ed048d96d6cdb40a440e22a4a3fcb421b330013 100644
--- a/aleksis/apps/chronos/admin.py
+++ b/aleksis/apps/chronos/admin.py
@@ -1,218 +1,9 @@
 # noqa
 
 from django.contrib import admin
-from django.utils.html import format_html
-
-from guardian.admin import GuardedModelAdmin
-
-from aleksis.core.models import Room
 
 from .models import (
-    Absence,
-    AbsenceReason,
     AutomaticPlan,
-    Break,
-    Event,
-    ExtraLesson,
-    Holiday,
-    Lesson,
-    LessonPeriod,
-    LessonSubstitution,
-    Subject,
-    Supervision,
-    SupervisionArea,
-    SupervisionSubstitution,
-    TimePeriod,
-    ValidityRange,
 )
-from .util.format import format_date_period, format_m2m
-
-
-def colour_badge(fg: str, bg: str, val: str):
-    html = """
-    <div style="
-        color: {};
-        background-color: {};
-        padding-top: 3px;
-        padding-bottom: 4px;
-        text-align: center;
-        border-radius: 3px;
-    ">{}</span>
-    """
-    return format_html(html, fg, bg, val)
-
-
-class AbsenceReasonAdmin(admin.ModelAdmin):
-    list_display = ("short_name", "name")
-    list_display_links = ("short_name", "name")
-
-
-admin.site.register(AbsenceReason, AbsenceReasonAdmin)
-
-
-class AbsenceAdmin(admin.ModelAdmin):
-    def start(self, obj):
-        return format_date_period(obj.date_start, obj.period_from)
-
-    def end(self, obj):
-        return format_date_period(obj.date_end, obj.period_to)
-
-    list_display = ("__str__", "reason", "start", "end")
-
-
-admin.site.register(Absence, AbsenceAdmin)
-
-
-class SupervisionInline(admin.TabularInline):
-    model = Supervision
-
-
-class BreakAdmin(admin.ModelAdmin):
-    list_display = ("__str__", "after_period", "before_period")
-    inlines = [SupervisionInline]
-
-
-admin.site.register(Break, BreakAdmin)
-
-
-class EventAdmin(admin.ModelAdmin):
-    def start(self, obj):
-        return format_date_period(obj.date_start, obj.period_from)
-
-    def end(self, obj):
-        return format_date_period(obj.date_end, obj.period_to)
-
-    def _groups(self, obj):
-        return format_m2m(obj.groups)
-
-    def _teachers(self, obj):
-        return format_m2m(obj.teachers)
-
-    def _rooms(self, obj):
-        return format_m2m(obj.rooms)
-
-    filter_horizontal = ("groups", "teachers", "rooms")
-    list_display = ("__str__", "_groups", "_teachers", "_rooms", "start", "end")
-
-
-admin.site.register(Event, EventAdmin)
-
-
-class ExtraLessonAdmin(admin.ModelAdmin):
-    def _groups(self, obj):
-        return format_m2m(obj.groups)
-
-    def _teachers(self, obj):
-        return format_m2m(obj.teachers)
-
-    list_display = ("week", "period", "subject", "_groups", "_teachers", "room")
-
-
-admin.site.register(ExtraLesson, ExtraLessonAdmin)
-
-
-class HolidayAdmin(admin.ModelAdmin):
-    list_display = ("title", "date_start", "date_end")
-
-
-admin.site.register(Holiday, HolidayAdmin)
-
-
-class LessonPeriodInline(admin.TabularInline):
-    model = LessonPeriod
-
-
-class LessonSubstitutionAdmin(admin.ModelAdmin):
-    list_display = ("lesson_period", "week", "date")
-    list_display_links = ("lesson_period", "week", "date")
-    filter_horizontal = ("teachers",)
-
-
-admin.site.register(LessonSubstitution, LessonSubstitutionAdmin)
-
-
-class LessonAdmin(admin.ModelAdmin):
-    def _groups(self, obj):
-        return format_m2m(obj.groups)
-
-    def _teachers(self, obj):
-        return format_m2m(obj.teachers)
-
-    filter_horizontal = ["teachers", "groups"]
-    inlines = [LessonPeriodInline]
-    list_filter = ("subject", "groups", "groups__parent_groups", "teachers")
-    list_display = ("_groups", "subject", "_teachers")
-
-
-admin.site.register(Lesson, LessonAdmin)
-
-
-class RoomAdmin(GuardedModelAdmin):
-    list_display = ("short_name", "name")
-    list_display_links = ("short_name", "name")
-
-
-admin.site.register(Room, RoomAdmin)
-
-
-class SubjectAdmin(admin.ModelAdmin):
-    def _colour(self, obj):
-        return colour_badge(
-            obj.colour_fg,
-            obj.colour_bg,
-            obj.short_name,
-        )
-
-    list_display = ("short_name", "name", "_colour")
-    list_display_links = ("short_name", "name")
-
-
-admin.site.register(Subject, SubjectAdmin)
-
-
-class SupervisionAreaAdmin(admin.ModelAdmin):
-    def _colour(self, obj):
-        return colour_badge(
-            obj.colour_fg,
-            obj.colour_bg,
-            obj.short_name,
-        )
-
-    list_display = ("short_name", "name", "_colour")
-    list_display_links = ("short_name", "name")
-    inlines = [SupervisionInline]
-
-
-admin.site.register(SupervisionArea, SupervisionAreaAdmin)
-
-
-class SupervisionSubstitutionAdmin(admin.ModelAdmin):
-    list_display = ("supervision", "date")
-
-
-admin.site.register(SupervisionSubstitution, SupervisionSubstitutionAdmin)
-
-
-class SupervisionAdmin(admin.ModelAdmin):
-    list_display = ("break_item", "area", "teacher")
-
-
-admin.site.register(Supervision, SupervisionAdmin)
-
-
-class TimePeriodAdmin(admin.ModelAdmin):
-    list_display = ("weekday", "period", "time_start", "time_end")
-    list_display_links = ("weekday", "period")
-
-
-admin.site.register(TimePeriod, TimePeriodAdmin)
-
-
-class ValidityRangeAdmin(admin.ModelAdmin):
-    list_display = ("__str__", "date_start", "date_end")
-    list_display_links = ("__str__", "date_start", "date_end")
-
-
-admin.site.register(ValidityRange, ValidityRangeAdmin)
 
 admin.site.register(AutomaticPlan)
diff --git a/aleksis/apps/chronos/apps.py b/aleksis/apps/chronos/apps.py
index 377cefd6c1a90969c1c6c650da75fad34d3f2ae5..8eeee15eb1aebf7170021a338a22ca7c0cc5db68 100644
--- a/aleksis/apps/chronos/apps.py
+++ b/aleksis/apps/chronos/apps.py
@@ -1,6 +1,3 @@
-from typing import Any, Optional
-
-import django.apps
 from django.db import transaction
 
 from reversion.signals import post_revision_commit
@@ -37,75 +34,3 @@ class ChronosConfig(AppConfig):
             transaction.on_commit(lambda: handle_new_revision.delay(revision.pk))
 
         post_revision_commit.connect(_handle_post_revision_commit, weak=False)
-
-    def _ensure_notification_task(self):
-        """Update or create the task for sending notifications."""
-        from django.conf import settings  # noqa
-
-        from celery import schedules
-        from django_celery_beat.models import CrontabSchedule, PeriodicTask
-
-        from aleksis.core.util.core_helpers import get_site_preferences
-
-        time_for_sending = get_site_preferences()["chronos__time_for_sending_notifications"]
-        active = get_site_preferences()["chronos__send_notifications_site"]
-
-        if active:
-            schedule = schedules.crontab(
-                minute=str(time_for_sending.minute), hour=str(time_for_sending.hour)
-            )
-            schedule = CrontabSchedule.from_schedule(schedule)
-            schedule.timezone = settings.TIME_ZONE
-            schedule.save()
-
-        possible_periodic_tasks = PeriodicTask.objects.filter(
-            task="chronos_send_notifications_for_next_day"
-        )
-
-        if not active:
-            possible_periodic_tasks.delete()
-
-        elif possible_periodic_tasks.exists():
-            task = possible_periodic_tasks[0]
-            for d_task in possible_periodic_tasks:
-                if d_task != task:
-                    d_task.delete()
-
-            if task.crontab != schedule:
-                task.interval, task.solar, task.clocked = None, None, None
-                task.crontab = schedule
-                task.save()
-
-        elif active:
-            PeriodicTask.objects.get_or_create(
-                task="chronos_send_notifications_for_next_day",
-                crontab=schedule,
-                defaults=dict(name="Send notifications for next day (automatic schedule)"),
-            )
-
-    def preference_updated(
-        self,
-        sender: Any,
-        section: Optional[str] = None,
-        name: Optional[str] = None,
-        old_value: Optional[Any] = None,
-        new_value: Optional[Any] = None,
-        **kwargs,
-    ) -> None:
-        if section == "chronos" and name in (
-            "send_notifications_site",
-            "time_for_sending_notifications",
-        ):
-            self._ensure_notification_task()
-
-    def post_migrate(
-        self,
-        app_config: django.apps.AppConfig,
-        verbosity: int,
-        interactive: bool,
-        using: str,
-        **kwargs,
-    ) -> None:
-        super().post_migrate(app_config, verbosity, interactive, using, **kwargs)
-        # Ensure that the notification task is created after setting up AlekSIS
-        self._ensure_notification_task()
diff --git a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
index 84cf21c8498e54de13efd2056117fe478212aae1..f11b2ed2ebc60b72895f3e682c4b66cc024fddad 100644
--- a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
+++ b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
@@ -1,6 +1,9 @@
 <script>
+import Mascot from "aleksis.core/components/generic/mascot/Mascot.vue";
+
 export default {
   name: "NoTimetableCard",
+  components: { Mascot },
   props: {
     titleKey: {
       type: String,
@@ -22,7 +25,7 @@ export default {
     v-bind="$attrs"
   >
     <div class="text-center">
-      <v-icon color="secondary" size="60" class="mb-4"> mdi-grid-off </v-icon>
+      <mascot type="timetable" size="60" class="mb-4" />
       <div class="text-h5 grey--text text--darken-2 mb-2">
         {{ $t(titleKey) }}
       </div>
diff --git a/aleksis/apps/chronos/frontend/components/Substitutions.vue b/aleksis/apps/chronos/frontend/components/Substitutions.vue
new file mode 100644
index 0000000000000000000000000000000000000000..93c6f6dcab78d79b8b24f0cfd39744bb1ee26502
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/Substitutions.vue
@@ -0,0 +1,236 @@
+<script setup>
+import CRUDList from "aleksis.core/components/generic/CRUDList.vue";
+import PrimaryActionButton from "aleksis.core/components/generic/buttons/PrimaryActionButton.vue";
+import PersonChip from "aleksis.core/components/person/PersonChip.vue";
+import GroupChip from "aleksis.core/components/group/GroupChip.vue";
+import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.vue";
+</script>
+
+<template>
+  <c-r-u-d-list
+    :gql-query="query"
+    :gql-additional-query-args="{ date: date }"
+    :get-gql-data="prepareList"
+    :headers="headers"
+    :disable-sort="true"
+    :item-class="itemColor"
+    :show-select="false"
+    :enable-create="false"
+    :enable-edit="false"
+  >
+    <template #title>
+      <v-row class="d-flex align-center pt-2 pa-2">
+        <v-card-title class="text-h4">
+          {{ $d(new Date(date), "dateWithWeekday") }}
+        </v-card-title>
+        <v-spacer />
+        <primary-action-button
+          class="mr-4"
+          i18n-key="chronos.substitutions.print"
+          icon-text="$print"
+          :to="{
+            name: 'chronos.printSubstitutionsForDate',
+            params: {
+              date: date,
+            },
+          }"
+        />
+      </v-row>
+      <v-card-text>
+        <div v-if="affectedTeachers.length > 0">
+          <strong>
+            {{ $t("chronos.substitutions.affected_teachers") }}:
+          </strong>
+          <person-chip
+            v-for="teacher in affectedTeachers"
+            :key="teacher.id"
+            class="ma-1"
+            :person="teacher"
+            :to="{
+              name: 'chronos.timetableWithId',
+              params: {
+                type: 'person',
+                id: teacher.id,
+              },
+            }"
+          />
+        </div>
+        <div v-if="affectedGroups.length > 0">
+          <strong> {{ $t("chronos.substitutions.affected_groups") }}: </strong>
+          <!-- TODO: Link to group-timetable as well -->
+          <!-- as soon as it becomes possible to resolve a -->
+          <!-- group-timetable from the lesson-event group too. -->
+          <group-chip
+            v-for="group in affectedGroups"
+            class="ma-1"
+            :key="group.id"
+            :group="group"
+            format="short"
+          />
+        </div>
+      </v-card-text>
+    </template>
+    <!-- TODO: Extract strike -> bold || normal pattern into own -->
+    <!-- component and reuse? -->
+    <template #groups="{ item: { oldGroups, newGroups } }">
+      <span v-if="newGroups.length > 0">
+        <span class="strike-through" v-for="g in oldGroups" :key="g.id">{{
+          g.shortName
+        }}</span>
+        <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+        <span>&nbsp;→&nbsp;</span>
+        <strong v-for="g in newGroups" :key="g.id">{{ g.shortName }}</strong>
+      </span>
+      <span v-else v-for="g in oldGroups" :key="g.id">{{ g.shortName }}</span>
+    </template>
+    <template #time="{ item: { startSlot, endSlot, startTime, endTime } }">
+      <span v-if="startSlot && endSlot && startSlot === endSlot">
+        {{ startSlot }}.
+      </span>
+      <span v-else-if="startSlot && endSlot">
+        {{ startSlot }}.–{{ endSlot }}.
+      </span>
+      <span v-else-if="startTime && endTime">
+        {{ $d(new Date(startTime), "shortTime") }}
+        –
+        {{ $d(new Date(endTime), "shortTime") }}
+      </span>
+      <span v-else>{{ $t("chronos.substitutions.all_day") }}</span>
+    </template>
+    <template #teachers="{ item: { oldTeachers, newTeachers } }">
+      <span v-if="newTeachers.length > 0">
+        <span class="strike-through" v-for="t in oldTeachers" :key="t.id">
+          {{ t.shortName || t.fullName }}
+        </span>
+        <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+        <span>&nbsp;→&nbsp;</span>
+        <strong v-for="t in newTeachers" :key="t.id">
+          {{ t.shortName || t.fullName }}
+        </strong>
+      </span>
+      <span v-else v-for="t in oldTeachers" :key="t.id">
+        {{ t.shortName || t.fullName }}
+      </span>
+    </template>
+    <template #subject="{ item: { oldSubject, newSubject } }">
+      <span v-if="oldSubject === 'SUPERVISION'">
+        {{ $t("chronos.substitutions.supervision") }}
+      </span>
+      <span v-else-if="newSubject">
+        <span class="strike-through">{{ oldSubject }}</span>
+        <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+        <span>&nbsp;→&nbsp;</span>
+        <strong>{{ newSubject }}</strong>
+      </span>
+      <span v-else>{{ oldSubject }}</span>
+    </template>
+    <template #rooms="{ item: { oldRooms, newRooms } }">
+      <span v-if="newRooms.length > 0">
+        <span class="strike-through" v-for="r in oldRooms" :key="r.id">{{
+          r.shortName || r.name
+        }}</span>
+        <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+        <span>&nbsp;→&nbsp;</span>
+        <strong v-for="r in newRooms" :key="r.id">{{
+          r.shortName || r.name
+        }}</strong>
+      </span>
+      <span v-else v-for="r in oldRooms" :key="r.id">{{
+        r.shortName || r.name
+      }}</span>
+    </template>
+    <template #notes="{ item: { cancelled, notes } }">
+      <v-chip v-if="cancelled" color="green" text-color="white">
+        {{ $t("chronos.substitutions.cancelled") }}
+      </v-chip>
+      {{ notes }}
+    </template>
+    <template #no-data>
+      {{ $t("chronos.substitutions.no_substitutions") }}
+    </template>
+    <template #footer>
+      <!-- TODO: Skip over unneeded days; eg. weekends. -->
+      <date-select-footer
+        :value="date"
+        @input="gotoDate"
+        @prev="gotoDate(DateTime.fromISO(date).minus({ days: 1 }).toISODate())"
+        @next="gotoDate(DateTime.fromISO(date).plus({ days: 1 }).toISODate())"
+      />
+    </template>
+  </c-r-u-d-list>
+</template>
+
+<script>
+import { substitutionsForDate } from "./substitutions.graphql";
+import { DateTime } from "luxon";
+
+export default {
+  name: "Substitutions",
+  props: {
+    date: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      query: substitutionsForDate,
+      affectedTeachers: [],
+      affectedGroups: [],
+      headers: [
+        {
+          text: this.$t("chronos.substitutions.groups"),
+          value: "groups",
+        },
+        {
+          text: this.$t("chronos.substitutions.time"),
+          value: "time",
+        },
+        {
+          text: this.$t("chronos.substitutions.teachers"),
+          value: "teachers",
+        },
+        {
+          text: this.$t("chronos.substitutions.subject"),
+          value: "subject",
+        },
+        {
+          text: this.$t("chronos.substitutions.rooms"),
+          value: "rooms",
+        },
+        {
+          text: this.$t("chronos.substitutions.notes"),
+          value: "notes",
+        },
+      ],
+    };
+  },
+  methods: {
+    prepareList(data) {
+      this.affectedTeachers = data.affectedTeachers;
+      this.affectedGroups = data.affectedGroups;
+      return data.substitutions;
+    },
+    itemColor(item) {
+      return item.cancelled ? "green-text" : "";
+    },
+    gotoDate(date) {
+      this.$router.push({
+        name: "chronos.listSubstitutionsForDate",
+        params: {
+          date: date,
+        },
+      });
+    },
+  },
+};
+</script>
+
+<style>
+.green-text {
+  color: green;
+}
+.strike-through {
+  text-decoration: line-through;
+}
+</style>
diff --git a/aleksis/apps/chronos/frontend/components/Timetable.vue b/aleksis/apps/chronos/frontend/components/Timetable.vue
index 5c0f66811aa7fc88098863b7fb71f53975e272b0..78947059bd524bcd829077d790dec3581bfe42a2 100644
--- a/aleksis/apps/chronos/frontend/components/Timetable.vue
+++ b/aleksis/apps/chronos/frontend/components/Timetable.vue
@@ -1,9 +1,84 @@
 <script setup>
 import TimetableWrapper from "./TimetableWrapper.vue";
 </script>
+
 <script>
+import { DateTime } from "luxon";
+
 export default {
   name: "Timetable",
+  data() {
+    return {
+      calendarFocus: "",
+      calendarType: "week",
+      initialRouteFocusSet: false,
+    };
+  },
+  methods: {
+    setCalendarFocus(val) {
+      this.calendarFocus = val;
+    },
+    setCalendarType(val) {
+      this.calendarType = val;
+    },
+    setInnerFocusAndType() {
+      if (this.$route.name === "chronos.timetableWithId") {
+        this.$refs.calendarWithControls.setCalendarFocus(
+          DateTime.now().toISODate(),
+        );
+        this.$refs.calendarWithControls.setCalendarType(
+          this.$vuetify.breakpoint.mdAndDown ? "day" : "week",
+        );
+      } else {
+        this.initialRouteFocusSet = true;
+        this.$refs.calendarWithControls.setCalendarFocus(
+          [
+            this.$route.params.year,
+            this.$route.params.month,
+            this.$route.params.day,
+          ].join("-"),
+        );
+        this.$refs.calendarWithControls.setCalendarType(
+          this.$route.params.view,
+        );
+      }
+    },
+  },
+  watch: {
+    calendarFocus(newValue, oldValue) {
+      // Do not redirect on first page load
+      if (oldValue === "") return;
+
+      // Do not redirect when calendar focus was just set with route param values
+      if (this.initialRouteFocusSet) {
+        this.initialRouteFocusSet = false;
+        return;
+      }
+
+      const [year, month, day] = newValue.split("-");
+      this.$router.push({
+        name: "chronos.timetableWithIdAndParams",
+        params: {
+          view: this.calendarType,
+          year,
+          month,
+          day,
+        },
+      });
+    },
+    calendarType(newValue) {
+      const [year, month, day] = this.calendarFocus.split("-");
+      this.$router.push({
+        name: "chronos.timetableWithIdAndParams",
+        params: {
+          view: newValue,
+          year,
+          month,
+          day,
+        },
+      });
+    },
+  },
 };
 </script>
 
@@ -17,6 +92,10 @@ export default {
           { name: 'holidays' },
         ]"
         :params="{ type: selected.type, id: selected.objId }"
+        ref="calendarWithControls"
+        @changeCalendarFocus="setCalendarFocus"
+        @changeCalendarType="setCalendarType"
+        @calendarReady="setInnerFocusAndType"
       />
     </template>
   </timetable-wrapper>
diff --git a/aleksis/apps/chronos/frontend/components/substitutions.graphql b/aleksis/apps/chronos/frontend/components/substitutions.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..f747c6b56552193374115245f552b14bd6584df8
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions.graphql
@@ -0,0 +1,45 @@
+query substitutionsForDate($date: Date!) {
+  items: substitutionsForDate(date: $date) {
+    affectedTeachers {
+      id
+      shortName
+      fullName
+    }
+    affectedGroups {
+      id
+      shortName
+    }
+    substitutions {
+      oldGroups {
+        shortName
+      }
+      newGroups {
+        shortName
+      }
+      startSlot
+      endSlot
+      startTime
+      endTime
+      oldTeachers {
+        shortName
+        fullName
+      }
+      newTeachers {
+        shortName
+        fullName
+      }
+      oldSubject
+      newSubject
+      oldRooms {
+        shortName
+        name
+      }
+      newRooms {
+        shortName
+        name
+      }
+      cancelled
+      notes
+    }
+  }
+}
diff --git a/aleksis/apps/chronos/frontend/index.js b/aleksis/apps/chronos/frontend/index.js
index 4749ac593afcd244d9d4f06b992ef6dea99c0225..1893e4f184e67177fb6f3d0e9d2f8d661c643d55 100644
--- a/aleksis/apps/chronos/frontend/index.js
+++ b/aleksis/apps/chronos/frontend/index.js
@@ -1,5 +1,7 @@
 import { hasPersonValidator } from "aleksis.core/routeValidators";
 import Timetable from "./components/Timetable.vue";
+import Substitutions from "./components/Substitutions.vue";
+import { DateTime } from "luxon";
 
 export default {
   meta: {
@@ -7,7 +9,7 @@ export default {
     titleKey: "chronos.menu_title",
     icon: "mdi-school-outline",
     iconActive: "mdi-school",
-    validators: [hasPersonValidator],
+    permission: "chronos.view_menu_rule",
   },
   children: [
     {
@@ -31,11 +33,22 @@ export default {
         permission: "chronos.view_timetable_overview_rule",
         fullWidth: true,
       },
+      children: [
+        {
+          path: ":view(month|week|day)/:year(\\d\\d\\d\\d)/:month(\\d\\d)/:day(\\d\\d)/",
+          component: Timetable,
+          name: "chronos.timetableWithIdAndParams",
+          meta: {
+            permission: "chronos.view_timetable_overview_rule",
+            fullWidth: true,
+          },
+        },
+      ],
     },
     {
       path: "substitutions/print/",
       component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-      name: "chronos.substitutions",
+      name: "chronos.printSubstitutions",
       props: {
         byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
@@ -43,10 +56,42 @@ export default {
     {
       path: "substitutions/print/:date/",
       component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-      name: "chronos.substitutionsByDate",
+      name: "chronos.printSubstitutionsForDate",
       props: {
         byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
     },
+    {
+      path: "substitutions/",
+      props: true,
+      component: Substitutions,
+      name: "chronos.listSubstitutionsForDateLanding",
+      redirect: () => {
+        return {
+          name: "chronos.listSubstitutionsForDate",
+          params: {
+            date: DateTime.now().toISODate(),
+          },
+        };
+      },
+      meta: {
+        inMenu: true,
+        titleKey: "chronos.substitutions.menu_title",
+        toolbarTitle: "chronos.substitutions.menu_title",
+        icon: "mdi-list-status",
+        permission: "chronos.view_substitutions_rule",
+      },
+      children: [
+        {
+          path: ":date/",
+          component: Substitutions,
+          name: "chronos.listSubstitutionsForDate",
+          meta: {
+            toolbarTitle: "chronos.substitutions.menu_title",
+            permission: "chronos.view_substitutions_rule",
+          },
+        },
+      ],
+    },
   ],
 };
diff --git a/aleksis/apps/chronos/frontend/messages/de.json b/aleksis/apps/chronos/frontend/messages/de.json
index f729d415edb8336cdddb7b5abe853bebc3cad5a4..a7c50a848e25f14b0cc24a36f0cf80df2e0efd94 100644
--- a/aleksis/apps/chronos/frontend/messages/de.json
+++ b/aleksis/apps/chronos/frontend/messages/de.json
@@ -22,7 +22,20 @@
     },
     "menu_title": "Stundenpläne",
     "substitutions": {
-      "menu_title": "Vertretungen"
+      "menu_title": "Vertretungen",
+      "print": "Drucken",
+      "groups": "Gruppen",
+      "time": "Zeit",
+      "teachers": "Lehrer",
+      "subject": "Fach",
+      "rooms": "Räume",
+      "notes": "Notizen",
+      "supervision": "Aufsicht",
+      "cancelled": "Entfällt",
+      "affected_teachers": "Betroffene Lehrer",
+      "affected_groups": "Betroffene Gruppen",
+      "all_day": "Ganztägig",
+      "no_substitutions": "Keine Vertretungen"
     },
     "supervisions": {
       "menu_title_daily": "Aufsichten",
diff --git a/aleksis/apps/chronos/frontend/messages/en.json b/aleksis/apps/chronos/frontend/messages/en.json
index e38a4e03df2f494032be278d85d1c4fcec880984..7ca0d3ee9fcc7774032eb2c2fa13950c361d898f 100644
--- a/aleksis/apps/chronos/frontend/messages/en.json
+++ b/aleksis/apps/chronos/frontend/messages/en.json
@@ -23,7 +23,20 @@
       "menu_title_daily": "Daily lessons"
     },
     "substitutions": {
-      "menu_title": "Substitutions"
+      "menu_title": "Substitutions",
+      "print": "Print",
+      "groups": "Groups",
+      "time": "Time",
+      "teachers": "Teachers",
+      "subject": "Subject",
+      "rooms": "Rooms",
+      "notes": "Notes",
+      "supervision": "Supervision",
+      "cancelled": "Cancelled",
+      "affected_teachers": "Affected teachers",
+      "affected_groups": "Affected groups",
+      "all_day": "All day",
+      "no_substitutions": "No substitutions"
     },
     "supervisions": {
       "title": "Supervision",
diff --git a/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
index 7bfa932f4f871a7787fc4f238c190419a4f07021..67e0bba4ca0e30d6ab451ba309c91fd6048dada5 100644
--- a/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,418 +18,133 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr ""
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
+#: aleksis/apps/chronos/models.py:48
+msgid "Number of days shown in the plan"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
+#: aleksis/apps/chronos/models.py:52
+msgid "Show header box"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+msgid "The header box shows affected teachers/groups."
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
+#: aleksis/apps/chronos/models.py:60
+msgid "Revision which triggered the last update"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
+msgid "Automatic plan"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
+msgid "Automatic plans"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
+#: aleksis/apps/chronos/models.py:147
+msgid "Can view all room timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
+#: aleksis/apps/chronos/models.py:148
+msgid "Can view all group timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
+#: aleksis/apps/chronos/models.py:149
+msgid "Can view all person timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
+#: aleksis/apps/chronos/models.py:150
+msgid "Can view timetable overview"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
+#: aleksis/apps/chronos/models.py:166
+msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
+#: aleksis/apps/chronos/models.py:169
+msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
+#: aleksis/apps/chronos/models.py:173
+msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
+#: aleksis/apps/chronos/models.py:191
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
+#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
+msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
+#: aleksis/apps/chronos/models.py:210
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
+msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
+#: aleksis/apps/chronos/models.py:467
+msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
+#: aleksis/apps/chronos/models.py:468
+msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
+#: aleksis/apps/chronos/models.py:475
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1194
-msgid "Number of days shown in the plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1198
-msgid "Show header box"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-msgid "The header box shows affected teachers/groups."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1206
-msgid "Revision which triggered the last update"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1278
-msgid "Automatic plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1279
-msgid "Automatic plans"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1293
-msgid "Can view all room timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1294
-msgid "Can view all group timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1295
-msgid "Can view all person timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1296
-msgid "Can view timetable overview"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1313
-msgid "Start slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1316
-msgid "End slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1320
-msgid "Course"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1353
-#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
-msgid "Cancelled"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
-msgid "{} (instead of {})"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1614
-msgid "Lesson Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1615
-msgid "Lesson Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr ""
 
@@ -445,72 +160,39 @@ msgstr ""
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr ""
 
@@ -534,6 +216,10 @@ msgstr ""
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr ""
@@ -546,6 +232,10 @@ msgstr ""
 msgid "Time"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr ""
@@ -557,99 +247,3 @@ msgstr ""
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:2
 msgid "Areas"
 msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:48
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:55
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:64
-#, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
-msgstr[4] ""
-msgstr[5] ""
-
-#: aleksis/apps/chronos/util/notifications.py:76
-#, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:82
-#, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:91
-#, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:99
-#, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:112
-#, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#, python-brace-format
-msgid "Groups: {groups}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:128
-#, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:135
-#, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:145
-#, python-brace-format
-msgid "Subject: {subject}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:149
-#, python-brace-format
-msgid "Room: {room}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:151
-#, python-brace-format
-msgid "Comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:154
-#, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr ""
diff --git a/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
index 6cc74cf61d93a9433e0182e2955f670f4c7bbec7..d53ce3b87c37282efc92294219a0fa18d0cdc4d2 100644
--- a/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
@@ -2,499 +2,158 @@
 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 # This file is distributed under the same license as the PACKAGE package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# Jonathan Weth <git@jonathanewth.de>, 2024.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: \n"
+"Project-Id-Version: unnamed project\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
-"PO-Revision-Date: 2024-08-18 13:31+0000\n"
-"Last-Translator: magicfelix <felix@felix-zauberer.de>\n"
-"Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/de/>\n"
-"Language: de_DE\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"PO-Revision-Date: 2024-10-17 11:54+0200\n"
+"Last-Translator: Jonathan Weth <git@jonathanewth.de>\n"
+"Language-Team: English\n"
+"Language: en\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 5.0.2\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Gtranslator 47.0\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr "Zugeordneter Gültigkeitsbereich"
-
-#: aleksis/apps/chronos/model_extensions.py:145
-#: aleksis/apps/chronos/model_extensions.py:151
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr "Kann Gruppenstundenpläne sehen"
 
-#: aleksis/apps/chronos/model_extensions.py:149
-#: aleksis/apps/chronos/model_extensions.py:155
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr "Kann Personenstundenpläne sehen"
 
-#: aleksis/apps/chronos/models.py:87 aleksis/apps/chronos/models.py:86
-msgid "School term"
-msgstr "Schuljahr"
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310 aleksis/apps/chronos/models.py:89
-#: aleksis/apps/chronos/models.py:699
-msgid "Name"
-msgstr "Name"
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-#: aleksis/apps/chronos/models.py:91 aleksis/apps/chronos/models.py:754
-#: aleksis/apps/chronos/models.py:827 aleksis/apps/chronos/models.py:1061
-msgid "Start date"
-msgstr "Startdatum"
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:755
-#: aleksis/apps/chronos/models.py:828 aleksis/apps/chronos/models.py:1062
-msgid "End date"
-msgstr "Enddatum"
-
-#: aleksis/apps/chronos/models.py:112 aleksis/apps/chronos/models.py:111
-msgid "The start date must be earlier than the end date."
-msgstr "Das Startdatum muss vor dem Enddatum liegen."
-
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
-msgstr "Der Gültigkeitsbereich muss innerhalb des Schuljahres liegen."
-
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
-msgstr ""
-"Es gibt bereits einen Gültigkeitsbereich für diesen Zeitraum oder einen Teil "
-"diesen Zeitraumes."
-
-#: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
-msgstr "Gültigkeitsbereich"
-
-#: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
-msgstr "Gültigkeitsbereiche"
-
-#: aleksis/apps/chronos/models.py:149 aleksis/apps/chronos/models.py:150
-msgid "Week day"
-msgstr "Wochentag"
-
-#: aleksis/apps/chronos/models.py:151 aleksis/apps/chronos/models.py:152
-msgid "Number of period"
-msgstr "Nummer der Stunde"
-
-#: aleksis/apps/chronos/models.py:153 aleksis/apps/chronos/models.py:154
-msgid "Start time"
-msgstr "Startzeit"
-
-#: aleksis/apps/chronos/models.py:154 aleksis/apps/chronos/models.py:155
-msgid "End time"
-msgstr "Endzeit"
-
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122 aleksis/apps/chronos/models.py:338
-#: aleksis/apps/chronos/models.py:525 aleksis/apps/chronos/models.py:1191
-msgid "Time period"
-msgstr "Stunde"
-
-#: aleksis/apps/chronos/models.py:325 aleksis/apps/chronos/models.py:339
-msgid "Time periods"
-msgstr "Stunden"
-
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-#: aleksis/apps/chronos/models.py:343 aleksis/apps/chronos/models.py:364
-#: aleksis/apps/chronos/models.py:698 aleksis/apps/chronos/models.py:873
-#: aleksis/apps/chronos/models.py:895
-msgid "Short name"
-msgstr "Kurzname"
-
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811 aleksis/apps/chronos/models.py:344
-#: aleksis/apps/chronos/models.py:365 aleksis/apps/chronos/models.py:874
-#: aleksis/apps/chronos/models.py:896
-msgid "Long name"
-msgstr "Langname"
-
-#: aleksis/apps/chronos/models.py:332 aleksis/apps/chronos/models.py:346
-msgid "Foreground colour"
-msgstr "Vordergrundfarbe"
-
-#: aleksis/apps/chronos/models.py:333 aleksis/apps/chronos/models.py:347
-msgid "Background colour"
-msgstr "Hintergrundfarbe"
-
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-#: aleksis/apps/chronos/models.py:354 aleksis/apps/chronos/models.py:390
-#: aleksis/apps/chronos/models.py:446 aleksis/apps/chronos/models.py:1198
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:47
-msgid "Subject"
-msgstr "Fach"
-
-#: aleksis/apps/chronos/models.py:341 aleksis/apps/chronos/models.py:355
-msgid "Subjects"
-msgstr "Fächer"
-
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
-#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
-#: aleksis/apps/chronos/models.py:393 aleksis/apps/chronos/models.py:452
-#: aleksis/apps/chronos/models.py:1080 aleksis/apps/chronos/models.py:1206
-#: aleksis/apps/chronos/tables.py:35
-#: aleksis/apps/chronos/templates/chronos/all.html:17
-msgid "Teachers"
-msgstr "Lehrkräfte"
-
-#: aleksis/apps/chronos/models.py:358 aleksis/apps/chronos/models.py:399
-msgid "Periods"
-msgstr "Stunden"
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-#: aleksis/apps/chronos/models.py:401 aleksis/apps/chronos/models.py:1077
-#: aleksis/apps/chronos/models.py:1201 aleksis/apps/chronos/tables.py:34
-#: aleksis/apps/chronos/templates/chronos/all.html:31
-msgid "Groups"
-msgstr "Gruppen"
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454 aleksis/apps/chronos/models.py:424
-#: aleksis/apps/chronos/models.py:519 aleksis/apps/chronos/models.py:795
-msgid "Lesson"
-msgstr "Unterrichtsstunde"
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-#: aleksis/apps/chronos/models.py:425
-#: aleksis/apps/chronos/templates/chronos/lessons_day.html:9
-#: aleksis/apps/chronos/templates/chronos/lessons_day.html:19
-msgid "Lessons"
-msgstr "Unterrichtsstunden"
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:1185
-msgid "Week"
-msgstr "KW"
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-#: aleksis/apps/chronos/models.py:434 aleksis/apps/chronos/models.py:1186
-msgid "Year"
-msgstr "Jahr"
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-#: aleksis/apps/chronos/models.py:437 aleksis/apps/chronos/models.py:654
-msgid "Lesson period"
-msgstr "Unterrichtsstunde"
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-#: aleksis/apps/chronos/models.py:376 aleksis/apps/chronos/models.py:454
-#: aleksis/apps/chronos/models.py:533 aleksis/apps/chronos/models.py:751
-#: aleksis/apps/chronos/models.py:1213
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:48
-msgid "Room"
-msgstr "Raum"
-
-#: aleksis/apps/chronos/models.py:428 aleksis/apps/chronos/models.py:456
-msgid "Cancelled?"
-msgstr "Entfällt?"
-
-#: aleksis/apps/chronos/models.py:430 aleksis/apps/chronos/models.py:458
-msgid "Cancelled for teachers?"
-msgstr "Entfällt für Lehrkräfte?"
-
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357 aleksis/apps/chronos/models.py:461
-#: aleksis/apps/chronos/models.py:770 aleksis/apps/chronos/models.py:813
-#: aleksis/apps/chronos/models.py:1216
-msgid "Comment"
-msgstr "Kommentar"
-
-#: aleksis/apps/chronos/models.py:437 aleksis/apps/chronos/models.py:465
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-"Unterrichtsstunden können nur entweder vertreten werden oder ausfallen."
-
-#: aleksis/apps/chronos/models.py:477 aleksis/apps/chronos/models.py:506
-msgid "Lesson substitution"
-msgstr "Vertretung"
-
-#: aleksis/apps/chronos/models.py:478 aleksis/apps/chronos/models.py:507
-msgid "Lesson substitutions"
-msgstr "Vertretungen"
-
-#: aleksis/apps/chronos/models.py:621 aleksis/apps/chronos/models.py:655
-msgid "Lesson periods"
-msgstr "Unterrichtsstunden"
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-#: aleksis/apps/chronos/models.py:708 aleksis/apps/chronos/models.py:726
-msgid "Absence reason"
-msgstr "Abwesenheitsgrund"
-
-#: aleksis/apps/chronos/models.py:636 aleksis/apps/chronos/models.py:709
-msgid "Absence reasons"
-msgstr "Abwesenheitsgründe"
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943 aleksis/apps/chronos/models.py:735
-#: aleksis/apps/chronos/models.py:974 aleksis/apps/chronos/models.py:1025
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:46
-msgid "Teacher"
-msgstr "Lehrkraft"
-
-#: aleksis/apps/chronos/models.py:665 aleksis/apps/chronos/models.py:743
-msgid "Group"
-msgstr "Gruppe"
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-#: aleksis/apps/chronos/models.py:759 aleksis/apps/chronos/models.py:802
-msgid "Start period"
-msgstr "Startstunde"
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-#: aleksis/apps/chronos/models.py:766 aleksis/apps/chronos/models.py:808
-msgid "End period"
-msgstr "Endstunde"
-
-#: aleksis/apps/chronos/models.py:702 aleksis/apps/chronos/models.py:780
-msgid "Unknown absence"
-msgstr "Unbekannte Absenz"
-
-#: aleksis/apps/chronos/models.py:707 aleksis/apps/chronos/models.py:786
-msgid "Absence"
-msgstr "Abwesenheit"
-
-#: aleksis/apps/chronos/models.py:708 aleksis/apps/chronos/models.py:787
-msgid "Absences"
-msgstr "Abwesenheiten"
-
-#: aleksis/apps/chronos/models.py:719 aleksis/apps/chronos/models.py:798
-msgid "Date of exam"
-msgstr "Datum der Klausur"
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977 aleksis/apps/chronos/models.py:812
-#: aleksis/apps/chronos/models.py:826 aleksis/apps/chronos/models.py:1059
-msgid "Title"
-msgstr "Titel"
-
-#: aleksis/apps/chronos/models.py:739 aleksis/apps/chronos/models.py:819
-msgid "Exam"
-msgstr "Klausur"
-
-#: aleksis/apps/chronos/models.py:740 aleksis/apps/chronos/models.py:820
-msgid "Exams"
-msgstr "Klausuren"
-
-#: aleksis/apps/chronos/models.py:749 aleksis/apps/chronos/models.py:829
-msgid "Comments"
-msgstr "Kommentare"
-
-#: aleksis/apps/chronos/models.py:788 aleksis/apps/chronos/models.py:868
-msgid "Holiday"
-msgstr "Ferien"
-
-#: aleksis/apps/chronos/models.py:789 aleksis/apps/chronos/models.py:869
-msgid "Holidays"
-msgstr "Ferien"
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-#: aleksis/apps/chronos/models.py:883 aleksis/apps/chronos/models.py:964
-msgid "Supervision area"
-msgstr "Aufsichtsgebiet"
-
-#: aleksis/apps/chronos/models.py:804 aleksis/apps/chronos/models.py:884
-msgid "Supervision areas"
-msgstr "Aufsichtsgebiete"
-
-#: aleksis/apps/chronos/models.py:816 aleksis/apps/chronos/models.py:901
-msgid "Time period after break starts"
-msgstr "Stunde, nach der die Pause startet"
-
-#: aleksis/apps/chronos/models.py:824 aleksis/apps/chronos/models.py:909
-msgid "Time period before break ends"
-msgstr "Stunde nach der Pause"
-
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-#: aleksis/apps/chronos/models.py:949 aleksis/apps/chronos/models.py:968
-msgid "Break"
-msgstr "Pause"
-
-#: aleksis/apps/chronos/models.py:865 aleksis/apps/chronos/models.py:950
-msgid "Breaks"
-msgstr "Pausen"
-
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-#: aleksis/apps/chronos/models.py:1005 aleksis/apps/chronos/models.py:1018
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:23
-#: aleksis/apps/chronos/templates/chronos/partials/supervision.html:15
-msgid "Supervision"
-msgstr "Aufsicht"
-
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
-#: aleksis/apps/chronos/models.py:1006
-msgid "Supervisions"
-msgstr "Aufsichten"
-
-#: aleksis/apps/chronos/models.py:932 aleksis/apps/chronos/models.py:1014
-msgid "Date"
-msgstr "Datum"
-
-#: aleksis/apps/chronos/models.py:966 aleksis/apps/chronos/models.py:1048
-msgid "Supervision substitution"
-msgstr "Aufsichtsvertretung"
-
-#: aleksis/apps/chronos/models.py:967 aleksis/apps/chronos/models.py:1049
-msgid "Supervision substitutions"
-msgstr "Aufsichtsvertretungen"
-
-#: aleksis/apps/chronos/models.py:985 aleksis/apps/chronos/models.py:1067
-msgid "Start time period"
-msgstr "Startstunde"
-
-#: aleksis/apps/chronos/models.py:991 aleksis/apps/chronos/models.py:1073
-msgid "End time period"
-msgstr "Endstunde"
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-#: aleksis/apps/chronos/models.py:377 aleksis/apps/chronos/models.py:1078
-#: aleksis/apps/chronos/templates/chronos/all.html:45
-msgid "Rooms"
-msgstr "Räume"
-
-#: aleksis/apps/chronos/models.py:1005 aleksis/apps/chronos/models.py:1087
-#, python-brace-format
-msgid "Event {pk}"
-msgstr "Veranstaltung {pk}"
-
-#: aleksis/apps/chronos/models.py:1103 aleksis/apps/chronos/models.py:1172
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:27
-msgid "Event"
-msgstr "Veranstaltung"
-
-#: aleksis/apps/chronos/models.py:1104 aleksis/apps/chronos/models.py:1173
-msgid "Events"
-msgstr "Veranstaltungen"
-
-#: aleksis/apps/chronos/models.py:1152 aleksis/apps/chronos/models.py:1221
-msgid "Related exam"
-msgstr "Zugehörige Klausur"
-
-#: aleksis/apps/chronos/models.py:1181 aleksis/apps/chronos/models.py:1251
-msgid "Extra lesson"
-msgstr "Sonderstunde"
-
-#: aleksis/apps/chronos/models.py:1182 aleksis/apps/chronos/models.py:1252
-msgid "Extra lessons"
-msgstr "Sonderstunden"
-
-#: aleksis/apps/chronos/models.py:1194 aleksis/apps/chronos/models.py:1264
+#: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr "Anzahl der Tage, die im Plan angezeigt werden"
 
-#: aleksis/apps/chronos/models.py:1198 aleksis/apps/chronos/models.py:1268
+#: aleksis/apps/chronos/models.py:52
 msgid "Show header box"
 msgstr "Kopfbox anzeigen"
 
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-#: aleksis/apps/chronos/models.py:1269 aleksis/apps/chronos/preferences.py:61
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
 msgid "The header box shows affected teachers/groups."
 msgstr "Die Kopfbox zeigt betroffene Lehrkräfte/Gruppen."
 
-#: aleksis/apps/chronos/models.py:1206 aleksis/apps/chronos/models.py:1276
+#: aleksis/apps/chronos/models.py:60
 msgid "Revision which triggered the last update"
 msgstr "Revision, die die letzte Aktualisierung ausgelöst hat"
 
-#: aleksis/apps/chronos/models.py:1278 aleksis/apps/chronos/models.py:1337
+#: aleksis/apps/chronos/models.py:132
 msgid "Automatic plan"
 msgstr "Automatischer Plan"
 
-#: aleksis/apps/chronos/models.py:1279 aleksis/apps/chronos/models.py:1338
+#: aleksis/apps/chronos/models.py:133
 msgid "Automatic plans"
 msgstr "Automatische Pläne"
 
-#: aleksis/apps/chronos/models.py:1293 aleksis/apps/chronos/models.py:1352
+#: aleksis/apps/chronos/models.py:147
 msgid "Can view all room timetables"
 msgstr "Kann alle Raumstundenpläne sehen"
 
-#: aleksis/apps/chronos/models.py:1294 aleksis/apps/chronos/models.py:1353
+#: aleksis/apps/chronos/models.py:148
 msgid "Can view all group timetables"
 msgstr "Kann alle Gruppenstundenpläne sehen"
 
-#: aleksis/apps/chronos/models.py:1295 aleksis/apps/chronos/models.py:1354
+#: aleksis/apps/chronos/models.py:149
 msgid "Can view all person timetables"
 msgstr "Kann alle Personenstundenpläne sehen"
 
-#: aleksis/apps/chronos/models.py:1296 aleksis/apps/chronos/models.py:1355
+#: aleksis/apps/chronos/models.py:150
 msgid "Can view timetable overview"
 msgstr "Kann Stundenplanübersicht sehen"
 
-#: aleksis/apps/chronos/models.py:1297 aleksis/apps/chronos/models.py:1356
-msgid "Can view all lessons per day"
-msgstr "Kann alle Tagesstunden sehen"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
+msgstr "Kann Vertretungstabelle sehen"
 
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr "Kann alle Tagesaufsichten sehen"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
+msgstr "Unterrichtsstunden"
 
-#: aleksis/apps/chronos/models.py:1313
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
+msgstr "Name"
+
+#: aleksis/apps/chronos/models.py:166
 msgid "Start slot number"
 msgstr "Startzeitfenster-Nummer"
 
-#: aleksis/apps/chronos/models.py:1316
+#: aleksis/apps/chronos/models.py:169
 msgid "End slot number"
 msgstr "Endzeitfenster-Nummer"
 
-#: aleksis/apps/chronos/models.py:1320
+#: aleksis/apps/chronos/models.py:173
 msgid "Course"
 msgstr "Kurs"
 
-#: aleksis/apps/chronos/models.py:1353
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
+msgstr "Gruppen"
+
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
+msgstr "Räume"
+
+#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
+#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
+msgid "Teachers"
+msgstr "Lehrkräfte"
+
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
+msgstr "Fach"
+
+#: aleksis/apps/chronos/models.py:206
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr "Entfall"
 
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
+#: aleksis/apps/chronos/models.py:210
+msgid "Comment"
+msgstr "Kommentar"
+
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
 msgid "{} (instead of {})"
 msgstr "{} (statt {})"
 
-#: aleksis/apps/chronos/models.py:1614
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
+msgstr "Unterrichtsstunde"
+
+#: aleksis/apps/chronos/models.py:467
 msgid "Lesson Event"
 msgstr "Unterrichtsstunde"
 
-#: aleksis/apps/chronos/models.py:1615
+#: aleksis/apps/chronos/models.py:468
 msgid "Lesson Events"
 msgstr "Unterrichtsstunden"
 
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:475
+msgid "Supervisions"
+msgstr "Aufsichten"
+
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr "Aufsicht: {}"
 
-#: aleksis/apps/chronos/preferences.py:21 aleksis/apps/chronos/menus.py:6
-#: aleksis/apps/chronos/preferences.py:10
+#: aleksis/apps/chronos/preferences.py:21
 msgid "Timetables"
 msgstr "Stundenpläne"
 
 #: aleksis/apps/chronos/preferences.py:29
-#: aleksis/apps/chronos/preferences.py:18
 msgid "Use parent groups in timetable views"
 msgstr "Elterngruppen in Stundenplanansichten benutzen"
 
@@ -502,53 +161,29 @@ msgstr "Elterngruppen in Stundenplanansichten benutzen"
 msgid ""
 "If a lesson or substitution has only one group and this group has parent "
 "groups, show the parent groups instead of the original group."
-msgstr "Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
-
-#: aleksis/apps/chronos/preferences.py:42
-#: aleksis/apps/chronos/preferences.py:31
-msgid "Shorten groups in timetable views"
-msgstr "Gruppen in Stundenplanansichten kürzen"
-
-#: aleksis/apps/chronos/preferences.py:43
-#: aleksis/apps/chronos/preferences.py:32
-msgid "If there are more groups than the set limit, they will be collapsed."
 msgstr ""
-"Wenn es mehr Gruppen als das gesetzte Limit gibt, werden die Gruppenangaben "
-"gekürzt."
-
-#: aleksis/apps/chronos/preferences.py:51
-#: aleksis/apps/chronos/preferences.py:40
-msgid "Limit of groups for shortening of groups"
-msgstr "Anzahl der Gruppen, ab der gekürzt wird"
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid ""
-"If a user activates shortening of groups,they will be collapsed if there are "
-"more groups than this limit."
-msgstr "Wenn Benutzer*innen die Kürzung von Gruppen aktiviert hat, werden sie ab diesem Limit gekürzt."
+"Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe "
+"Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
 
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr "Relevante Tage für Vertretungspläne"
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr "Zeit zu der Vertretungspläne zum nächsten Tag wechseln"
 
-#: aleksis/apps/chronos/preferences.py:91
-#: aleksis/apps/chronos/preferences.py:52
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 "Anzahl der Tage, die in der Druckansicht des Vertretungsplanes angezeigt "
 "werden soll"
 
-#: aleksis/apps/chronos/preferences.py:99
-#: aleksis/apps/chronos/preferences.py:60
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr "Kopfbox in Vertretungsplänen anzeigen"
 
-#: aleksis/apps/chronos/preferences.py:109
-#: aleksis/apps/chronos/preferences.py:70
+#: aleksis/apps/chronos/preferences.py:88
 msgid ""
 "Show parent groups in header box in substitution views instead of original "
 "groups"
@@ -556,50 +191,19 @@ msgstr ""
 "Elterngruppen in der Kopfbox in Vertretungsplänen an Stelle der eigentlichen "
 "Gruppen anzeigen"
 
-#: aleksis/apps/chronos/preferences.py:118
-#: aleksis/apps/chronos/preferences.py:79
-msgid ""
-"How many days in advance users should be notified about timetable changes?"
-msgstr ""
-"Wie viele Tage im Voraus sollen Benutzer über Stundenplanänderungen "
-"benachrichtigt werden?"
-
-#: aleksis/apps/chronos/preferences.py:126
-#: aleksis/apps/chronos/preferences.py:87
-msgid "Time for sending notifications about timetable changes"
-msgstr "Zeitpunkt zum Senden von Benachrichtigungen über Stundenplanänderungen"
-
-#: aleksis/apps/chronos/preferences.py:129
-#: aleksis/apps/chronos/preferences.py:90
-msgid ""
-"This is only used for scheduling notifications which doesn't affect the time "
-"period configured above. All other notifications affecting the next days are "
-"sent immediately."
-msgstr ""
-"Dies wird nur benutzt, um Benachrichtigungen zu planen, welche nicht den "
-"oben konfigurierten Zeitraum betreffen. Alle weiteren Benachrichtigungen, "
-"die die nächsten Tage betreffen, werden sofort gesendet."
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-#: aleksis/apps/chronos/preferences.py:101
-#: aleksis/apps/chronos/preferences.py:109
-msgid "Send notifications for current timetable changes"
-msgstr "Benachrichtigungen für aktuelle Stundenplanänderungen verschicken"
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr "In Stundenplänen anzuzeigende Gruppentypen"
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr "Wenn Sie das Feld frei lassen, werden alle Gruppen angezeigt."
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr "Unterrichtsstunden-Kalenderfarbe"
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr "Aufsichts-Kalenderfarbe"
 
@@ -623,14 +227,15 @@ msgstr "Betroffene Gruppen"
 msgid "all day"
 msgstr "ganzer Tag"
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr "Aufsicht"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr "Druckansicht: Vertretungen"
 
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:12
-#: aleksis/apps/chronos/menus.py:49
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:11
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:17
 msgid "Substitutions"
 msgstr "Vertretungen"
 
@@ -638,13 +243,15 @@ msgstr "Vertretungen"
 msgid "Time"
 msgstr "Zeit"
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr "Raum"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:49
 msgid "Notes"
 msgstr "Notizen"
 
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:41
-#: aleksis/apps/chronos/templates/chronos/substitutions.html:57
 msgid "No substitutions available."
 msgstr "Keine Vertretungen vorhanden."
 
@@ -652,302 +259,458 @@ msgstr "Keine Vertretungen vorhanden."
 msgid "Areas"
 msgstr "Bereiche"
 
-#: aleksis/apps/chronos/util/notifications.py:48
-#: aleksis/apps/chronos/util/notifications.py:50
+#~ msgid "Linked validity range"
+#~ msgstr "Zugeordneter Gültigkeitsbereich"
+
+#~ msgid "School term"
+#~ msgstr "Schuljahr"
+
+#~ msgid "Start date"
+#~ msgstr "Startdatum"
+
+#~ msgid "End date"
+#~ msgstr "Enddatum"
+
+#~ msgid "The start date must be earlier than the end date."
+#~ msgstr "Das Startdatum muss vor dem Enddatum liegen."
+
+#~ msgid "The validity range must be within the school term."
+#~ msgstr "Der Gültigkeitsbereich muss innerhalb des Schuljahres liegen."
+
+#~ msgid ""
+#~ "There is already a validity range for this time or a part of this time."
+#~ msgstr ""
+#~ "Es gibt bereits einen Gültigkeitsbereich für diesen Zeitraum oder einen "
+#~ "Teil diesen Zeitraumes."
+
+#~ msgid "Validity range"
+#~ msgstr "Gültigkeitsbereich"
+
+#~ msgid "Validity ranges"
+#~ msgstr "Gültigkeitsbereiche"
+
+#~ msgid "Week day"
+#~ msgstr "Wochentag"
+
+#~ msgid "Number of period"
+#~ msgstr "Nummer der Stunde"
+
+#~ msgid "Start time"
+#~ msgstr "Startzeit"
+
+#~ msgid "End time"
+#~ msgstr "Endzeit"
+
+#~ msgid "Time period"
+#~ msgstr "Stunde"
+
+#~ msgid "Time periods"
+#~ msgstr "Stunden"
+
+#~ msgid "Short name"
+#~ msgstr "Kurzname"
+
+#~ msgid "Long name"
+#~ msgstr "Langname"
+
+#~ msgid "Foreground colour"
+#~ msgstr "Vordergrundfarbe"
+
+#~ msgid "Background colour"
+#~ msgstr "Hintergrundfarbe"
+
+#~ msgid "Subjects"
+#~ msgstr "Fächer"
+
+#~ msgid "Periods"
+#~ msgstr "Stunden"
+
+#~ msgid "Week"
+#~ msgstr "KW"
+
+#~ msgid "Year"
+#~ msgstr "Jahr"
+
+#~ msgid "Lesson period"
+#~ msgstr "Unterrichtsstunde"
+
+#~ msgid "Cancelled?"
+#~ msgstr "Entfällt?"
+
+#~ msgid "Cancelled for teachers?"
+#~ msgstr "Entfällt für Lehrkräfte?"
+
+#~ msgid "Lessons can only be either substituted or cancelled."
+#~ msgstr ""
+#~ "Unterrichtsstunden können nur entweder vertreten werden oder ausfallen."
+
+#~ msgid "Lesson substitution"
+#~ msgstr "Vertretung"
+
+#~ msgid "Lesson substitutions"
+#~ msgstr "Vertretungen"
+
+#~ msgid "Lesson periods"
+#~ msgstr "Unterrichtsstunden"
+
+#~ msgid "Absence reason"
+#~ msgstr "Abwesenheitsgrund"
+
+#~ msgid "Absence reasons"
+#~ msgstr "Abwesenheitsgründe"
+
+#~ msgid "Teacher"
+#~ msgstr "Lehrkraft"
+
+#~ msgid "Group"
+#~ msgstr "Gruppe"
+
+#~ msgid "Start period"
+#~ msgstr "Startstunde"
+
+#~ msgid "End period"
+#~ msgstr "Endstunde"
+
+#~ msgid "Unknown absence"
+#~ msgstr "Unbekannte Absenz"
+
+#~ msgid "Absence"
+#~ msgstr "Abwesenheit"
+
+#~ msgid "Absences"
+#~ msgstr "Abwesenheiten"
+
+#~ msgid "Date of exam"
+#~ msgstr "Datum der Klausur"
+
+#~ msgid "Title"
+#~ msgstr "Titel"
+
+#~ msgid "Exam"
+#~ msgstr "Klausur"
+
+#~ msgid "Exams"
+#~ msgstr "Klausuren"
+
+#~ msgid "Comments"
+#~ msgstr "Kommentare"
+
+#~ msgid "Holiday"
+#~ msgstr "Ferien"
+
+#~ msgid "Holidays"
+#~ msgstr "Ferien"
+
+#~ msgid "Supervision area"
+#~ msgstr "Aufsichtsgebiet"
+
+#~ msgid "Supervision areas"
+#~ msgstr "Aufsichtsgebiete"
+
+#~ msgid "Time period after break starts"
+#~ msgstr "Stunde, nach der die Pause startet"
+
+#~ msgid "Time period before break ends"
+#~ msgstr "Stunde nach der Pause"
+
+#~ msgid "Break"
+#~ msgstr "Pause"
+
+#~ msgid "Breaks"
+#~ msgstr "Pausen"
+
+#~ msgid "Date"
+#~ msgstr "Datum"
+
+#~ msgid "Supervision substitution"
+#~ msgstr "Aufsichtsvertretung"
+
+#~ msgid "Supervision substitutions"
+#~ msgstr "Aufsichtsvertretungen"
+
+#~ msgid "Start time period"
+#~ msgstr "Startstunde"
+
+#~ msgid "End time period"
+#~ msgstr "Endstunde"
+
 #, python-brace-format
-msgid ""
-"The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr "Die {subject}-Stunde in der {period}. Stunde am {day} fällt aus."
+#~ msgid "Event {pk}"
+#~ msgstr "Veranstaltung {pk}"
+
+#~ msgid "Event"
+#~ msgstr "Veranstaltung"
+
+#~ msgid "Events"
+#~ msgstr "Veranstaltungen"
+
+#~ msgid "Related exam"
+#~ msgstr "Zugehörige Klausur"
+
+#~ msgid "Extra lesson"
+#~ msgstr "Sonderstunde"
+
+#~ msgid "Extra lessons"
+#~ msgstr "Sonderstunden"
+
+#~ msgid "Can view all lessons per day"
+#~ msgstr "Kann alle Tagesstunden sehen"
+
+#~ msgid "Can view all supervisions per day"
+#~ msgstr "Kann alle Tagesaufsichten sehen"
+
+#~ msgid "Shorten groups in timetable views"
+#~ msgstr "Gruppen in Stundenplanansichten kürzen"
+
+#~ msgid "If there are more groups than the set limit, they will be collapsed."
+#~ msgstr ""
+#~ "Wenn es mehr Gruppen als das gesetzte Limit gibt, werden die "
+#~ "Gruppenangaben gekürzt."
+
+#~ msgid "Limit of groups for shortening of groups"
+#~ msgstr "Anzahl der Gruppen, ab der gekürzt wird"
+
+#~ msgid ""
+#~ "If a user activates shortening of groups,they will be collapsed if there "
+#~ "are more groups than this limit."
+#~ msgstr ""
+#~ "Wenn Benutzer*innen die Kürzung von Gruppen aktiviert hat, werden sie ab "
+#~ "diesem Limit gekürzt."
+
+#~ msgid ""
+#~ "How many days in advance users should be notified about timetable changes?"
+#~ msgstr ""
+#~ "Wie viele Tage im Voraus sollen Benutzer über Stundenplanänderungen "
+#~ "benachrichtigt werden?"
+
+#~ msgid "Time for sending notifications about timetable changes"
+#~ msgstr ""
+#~ "Zeitpunkt zum Senden von Benachrichtigungen über Stundenplanänderungen"
+
+#~ msgid ""
+#~ "This is only used for scheduling notifications which doesn't affect the "
+#~ "time period configured above. All other notifications affecting the next "
+#~ "days are sent immediately."
+#~ msgstr ""
+#~ "Dies wird nur benutzt, um Benachrichtigungen zu planen, welche nicht den "
+#~ "oben konfigurierten Zeitraum betreffen. Alle weiteren Benachrichtigungen, "
+#~ "die die nächsten Tage betreffen, werden sofort gesendet."
+
+#~ msgid "Send notifications for current timetable changes"
+#~ msgstr "Benachrichtigungen für aktuelle Stundenplanänderungen verschicken"
 
-#: aleksis/apps/chronos/util/notifications.py:55
-#: aleksis/apps/chronos/util/notifications.py:57
 #, python-brace-format
-msgid ""
-"The {subject} lesson in the {period}. period on {day} has some current "
-"changes."
-msgstr ""
-"Die {subject}-Stunde in der {period}. Stunde am {day} hat aktuelle "
-"Änderungen."
+#~ msgid ""
+#~ "The {subject} lesson in the {period}. period on {day} has been cancelled."
+#~ msgstr "Die {subject}-Stunde in der {period}. Stunde am {day} fällt aus."
 
-#: aleksis/apps/chronos/util/notifications.py:64
-#: aleksis/apps/chronos/util/notifications.py:66
 #, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] "Die Lehrkraft {old} wird von {new} vertreten."
-msgstr[1] "Die Lehrkräfte {old} werden von {new} vertreten."
+#~ msgid ""
+#~ "The {subject} lesson in the {period}. period on {day} has some current "
+#~ "changes."
+#~ msgstr ""
+#~ "Die {subject}-Stunde in der {period}. Stunde am {day} hat aktuelle "
+#~ "Änderungen."
 
-#: aleksis/apps/chronos/util/notifications.py:76
-#: aleksis/apps/chronos/util/notifications.py:78
 #, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr "Das Fach ist zu {subject} geändert."
+#~ msgid "The teacher {old} is substituted by {new}."
+#~ msgid_plural "The teachers {old} are substituted by {new}."
+#~ msgstr[0] "Die Lehrkraft {old} wird von {new} vertreten."
+#~ msgstr[1] "Die Lehrkräfte {old} werden von {new} vertreten."
 
-#: aleksis/apps/chronos/util/notifications.py:82
-#: aleksis/apps/chronos/util/notifications.py:84
 #, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr "Die Stunde wurde von {old} nach {new} verschoben."
+#~ msgid "The subject is changed to {subject}."
+#~ msgstr "Das Fach ist zu {subject} geändert."
 
-#: aleksis/apps/chronos/util/notifications.py:91
-#: aleksis/apps/chronos/util/notifications.py:93
 #, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr "Es gibt einen zusätzlichen Hinweis: {comment}."
+#~ msgid "The lesson is moved from {old} to {new}."
+#~ msgstr "Die Stunde wurde von {old} nach {new} verschoben."
 
-#: aleksis/apps/chronos/util/notifications.py:99
-#: aleksis/apps/chronos/util/notifications.py:101
 #, python-brace-format
-msgid ""
-"There is an event that starts on {date_start}, {period_from}. period and "
-"ends on {date_end}, {period_to}. period:"
-msgstr ""
-"Es gibt eine Veranstaltung, die am {date_start} in der {period_from}. Stunde "
-"startet und am {date_end} in der {period_to}. Stunde endet:"
+#~ msgid "There is an additional comment: {comment}."
+#~ msgstr "Es gibt einen zusätzlichen Hinweis: {comment}."
 
-#: aleksis/apps/chronos/util/notifications.py:112
-#: aleksis/apps/chronos/util/notifications.py:114
 #, python-brace-format
-msgid ""
-"There is an event on {date} from the {period_from}. period to the "
-"{period_to}. period:"
-msgstr ""
-"Es gibt eine Veranstaltung am {date} von der {period_from}. Stunde bis zur "
-"{period_to}. Stunde:"
+#~ msgid ""
+#~ "There is an event that starts on {date_start}, {period_from}. period and "
+#~ "ends on {date_end}, {period_to}. period:"
+#~ msgstr ""
+#~ "Es gibt eine Veranstaltung, die am {date_start} in der {period_from}. "
+#~ "Stunde startet und am {date_end} in der {period_to}. Stunde endet:"
 
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:145
 #, python-brace-format
-msgid "Groups: {groups}"
-msgstr "Gruppen: {groups}"
+#~ msgid ""
+#~ "There is an event on {date} from the {period_from}. period to the "
+#~ "{period_to}. period:"
+#~ msgstr ""
+#~ "Es gibt eine Veranstaltung am {date} von der {period_from}. Stunde bis "
+#~ "zur {period_to}. Stunde:"
 
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#: aleksis/apps/chronos/util/notifications.py:127
-#: aleksis/apps/chronos/util/notifications.py:149
 #, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr "Lehrkräfte: {teachers}"
+#~ msgid "Groups: {groups}"
+#~ msgstr "Gruppen: {groups}"
 
-#: aleksis/apps/chronos/util/notifications.py:128
-#: aleksis/apps/chronos/util/notifications.py:130
 #, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr "Räume: {rooms}"
+#~ msgid "Teachers: {teachers}"
+#~ msgstr "Lehrkräfte: {teachers}"
 
-#: aleksis/apps/chronos/util/notifications.py:135
-#: aleksis/apps/chronos/util/notifications.py:137
 #, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr "Es gibt eine Zusatzstunde am {date} in der {period}. Stunde:"
+#~ msgid "Rooms: {rooms}"
+#~ msgstr "Räume: {rooms}"
 
-#: aleksis/apps/chronos/util/notifications.py:145
-#: aleksis/apps/chronos/util/notifications.py:147
 #, python-brace-format
-msgid "Subject: {subject}"
-msgstr "Fach: {subject}"
+#~ msgid "There is an extra lesson on {date} in the {period}. period:"
+#~ msgstr "Es gibt eine Zusatzstunde am {date} in der {period}. Stunde:"
 
-#: aleksis/apps/chronos/util/notifications.py:149
-#: aleksis/apps/chronos/util/notifications.py:151
 #, python-brace-format
-msgid "Room: {room}"
-msgstr "Raum: {room}"
+#~ msgid "Subject: {subject}"
+#~ msgstr "Fach: {subject}"
 
-#: aleksis/apps/chronos/util/notifications.py:151
-#: aleksis/apps/chronos/util/notifications.py:153
 #, python-brace-format
-msgid "Comment: {comment}."
-msgstr "Kommentar: {comment}."
+#~ msgid "Room: {room}"
+#~ msgstr "Raum: {room}"
 
-#: aleksis/apps/chronos/util/notifications.py:154
-#: aleksis/apps/chronos/util/notifications.py:156
 #, python-brace-format
-msgid ""
-"The supervision of {old} on {date} between the {period_from}. period and the "
-"{period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-"Die Aufsicht von {old} am {date} zwischen der {period_from}. Stunde un der "
-"{period_to}. Stunde im Gebiet {area} wird von {new} vertreten."
-
-#: aleksis/apps/chronos/util/notifications.py:204
-#: aleksis/apps/chronos/templates/chronos/timetable.html:11
-#: aleksis/apps/chronos/templates/chronos/timetable.html:24
-#: aleksis/apps/chronos/templates/chronos/timetable_print.html:11
-#: aleksis/apps/chronos/util/notifications.py:206
-msgid "Timetable"
-msgstr "Stundenplan"
-
-#: aleksis/apps/chronos/util/notifications.py:205
-#: aleksis/apps/chronos/util/notifications.py:207
-msgid "There are current changes to your timetable."
-msgstr "Es gibt aktuelle Änderungen in Ihrem Stundenplan."
-
-#: aleksis/apps/chronos/form_extensions.py:7
-msgid "Options for timetables"
-msgstr "Einstellungen für Stundenpläne"
-
-#: aleksis/apps/chronos/form_extensions.py:8
-msgid "Optional data for timetables"
-msgstr "Optionale Daten für Stundenpläne"
-
-#: aleksis/apps/chronos/menus.py:16
-#: aleksis/apps/chronos/templates/chronos/my_timetable.html:11
-#: aleksis/apps/chronos/templates/chronos/my_timetable.html:18
-msgid "My timetable"
-msgstr "Mein Plan"
-
-#: aleksis/apps/chronos/menus.py:27
-#: aleksis/apps/chronos/templates/chronos/all.html:11
-#: aleksis/apps/chronos/templates/chronos/all.html:12
-msgid "All timetables"
-msgstr "Alle Stundenpläne"
-
-#: aleksis/apps/chronos/menus.py:38
-msgid "Daily lessons"
-msgstr "Tagesstunden"
-
-#: aleksis/apps/chronos/model_extensions.py:142
-msgid "Show announcement in timetable views?"
-msgstr "Ankündigungen in Stundenplanansichten anzeigen?"
-
-#: aleksis/apps/chronos/models.py:374
-msgid "Can view room timetable"
-msgstr "Kann Raum-Stundenplan sehen"
-
-#: aleksis/apps/chronos/models.py:693
-msgid "Timetable widget"
-msgstr "Stundenplanwidget"
-
-#: aleksis/apps/chronos/models.py:694
-msgid "Timetable widgets"
-msgstr "Stundenplanwidgets"
-
-#: aleksis/apps/chronos/tables.py:41
-msgid "Substitution"
-msgstr "Vertretung"
-
-#: aleksis/apps/chronos/tables.py:43
-msgid "Manage substitution"
-msgstr "Vertretung verwalten"
-
-#: aleksis/apps/chronos/templates/chronos/all.html:25
-msgid "No teachers timetables available."
-msgstr "Keine Stundenpläne von Lehrkräften vorhanden."
-
-#: aleksis/apps/chronos/templates/chronos/all.html:39
-msgid "No group timetables available."
-msgstr "Keine Gruppen-Stundenpläne verfügbar."
-
-#: aleksis/apps/chronos/templates/chronos/all.html:53
-msgid "No room timetables available."
-msgstr "Keine Raum-Stundenpläne verfügbar."
-
-#: aleksis/apps/chronos/templates/chronos/edit_substitution.html:11
-msgid "Edit substitution."
-msgstr "Vertretung bearbeiten."
-
-#: aleksis/apps/chronos/templates/chronos/edit_substitution.html:12
-msgid "Edit substitution"
-msgstr "Vertretung bearbeiten"
-
-#: aleksis/apps/chronos/templates/chronos/edit_substitution.html:25
-msgid "Delete"
-msgstr "Löschen"
-
-#: aleksis/apps/chronos/templates/chronos/my_timetable.html:19
-#: aleksis/apps/chronos/templates/chronos/timetable.html:50
-msgid "SMART PLAN"
-msgstr "SMART PLAN"
-
-#: aleksis/apps/chronos/templates/chronos/my_timetable.html:22
-msgid "Show week timetable for"
-msgstr "Wochenplan anzeigen für"
-
-#: aleksis/apps/chronos/templates/chronos/partials/lesson.html:27
-msgid "Cancelled due to an event"
-msgstr "Fällt wg. einer Veranstaltung aus"
-
-#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:6
-msgid "Cancelled for teachers"
-msgstr "Entfall für Lehrkräfte"
-
-#: aleksis/apps/chronos/templates/chronos/partials/today.html:2
-msgid "Today"
-msgstr "Heute"
-
-#: aleksis/apps/chronos/templates/chronos/partials/week_select.html:14
-#: aleksis/apps/chronos/templates/chronos/partials/week_select.html:25
-#: aleksis/apps/chronos/templates/chronos/partials/week_select.html:36
-msgid "CW"
-msgstr "KW"
-
-#: aleksis/apps/chronos/templates/chronos/timetable.html:29
-msgid "Group teachers:"
-msgstr "Gruppenlehrkräfte:"
-
-#: aleksis/apps/chronos/templates/chronos/timetable.html:55
-msgid "Show regular timetable"
-msgstr "Regulären Plan anzeigen"
-
-#: aleksis/apps/chronos/templates/chronos/timetable.html:63
-#: aleksis/apps/chronos/templates/chronos/timetable.html:84
-msgid "View class register of this week"
-msgstr "Klassenbuch dieser Woche anzeigen"
-
-#: aleksis/apps/chronos/templates/chronos/timetable.html:76
-msgid "Show SMART PLAN"
-msgstr "SMART PLAN anzeigen"
-
-#: aleksis/apps/chronos/templates/chronos/widget.html:8
+#~ msgid "Comment: {comment}."
+#~ msgstr "Kommentar: {comment}."
+
+#, python-brace-format
+#~ msgid ""
+#~ "The supervision of {old} on {date} between the {period_from}. period and "
+#~ "the {period_to}. period in the area {area} is substituted by {new}."
+#~ msgstr ""
+#~ "Die Aufsicht von {old} am {date} zwischen der {period_from}. Stunde un "
+#~ "der {period_to}. Stunde im Gebiet {area} wird von {new} vertreten."
+
+#~ msgid "Timetable"
+#~ msgstr "Stundenplan"
+
+#~ msgid "There are current changes to your timetable."
+#~ msgstr "Es gibt aktuelle Änderungen in Ihrem Stundenplan."
+
+#~ msgid "Options for timetables"
+#~ msgstr "Einstellungen für Stundenpläne"
+
+#~ msgid "Optional data for timetables"
+#~ msgstr "Optionale Daten für Stundenpläne"
+
+#~ msgid "My timetable"
+#~ msgstr "Mein Plan"
+
+#~ msgid "All timetables"
+#~ msgstr "Alle Stundenpläne"
+
+#~ msgid "Daily lessons"
+#~ msgstr "Tagesstunden"
+
+#~ msgid "Show announcement in timetable views?"
+#~ msgstr "Ankündigungen in Stundenplanansichten anzeigen?"
+
+#~ msgid "Can view room timetable"
+#~ msgstr "Kann Raum-Stundenplan sehen"
+
+#~ msgid "Timetable widget"
+#~ msgstr "Stundenplanwidget"
+
+#~ msgid "Timetable widgets"
+#~ msgstr "Stundenplanwidgets"
+
+#~ msgid "Substitution"
+#~ msgstr "Vertretung"
+
+#~ msgid "Manage substitution"
+#~ msgstr "Vertretung verwalten"
+
+#~ msgid "No teachers timetables available."
+#~ msgstr "Keine Stundenpläne von Lehrkräften vorhanden."
+
+#~ msgid "No group timetables available."
+#~ msgstr "Keine Gruppen-Stundenpläne verfügbar."
+
+#~ msgid "No room timetables available."
+#~ msgstr "Keine Raum-Stundenpläne verfügbar."
+
+#~ msgid "Edit substitution."
+#~ msgstr "Vertretung bearbeiten."
+
+#~ msgid "Edit substitution"
+#~ msgstr "Vertretung bearbeiten"
+
+#~ msgid "Delete"
+#~ msgstr "Löschen"
+
+#~ msgid "SMART PLAN"
+#~ msgstr "SMART PLAN"
+
+#~ msgid "Show week timetable for"
+#~ msgstr "Wochenplan anzeigen für"
+
+#~ msgid "Cancelled due to an event"
+#~ msgstr "Fällt wg. einer Veranstaltung aus"
+
+#~ msgid "Cancelled for teachers"
+#~ msgstr "Entfall für Lehrkräfte"
+
+#~ msgid "Today"
+#~ msgstr "Heute"
+
+#~ msgid "CW"
+#~ msgstr "KW"
+
+#~ msgid "Group teachers:"
+#~ msgstr "Gruppenlehrkräfte:"
+
+#~ msgid "Show regular timetable"
+#~ msgstr "Regulären Plan anzeigen"
+
+#~ msgid "View class register of this week"
+#~ msgstr "Klassenbuch dieser Woche anzeigen"
+
+#~ msgid "Show SMART PLAN"
+#~ msgstr "SMART PLAN anzeigen"
+
 #, python-format
-msgid ""
-"\n"
-"        My timetable for %(day)s\n"
-"      "
-msgstr ""
-"\n"
-"        Mein Stundenplan für %(day)s\n"
-"      "
+#~ msgid ""
+#~ "\n"
+#~ "        My timetable for %(day)s\n"
+#~ "      "
+#~ msgstr ""
+#~ "\n"
+#~ "        Mein Stundenplan für %(day)s\n"
+#~ "      "
 
-#: aleksis/apps/chronos/templates/chronos/widget.html:18
-msgid ""
-"\n"
-"          There is no timetable linked to your person.\n"
-"          "
-msgstr ""
-"\n"
-"          Es gibt keine Stundenplan, der Ihrer Person zugeordnet ist.\n"
-"          "
+#~ msgid ""
+#~ "\n"
+#~ "          There is no timetable linked to your person.\n"
+#~ "          "
+#~ msgstr ""
+#~ "\n"
+#~ "          Es gibt keine Stundenplan, der Ihrer Person zugeordnet ist.\n"
+#~ "          "
 
-#: aleksis/apps/chronos/templates/chronos/widget.html:27
-msgid "Go to smart plan"
-msgstr "SMART PLAN anzeigen"
+#~ msgid "Go to smart plan"
+#~ msgstr "SMART PLAN anzeigen"
 
-#: aleksis/apps/chronos/views.py:270
-msgid "The substitution has been saved."
-msgstr "Die Vertretung wurde gespeichert."
+#~ msgid "The substitution has been saved."
+#~ msgstr "Die Vertretung wurde gespeichert."
 
-#: aleksis/apps/chronos/views.py:291
-msgid "The substitution has been deleted."
-msgstr "Die Vertretung wurde gelöscht."
+#~ msgid "The substitution has been deleted."
+#~ msgstr "Die Vertretung wurde gelöscht."
 
-#: aleksis/apps/chronos/preferences.py:20
-msgid ""
-"If an lesson or substitution has only one group and this group has parent "
-"groups, show the parent groups instead of the original group."
-msgstr ""
-"Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe "
-"Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
+#~ msgid ""
+#~ "If an lesson or substitution has only one group and this group has parent "
+#~ "groups, show the parent groups instead of the original group."
+#~ msgstr ""
+#~ "Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe "
+#~ "Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen "
+#~ "Gruppe."
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid ""
-"If an user activates shortening of groups,they will be collapsed if there "
-"are more groups than this limit."
-msgstr ""
-"Wenn ein Benutzer die Kürzung von Gruppen aktiviert hat, werden sie ab "
-"diesem Limit gekürzt."
+#~ msgid ""
+#~ "If an user activates shortening of groups,they will be collapsed if there "
+#~ "are more groups than this limit."
+#~ msgstr ""
+#~ "Wenn ein Benutzer die Kürzung von Gruppen aktiviert hat, werden sie ab "
+#~ "diesem Limit gekürzt."
 
 #~ msgid "View class register of the current week"
 #~ msgstr "Klassenbuch der aktuellen Woche anzeigen"
diff --git a/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
index 0efbbcbc0bddc2a0dfd0fc65f2f487e69d5c6532..cea5c852f6887401bcd06dc8ae9fb07cdb4b222d 100644
--- a/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: 2021-06-16 11:59+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: French <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/fr/>\n"
@@ -18,424 +18,137 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n > 1;\n"
 "X-Generator: Weblate 4.4\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr ""
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
+#: aleksis/apps/chronos/models.py:48
+msgid "Number of days shown in the plan"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
+#: aleksis/apps/chronos/models.py:52
+msgid "Show header box"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+msgid "The header box shows affected teachers/groups."
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
+#: aleksis/apps/chronos/models.py:60
+msgid "Revision which triggered the last update"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
+msgid "Automatic plan"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
+msgid "Automatic plans"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
+#: aleksis/apps/chronos/models.py:147
+msgid "Can view all room timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
+#: aleksis/apps/chronos/models.py:148
+msgid "Can view all group timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
+#: aleksis/apps/chronos/models.py:149
+msgid "Can view all person timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
+#: aleksis/apps/chronos/models.py:150
+msgid "Can view timetable overview"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
+#: aleksis/apps/chronos/models.py:166
+msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
+#: aleksis/apps/chronos/models.py:169
+msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
-msgstr "Sujet"
-
-#: aleksis/apps/chronos/models.py:341
-#, fuzzy
-#| msgid "Subject"
-msgid "Subjects"
-msgstr "Sujet"
-
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
-#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
-msgid "Teachers"
-msgstr "Profs"
-
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
+#: aleksis/apps/chronos/models.py:173
+msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
+#: aleksis/apps/chronos/models.py:179
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr "Cours"
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr "Salle"
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
-msgid "Comment"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr "prof"
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr "groupe"
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr "Absences"
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
-msgid "Supervisions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr "Date"
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
+#: aleksis/apps/chronos/models.py:185
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1194
-msgid "Number of days shown in the plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1198
-msgid "Show header box"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-msgid "The header box shows affected teachers/groups."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1206
-msgid "Revision which triggered the last update"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1278
-msgid "Automatic plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1279
-msgid "Automatic plans"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1293
-msgid "Can view all room timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1294
-msgid "Can view all group timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1295
-msgid "Can view all person timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1296
-msgid "Can view timetable overview"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1313
-msgid "Start slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1316
-msgid "End slot number"
-msgstr ""
+#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
+#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
+msgid "Teachers"
+msgstr "Profs"
 
-#: aleksis/apps/chronos/models.py:1320
-msgid "Course"
-msgstr ""
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
+msgstr "Sujet"
 
-#: aleksis/apps/chronos/models.py:1353
+#: aleksis/apps/chronos/models.py:206
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
+#: aleksis/apps/chronos/models.py:210
+msgid "Comment"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1614
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
+msgstr "Cours"
+
+#: aleksis/apps/chronos/models.py:467
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Event"
 msgstr "Cours"
 
-#: aleksis/apps/chronos/models.py:1615
+#: aleksis/apps/chronos/models.py:468
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Events"
 msgstr "Cours"
 
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:475
+msgid "Supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr ""
 
@@ -451,72 +164,39 @@ msgstr ""
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr ""
 
@@ -540,6 +220,10 @@ msgstr ""
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr ""
@@ -552,6 +236,10 @@ msgstr ""
 msgid "Time"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr "Salle"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr "Notes"
@@ -564,95 +252,24 @@ msgstr ""
 msgid "Areas"
 msgstr ""
 
-#: aleksis/apps/chronos/util/notifications.py:48
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:55
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:64
-#, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] ""
-msgstr[1] ""
-
-#: aleksis/apps/chronos/util/notifications.py:76
-#, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:82
-#, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:91
-#, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:99
-#, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:112
-#, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr ""
+#, fuzzy
+#~| msgid "Subject"
+#~ msgid "Subjects"
+#~ msgstr "Sujet"
 
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#, python-brace-format
-msgid "Groups: {groups}"
-msgstr ""
+#~ msgid "Teacher"
+#~ msgstr "prof"
 
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr ""
+#~ msgid "Group"
+#~ msgstr "groupe"
 
-#: aleksis/apps/chronos/util/notifications.py:128
-#, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr ""
+#~ msgid "Absences"
+#~ msgstr "Absences"
 
-#: aleksis/apps/chronos/util/notifications.py:135
-#, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr ""
+#~ msgid "Date"
+#~ msgstr "Date"
 
-#: aleksis/apps/chronos/util/notifications.py:145
 #, fuzzy, python-brace-format
-#| msgid "Subject"
-msgid "Subject: {subject}"
-msgstr "Sujet"
-
-#: aleksis/apps/chronos/util/notifications.py:149
-#, python-brace-format
-msgid "Room: {room}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:151
-#, python-brace-format
-msgid "Comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:154
-#, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr ""
+#~| msgid "Subject"
+#~ msgid "Subject: {subject}"
+#~ msgstr "Sujet"
diff --git a/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
index 279ead538768dffda25f3cdb31fb9af609117621..8d0616a26f634a0470c7980ef1529cd4a1642a10 100644
--- a/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: 2020-08-23 13:49+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/la/>\n"
@@ -18,418 +18,133 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 "X-Generator: Weblate 4.1.1\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr ""
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
+#: aleksis/apps/chronos/models.py:48
+msgid "Number of days shown in the plan"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
+#: aleksis/apps/chronos/models.py:52
+msgid "Show header box"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+msgid "The header box shows affected teachers/groups."
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
+#: aleksis/apps/chronos/models.py:60
+msgid "Revision which triggered the last update"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
+msgid "Automatic plan"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
+msgid "Automatic plans"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
+#: aleksis/apps/chronos/models.py:147
+msgid "Can view all room timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
+#: aleksis/apps/chronos/models.py:148
+msgid "Can view all group timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
+#: aleksis/apps/chronos/models.py:149
+msgid "Can view all person timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
+#: aleksis/apps/chronos/models.py:150
+msgid "Can view timetable overview"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
+#: aleksis/apps/chronos/models.py:166
+msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
+#: aleksis/apps/chronos/models.py:169
+msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
+#: aleksis/apps/chronos/models.py:173
+msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
+#: aleksis/apps/chronos/models.py:191
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
+#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
+msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
+#: aleksis/apps/chronos/models.py:210
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr "Grex"
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
+msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
+#: aleksis/apps/chronos/models.py:467
+msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
+#: aleksis/apps/chronos/models.py:468
+msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
+#: aleksis/apps/chronos/models.py:475
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1194
-msgid "Number of days shown in the plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1198
-msgid "Show header box"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-msgid "The header box shows affected teachers/groups."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1206
-msgid "Revision which triggered the last update"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1278
-msgid "Automatic plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1279
-msgid "Automatic plans"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1293
-msgid "Can view all room timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1294
-msgid "Can view all group timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1295
-msgid "Can view all person timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1296
-msgid "Can view timetable overview"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1313
-msgid "Start slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1316
-msgid "End slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1320
-msgid "Course"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1353
-#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
-msgid "Cancelled"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
-msgid "{} (instead of {})"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1614
-msgid "Lesson Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1615
-msgid "Lesson Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr ""
 
@@ -445,72 +160,39 @@ msgstr ""
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr ""
 
@@ -534,6 +216,10 @@ msgstr ""
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr ""
@@ -546,6 +232,10 @@ msgstr ""
 msgid "Time"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr ""
@@ -558,94 +248,5 @@ msgstr ""
 msgid "Areas"
 msgstr ""
 
-#: aleksis/apps/chronos/util/notifications.py:48
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:55
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:64
-#, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] ""
-msgstr[1] ""
-
-#: aleksis/apps/chronos/util/notifications.py:76
-#, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:82
-#, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:91
-#, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:99
-#, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:112
-#, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#, python-brace-format
-msgid "Groups: {groups}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:128
-#, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:135
-#, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:145
-#, python-brace-format
-msgid "Subject: {subject}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:149
-#, python-brace-format
-msgid "Room: {room}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:151
-#, python-brace-format
-msgid "Comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:154
-#, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr ""
+#~ msgid "Group"
+#~ msgstr "Grex"
diff --git a/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
index cfc760f01b3206aaf7123340eccf529e9e4db354..e7015c68a7c47522f5d628d897256ea935a950ed 100644
--- a/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,418 +17,133 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr ""
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
+#: aleksis/apps/chronos/models.py:48
+msgid "Number of days shown in the plan"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
+#: aleksis/apps/chronos/models.py:52
+msgid "Show header box"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+msgid "The header box shows affected teachers/groups."
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
+#: aleksis/apps/chronos/models.py:60
+msgid "Revision which triggered the last update"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
+msgid "Automatic plan"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
+msgid "Automatic plans"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
+#: aleksis/apps/chronos/models.py:147
+msgid "Can view all room timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
+#: aleksis/apps/chronos/models.py:148
+msgid "Can view all group timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
+#: aleksis/apps/chronos/models.py:149
+msgid "Can view all person timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
+#: aleksis/apps/chronos/models.py:150
+msgid "Can view timetable overview"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
+#: aleksis/apps/chronos/models.py:166
+msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
+#: aleksis/apps/chronos/models.py:169
+msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
+#: aleksis/apps/chronos/models.py:173
+msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
+#: aleksis/apps/chronos/models.py:191
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
+#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
+msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
+#: aleksis/apps/chronos/models.py:210
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
+msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
+#: aleksis/apps/chronos/models.py:467
+msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
+#: aleksis/apps/chronos/models.py:468
+msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
+#: aleksis/apps/chronos/models.py:475
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1194
-msgid "Number of days shown in the plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1198
-msgid "Show header box"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-msgid "The header box shows affected teachers/groups."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1206
-msgid "Revision which triggered the last update"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1278
-msgid "Automatic plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1279
-msgid "Automatic plans"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1293
-msgid "Can view all room timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1294
-msgid "Can view all group timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1295
-msgid "Can view all person timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1296
-msgid "Can view timetable overview"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1313
-msgid "Start slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1316
-msgid "End slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1320
-msgid "Course"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1353
-#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
-msgid "Cancelled"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
-msgid "{} (instead of {})"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1614
-msgid "Lesson Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1615
-msgid "Lesson Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr ""
 
@@ -444,72 +159,39 @@ msgstr ""
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr ""
 
@@ -533,6 +215,10 @@ msgstr ""
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr ""
@@ -545,6 +231,10 @@ msgstr ""
 msgid "Time"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr ""
@@ -556,95 +246,3 @@ msgstr ""
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:2
 msgid "Areas"
 msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:48
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:55
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:64
-#, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] ""
-msgstr[1] ""
-
-#: aleksis/apps/chronos/util/notifications.py:76
-#, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:82
-#, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:91
-#, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:99
-#, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:112
-#, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#, python-brace-format
-msgid "Groups: {groups}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:128
-#, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:135
-#, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:145
-#, python-brace-format
-msgid "Subject: {subject}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:149
-#, python-brace-format
-msgid "Room: {room}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:151
-#, python-brace-format
-msgid "Comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:154
-#, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr ""
diff --git a/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
index 1b0a45605bebc00ee478a4bec86722a8d2d2e4d2..05b241d475490fd91d69b5e92ee3d1cf8a598a12 100644
--- a/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: 2023-05-26 04:38+0000\n"
 "Last-Translator: Serhii Horichenko <m@sgg.im>\n"
 "Language-Team: Russian <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/ru/>\n"
@@ -18,428 +18,143 @@ msgstr ""
 "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
 "X-Generator: Weblate 4.12.1\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr "Связанный диапазон"
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr "Может просматривать групповые расписания"
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr "Может просматривать личные расписания"
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr "Учебный год"
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr "Полное имя"
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr "Дата начала"
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
-msgstr "Дата окончания"
-
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
-msgstr "Дата начала должна быть ранее даты окончания."
-
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
-msgstr "Контрольный период должен быть в рамках учебного года."
-
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
-msgstr "На это время или на его часть контрольный период уже назначен."
-
-#: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
-msgstr "Контрольный период"
-
-#: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
-msgstr "Контрольные периоды"
-
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
-msgstr "День недели"
-
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
-msgstr "Номер урока"
-
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
-msgstr "Время начала"
-
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
-msgstr "Время окончания"
-
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
-msgstr "Время урока"
-
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
-msgstr "Время уроков"
-
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
-msgstr "Короткое имя"
-
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
-msgstr "Длинное имя"
-
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
-msgstr "Основной цвет"
-
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
-msgstr "Фоновый цвет"
-
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
-msgstr "Предмет"
-
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
-msgstr "Предметы"
-
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
-#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
-msgid "Teachers"
-msgstr "Преподаватели"
-
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr "Часы"
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr "Группы"
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr "Урок"
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr "Уроки"
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr "Неделя"
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr "Год"
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr "Урок по порядку"
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr "Комната"
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
-msgstr "Отменено?"
-
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
-msgstr "Отменено для преподавателей?"
-
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
-msgid "Comment"
-msgstr "Коментарий"
-
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr "Уроки могут быть только заменены или отменены."
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr "Замена урока"
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr "Замены уроков"
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr "Учебные часы"
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr "Причина отсутствия"
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr "Причины отсутствия"
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr "Преподаватель"
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr "Группа"
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr "Начало уроков"
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr "Окончание уроков"
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr "Отсутствие без уважительной причины"
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr "Отсутствие"
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr "Пропуски"
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr "Дата экзамена"
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr "Название"
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr "Экзамен"
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr "Экзамены"
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr "Комментарии"
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr "Выходной"
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr "Выходные"
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr "Зона контроля"
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr "Зоны контроля"
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr "Учёба после перерыва начинается"
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
-msgstr "Учёба перед перерывом заканчивается"
-
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
-msgstr "Перерыв"
-
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
-msgstr "Перерывы"
-
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
-msgstr "Контроль"
-
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
-msgid "Supervisions"
-msgstr "Контроли"
-
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr "Дата"
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr "Замена контроля"
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr "Замены контроля"
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr "Начало урока"
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr "Конец урока"
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr "Комнаты"
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr "Событие {pk}"
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr "Событие"
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr "События"
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr "Связанные экзамены"
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr "Дополнительный урок"
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr "Дополнительные уроки"
-
-#: aleksis/apps/chronos/models.py:1194
+#: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr "Количество дней для отображения в плане"
 
-#: aleksis/apps/chronos/models.py:1198
+#: aleksis/apps/chronos/models.py:52
 msgid "Show header box"
 msgstr "Показать заголовок"
 
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
 msgid "The header box shows affected teachers/groups."
 msgstr "Заголовок отображает преподавателей/группы, на которых влияет."
 
-#: aleksis/apps/chronos/models.py:1206
+#: aleksis/apps/chronos/models.py:60
 msgid "Revision which triggered the last update"
 msgstr "Ревизия, которая вызвала последнее обновление"
 
-#: aleksis/apps/chronos/models.py:1278
+#: aleksis/apps/chronos/models.py:132
 msgid "Automatic plan"
 msgstr "Автоматический план"
 
-#: aleksis/apps/chronos/models.py:1279
+#: aleksis/apps/chronos/models.py:133
 msgid "Automatic plans"
 msgstr "Автоматические планы"
 
-#: aleksis/apps/chronos/models.py:1293
+#: aleksis/apps/chronos/models.py:147
 msgid "Can view all room timetables"
 msgstr "Может просматривать расписания всех комнат"
 
-#: aleksis/apps/chronos/models.py:1294
+#: aleksis/apps/chronos/models.py:148
 msgid "Can view all group timetables"
 msgstr "Может просматривать расписания всех групп"
 
-#: aleksis/apps/chronos/models.py:1295
+#: aleksis/apps/chronos/models.py:149
 msgid "Can view all person timetables"
 msgstr "Может просматривать расписания всех людей"
 
-#: aleksis/apps/chronos/models.py:1296
+#: aleksis/apps/chronos/models.py:150
 msgid "Can view timetable overview"
 msgstr "Может просматривать общее расписание"
 
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr "Может просматривать все уроки за день"
-
-#: aleksis/apps/chronos/models.py:1298
+#: aleksis/apps/chronos/models.py:151
 #, fuzzy
-#| msgid "Can view all lessons per day"
-msgid "Can view all supervisions per day"
-msgstr "Может просматривать все уроки за день"
+#| msgid "No substitutions available."
+msgid "Can view substitutions table"
+msgstr "Замены недоступны."
+
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
+msgstr "Уроки"
+
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
+msgstr "Полное имя"
 
-#: aleksis/apps/chronos/models.py:1313
+#: aleksis/apps/chronos/models.py:166
 #, fuzzy
 #| msgid "Start time"
 msgid "Start slot number"
 msgstr "Время начала"
 
-#: aleksis/apps/chronos/models.py:1316
+#: aleksis/apps/chronos/models.py:169
 #, fuzzy
 #| msgid "End time"
 msgid "End slot number"
 msgstr "Время окончания"
 
-#: aleksis/apps/chronos/models.py:1320
+#: aleksis/apps/chronos/models.py:173
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1353
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
+msgstr "Группы"
+
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
+msgstr "Комнаты"
+
+#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
+#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
+msgid "Teachers"
+msgstr "Преподаватели"
+
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
+msgstr "Предмет"
+
+#: aleksis/apps/chronos/models.py:206
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr "Отменено"
 
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
+#: aleksis/apps/chronos/models.py:210
+msgid "Comment"
+msgstr "Коментарий"
+
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1614
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
+msgstr "Урок"
+
+#: aleksis/apps/chronos/models.py:467
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Event"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:1615
+#: aleksis/apps/chronos/models.py:468
 #, fuzzy
 #| msgid "Lessons"
 msgid "Lesson Events"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:475
+msgid "Supervisions"
+msgstr "Контроли"
+
+#: aleksis/apps/chronos/models.py:483
 #, fuzzy
 #| msgid "Supervision"
 msgid "Supervision: {}"
@@ -459,78 +174,43 @@ msgstr "Использовать родительские группы в обз
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr "Если у урока или у замены только одна группа и у этой группы есть родительские группы, отображать их вместо оригинальной группы."
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr "Сокращать группы в обзорах расписания"
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr "Если количество групп больше установленного лимита, они будут сгруппированы."
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr "Лимит количества групп для сокращения групп"
-
-#: aleksis/apps/chronos/preferences.py:53
-#, fuzzy
-#| msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr "Если пользователь активирует сокращение групп и количество больше установленного лимита, они группируются."
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 #, fuzzy
 #| msgid "Lesson substitutions"
 msgid "Relevant days for substitution plans"
 msgstr "Замены уроков"
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr "Количество дней для отображения в печатном виде замен"
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr "Отображать заголовок в обзоре замен"
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr "В заголовке обзора замен отображать родительские группы вместо оригинальных"
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr "За сколько дней до изменений в расписании необходимо уведомлять пользователей?"
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr "Время для отправки уведомлений об изменениях в расписании"
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr "Это используется только для планирования уведомлений, которые не влияют на время уроков, настроенных выше. Все другие уведомления, которые влияют на ближайшие дни, отправляются незамедлительно."
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr "Отправить уведомления о текущих изменениях в расписании"
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 #, fuzzy
 #| msgid "Shorten groups in timetable views"
 msgid "Group types to show in timetables"
 msgstr "Сокращать группы в обзорах расписания"
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 #, fuzzy
 #| msgid "Supervision area"
 msgid "Supervision calendar feed color"
@@ -556,6 +236,10 @@ msgstr "На какие группы влияет"
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr "Контроль"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr "Печать: Замены"
@@ -570,6 +254,10 @@ msgstr "Замены"
 msgid "Time"
 msgstr "Расписание"
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr "Комната"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr "Заметки"
@@ -582,99 +270,303 @@ msgstr "Замены недоступны."
 msgid "Areas"
 msgstr ""
 
-#: aleksis/apps/chronos/util/notifications.py:48
+#~ msgid "Linked validity range"
+#~ msgstr "Связанный диапазон"
+
+#~ msgid "School term"
+#~ msgstr "Учебный год"
+
+#~ msgid "Start date"
+#~ msgstr "Дата начала"
+
+#~ msgid "End date"
+#~ msgstr "Дата окончания"
+
+#~ msgid "The start date must be earlier than the end date."
+#~ msgstr "Дата начала должна быть ранее даты окончания."
+
+#~ msgid "The validity range must be within the school term."
+#~ msgstr "Контрольный период должен быть в рамках учебного года."
+
+#~ msgid "There is already a validity range for this time or a part of this time."
+#~ msgstr "На это время или на его часть контрольный период уже назначен."
+
+#~ msgid "Validity range"
+#~ msgstr "Контрольный период"
+
+#~ msgid "Validity ranges"
+#~ msgstr "Контрольные периоды"
+
+#~ msgid "Week day"
+#~ msgstr "День недели"
+
+#~ msgid "Number of period"
+#~ msgstr "Номер урока"
+
+#~ msgid "Start time"
+#~ msgstr "Время начала"
+
+#~ msgid "End time"
+#~ msgstr "Время окончания"
+
+#~ msgid "Time period"
+#~ msgstr "Время урока"
+
+#~ msgid "Time periods"
+#~ msgstr "Время уроков"
+
+#~ msgid "Short name"
+#~ msgstr "Короткое имя"
+
+#~ msgid "Long name"
+#~ msgstr "Длинное имя"
+
+#~ msgid "Foreground colour"
+#~ msgstr "Основной цвет"
+
+#~ msgid "Background colour"
+#~ msgstr "Фоновый цвет"
+
+#~ msgid "Subjects"
+#~ msgstr "Предметы"
+
+#~ msgid "Periods"
+#~ msgstr "Часы"
+
+#~ msgid "Week"
+#~ msgstr "Неделя"
+
+#~ msgid "Year"
+#~ msgstr "Год"
+
+#~ msgid "Lesson period"
+#~ msgstr "Урок по порядку"
+
+#~ msgid "Cancelled?"
+#~ msgstr "Отменено?"
+
+#~ msgid "Cancelled for teachers?"
+#~ msgstr "Отменено для преподавателей?"
+
+#~ msgid "Lessons can only be either substituted or cancelled."
+#~ msgstr "Уроки могут быть только заменены или отменены."
+
+#~ msgid "Lesson substitution"
+#~ msgstr "Замена урока"
+
+#~ msgid "Lesson substitutions"
+#~ msgstr "Замены уроков"
+
+#~ msgid "Lesson periods"
+#~ msgstr "Учебные часы"
+
+#~ msgid "Absence reason"
+#~ msgstr "Причина отсутствия"
+
+#~ msgid "Absence reasons"
+#~ msgstr "Причины отсутствия"
+
+#~ msgid "Teacher"
+#~ msgstr "Преподаватель"
+
+#~ msgid "Group"
+#~ msgstr "Группа"
+
+#~ msgid "Start period"
+#~ msgstr "Начало уроков"
+
+#~ msgid "End period"
+#~ msgstr "Окончание уроков"
+
+#~ msgid "Unknown absence"
+#~ msgstr "Отсутствие без уважительной причины"
+
+#~ msgid "Absence"
+#~ msgstr "Отсутствие"
+
+#~ msgid "Absences"
+#~ msgstr "Пропуски"
+
+#~ msgid "Date of exam"
+#~ msgstr "Дата экзамена"
+
+#~ msgid "Title"
+#~ msgstr "Название"
+
+#~ msgid "Exam"
+#~ msgstr "Экзамен"
+
+#~ msgid "Exams"
+#~ msgstr "Экзамены"
+
+#~ msgid "Comments"
+#~ msgstr "Комментарии"
+
+#~ msgid "Holiday"
+#~ msgstr "Выходной"
+
+#~ msgid "Holidays"
+#~ msgstr "Выходные"
+
+#~ msgid "Supervision area"
+#~ msgstr "Зона контроля"
+
+#~ msgid "Supervision areas"
+#~ msgstr "Зоны контроля"
+
+#~ msgid "Time period after break starts"
+#~ msgstr "Учёба после перерыва начинается"
+
+#~ msgid "Time period before break ends"
+#~ msgstr "Учёба перед перерывом заканчивается"
+
+#~ msgid "Break"
+#~ msgstr "Перерыв"
+
+#~ msgid "Breaks"
+#~ msgstr "Перерывы"
+
+#~ msgid "Date"
+#~ msgstr "Дата"
+
+#~ msgid "Supervision substitution"
+#~ msgstr "Замена контроля"
+
+#~ msgid "Supervision substitutions"
+#~ msgstr "Замены контроля"
+
+#~ msgid "Start time period"
+#~ msgstr "Начало урока"
+
+#~ msgid "End time period"
+#~ msgstr "Конец урока"
+
 #, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr "Урок {subject} на {period} уроке {day} отменён."
+#~ msgid "Event {pk}"
+#~ msgstr "Событие {pk}"
+
+#~ msgid "Event"
+#~ msgstr "Событие"
+
+#~ msgid "Events"
+#~ msgstr "События"
+
+#~ msgid "Related exam"
+#~ msgstr "Связанные экзамены"
+
+#~ msgid "Extra lesson"
+#~ msgstr "Дополнительный урок"
+
+#~ msgid "Extra lessons"
+#~ msgstr "Дополнительные уроки"
+
+#~ msgid "Can view all lessons per day"
+#~ msgstr "Может просматривать все уроки за день"
+
+#, fuzzy
+#~| msgid "Can view all lessons per day"
+#~ msgid "Can view all supervisions per day"
+#~ msgstr "Может просматривать все уроки за день"
+
+#~ msgid "Shorten groups in timetable views"
+#~ msgstr "Сокращать группы в обзорах расписания"
+
+#~ msgid "If there are more groups than the set limit, they will be collapsed."
+#~ msgstr "Если количество групп больше установленного лимита, они будут сгруппированы."
+
+#~ msgid "Limit of groups for shortening of groups"
+#~ msgstr "Лимит количества групп для сокращения групп"
+
+#, fuzzy
+#~| msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgstr "Если пользователь активирует сокращение групп и количество больше установленного лимита, они группируются."
+
+#~ msgid "How many days in advance users should be notified about timetable changes?"
+#~ msgstr "За сколько дней до изменений в расписании необходимо уведомлять пользователей?"
+
+#~ msgid "Time for sending notifications about timetable changes"
+#~ msgstr "Время для отправки уведомлений об изменениях в расписании"
+
+#~ msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
+#~ msgstr "Это используется только для планирования уведомлений, которые не влияют на время уроков, настроенных выше. Все другие уведомления, которые влияют на ближайшие дни, отправляются незамедлительно."
+
+#~ msgid "Send notifications for current timetable changes"
+#~ msgstr "Отправить уведомления о текущих изменениях в расписании"
 
-#: aleksis/apps/chronos/util/notifications.py:55
 #, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr "В урок {subject} на {period} уроке {day} внесены некоторые изменения."
+#~ msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
+#~ msgstr "Урок {subject} на {period} уроке {day} отменён."
 
-#: aleksis/apps/chronos/util/notifications.py:64
 #, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] "Преподаватель {old} заменён преподавателем {new}."
-msgstr[1] "Преподаватели {old} заменены преподавателями {new}."
-msgstr[2] "Преподаватели {old} заменены преподавателями {new}."
-msgstr[3] "Преподаватели {old} заменены преподавателями {new}."
-
-#: aleksis/apps/chronos/util/notifications.py:76
+#~ msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
+#~ msgstr "В урок {subject} на {period} уроке {day} внесены некоторые изменения."
+
 #, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr "Предмет заменён на {subject}."
+#~ msgid "The teacher {old} is substituted by {new}."
+#~ msgid_plural "The teachers {old} are substituted by {new}."
+#~ msgstr[0] "Преподаватель {old} заменён преподавателем {new}."
+#~ msgstr[1] "Преподаватели {old} заменены преподавателями {new}."
+#~ msgstr[2] "Преподаватели {old} заменены преподавателями {new}."
+#~ msgstr[3] "Преподаватели {old} заменены преподавателями {new}."
 
-#: aleksis/apps/chronos/util/notifications.py:82
 #, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr "Урок перенесён с {old} на {new}."
+#~ msgid "The subject is changed to {subject}."
+#~ msgstr "Предмет заменён на {subject}."
 
-#: aleksis/apps/chronos/util/notifications.py:91
 #, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr "Присутствует дополнительный комментарий: {comment}."
+#~ msgid "The lesson is moved from {old} to {new}."
+#~ msgstr "Урок перенесён с {old} на {new}."
 
-#: aleksis/apps/chronos/util/notifications.py:99
 #, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr "Запланировано мероприятие, которое начинается {date_start}, на {period_from}. уроке и заканчивается {date_end}, на {period_to}. уроке:"
+#~ msgid "There is an additional comment: {comment}."
+#~ msgstr "Присутствует дополнительный комментарий: {comment}."
 
-#: aleksis/apps/chronos/util/notifications.py:112
 #, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr "Запланировано мероприятие {date} с {period_from}. урока до {period_to}. урока:"
+#~ msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
+#~ msgstr "Запланировано мероприятие, которое начинается {date_start}, на {period_from}. уроке и заканчивается {date_end}, на {period_to}. уроке:"
 
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
 #, python-brace-format
-msgid "Groups: {groups}"
-msgstr "Группы: {groups}"
+#~ msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
+#~ msgstr "Запланировано мероприятие {date} с {period_from}. урока до {period_to}. урока:"
 
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
 #, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr "Преподаватели: {teachers}"
+#~ msgid "Groups: {groups}"
+#~ msgstr "Группы: {groups}"
 
-#: aleksis/apps/chronos/util/notifications.py:128
 #, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr "Комнаты: {rooms}"
+#~ msgid "Teachers: {teachers}"
+#~ msgstr "Преподаватели: {teachers}"
 
-#: aleksis/apps/chronos/util/notifications.py:135
 #, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr "Запланирован дополнительный урок {date} на {period}. уроке:"
+#~ msgid "Rooms: {rooms}"
+#~ msgstr "Комнаты: {rooms}"
 
-#: aleksis/apps/chronos/util/notifications.py:145
 #, python-brace-format
-msgid "Subject: {subject}"
-msgstr "Предмет: {subject}"
+#~ msgid "There is an extra lesson on {date} in the {period}. period:"
+#~ msgstr "Запланирован дополнительный урок {date} на {period}. уроке:"
 
-#: aleksis/apps/chronos/util/notifications.py:149
 #, python-brace-format
-msgid "Room: {room}"
-msgstr "Комната: {room}"
+#~ msgid "Subject: {subject}"
+#~ msgstr "Предмет: {subject}"
 
-#: aleksis/apps/chronos/util/notifications.py:151
 #, python-brace-format
-msgid "Comment: {comment}."
-msgstr "Комментарий: {comment}."
+#~ msgid "Room: {room}"
+#~ msgstr "Комната: {room}"
 
-#: aleksis/apps/chronos/util/notifications.py:154
 #, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr "Контроль {old}, {date} между {period_from}. и {period_to}. уроками в зоне {area} заменён на {new}."
+#~ msgid "Comment: {comment}."
+#~ msgstr "Комментарий: {comment}."
 
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr "Расписание"
+#, python-brace-format
+#~ msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
+#~ msgstr "Контроль {old}, {date} между {period_from}. и {period_to}. уроками в зоне {area} заменён на {new}."
+
+#~ msgid "Timetable"
+#~ msgstr "Расписание"
 
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr "У Вас в расписании произошли изменения."
+#~ msgid "There are current changes to your timetable."
+#~ msgstr "У Вас в расписании произошли изменения."
 
 #~ msgid "Options for timetables"
 #~ msgstr "Опции для расписания"
diff --git a/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
index cfc760f01b3206aaf7123340eccf529e9e4db354..e7015c68a7c47522f5d628d897256ea935a950ed 100644
--- a/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,418 +17,133 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr ""
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
+#: aleksis/apps/chronos/models.py:48
+msgid "Number of days shown in the plan"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
+#: aleksis/apps/chronos/models.py:52
+msgid "Show header box"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+msgid "The header box shows affected teachers/groups."
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
+#: aleksis/apps/chronos/models.py:60
+msgid "Revision which triggered the last update"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
+msgid "Automatic plan"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
+msgid "Automatic plans"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
+#: aleksis/apps/chronos/models.py:147
+msgid "Can view all room timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
+#: aleksis/apps/chronos/models.py:148
+msgid "Can view all group timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
+#: aleksis/apps/chronos/models.py:149
+msgid "Can view all person timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
+#: aleksis/apps/chronos/models.py:150
+msgid "Can view timetable overview"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
+#: aleksis/apps/chronos/models.py:151
+msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
+#: aleksis/apps/chronos/models.py:166
+msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
+#: aleksis/apps/chronos/models.py:169
+msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
+#: aleksis/apps/chronos/models.py:173
+msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
+#: aleksis/apps/chronos/models.py:191
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
+#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
+msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
+#: aleksis/apps/chronos/models.py:210
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
+msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
+#: aleksis/apps/chronos/models.py:467
+msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
+#: aleksis/apps/chronos/models.py:468
+msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
+#: aleksis/apps/chronos/models.py:475
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1194
-msgid "Number of days shown in the plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1198
-msgid "Show header box"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
-msgid "The header box shows affected teachers/groups."
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1206
-msgid "Revision which triggered the last update"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1278
-msgid "Automatic plan"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1279
-msgid "Automatic plans"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1293
-msgid "Can view all room timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1294
-msgid "Can view all group timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1295
-msgid "Can view all person timetables"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1296
-msgid "Can view timetable overview"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1298
-msgid "Can view all supervisions per day"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1313
-msgid "Start slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1316
-msgid "End slot number"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1320
-msgid "Course"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1353
-#: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
-msgid "Cancelled"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
-msgid "{} (instead of {})"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1614
-msgid "Lesson Event"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1615
-msgid "Lesson Events"
-msgstr ""
-
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:483
 msgid "Supervision: {}"
 msgstr ""
 
@@ -444,72 +159,39 @@ msgstr ""
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:53
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr ""
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 msgid "Supervision calendar feed color"
 msgstr ""
 
@@ -533,6 +215,10 @@ msgstr ""
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr ""
@@ -545,6 +231,10 @@ msgstr ""
 msgid "Time"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr ""
@@ -556,95 +246,3 @@ msgstr ""
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:2
 msgid "Areas"
 msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:48
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:55
-#, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:64
-#, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] ""
-msgstr[1] ""
-
-#: aleksis/apps/chronos/util/notifications.py:76
-#, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:82
-#, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:91
-#, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:99
-#, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:112
-#, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
-#, python-brace-format
-msgid "Groups: {groups}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
-#, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:128
-#, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:135
-#, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:145
-#, python-brace-format
-msgid "Subject: {subject}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:149
-#, python-brace-format
-msgid "Room: {room}"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:151
-#, python-brace-format
-msgid "Comment: {comment}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:154
-#, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr ""
-
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr ""
diff --git a/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
index 2a59feb53f2476f5088389967bf5de5ad9dbdd99..37c39e991658af7a313c6d082abb5bad239f057c 100644
--- a/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-18 14:23+0200\n"
+"POT-Creation-Date: 2024-10-17 10:48+0200\n"
 "PO-Revision-Date: 2022-06-22 19:59+0000\n"
 "Last-Translator: Serhii Horichenko <m@sgg.im>\n"
 "Language-Team: Ukrainian <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/uk/>\n"
@@ -18,428 +18,143 @@ msgstr ""
 "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
 "X-Generator: Weblate 4.12.1\n"
 
-#: aleksis/apps/chronos/mixins.py:25
-msgid "Linked validity range"
-msgstr "Пов'язаний діапазон"
-
-#: aleksis/apps/chronos/model_extensions.py:145
+#: aleksis/apps/chronos/model_extensions.py:9
 msgid "Can view group timetable"
 msgstr "Може переглядати групові розклади"
 
-#: aleksis/apps/chronos/model_extensions.py:149
+#: aleksis/apps/chronos/model_extensions.py:13
 msgid "Can view person timetable"
 msgstr "Може переглядати особисті розклади"
 
-#: aleksis/apps/chronos/models.py:87
-msgid "School term"
-msgstr "Навчальний рік"
-
-#: aleksis/apps/chronos/models.py:90 aleksis/apps/chronos/models.py:626
-#: aleksis/apps/chronos/models.py:1310
-msgid "Name"
-msgstr "Повне ім'я"
-
-#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
-#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
-msgid "Start date"
-msgstr "Дата початку"
-
-#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
-#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
-msgid "End date"
-msgstr "Дата закінчення"
-
-#: aleksis/apps/chronos/models.py:112
-msgid "The start date must be earlier than the end date."
-msgstr "Початкова дата повинна бути раніше кінцевої."
-
-#: aleksis/apps/chronos/models.py:118
-msgid "The validity range must be within the school term."
-msgstr "Контрольний період повинен бути в рамках навчального року."
-
-#: aleksis/apps/chronos/models.py:125
-msgid "There is already a validity range for this time or a part of this time."
-msgstr "На цей час або на частину цього часу контрольний період вже призначений."
-
-#: aleksis/apps/chronos/models.py:132
-msgid "Validity range"
-msgstr "Контрольний період"
-
-#: aleksis/apps/chronos/models.py:133
-msgid "Validity ranges"
-msgstr "Контрольні періоди"
-
-#: aleksis/apps/chronos/models.py:149
-msgid "Week day"
-msgstr "День тижня"
-
-#: aleksis/apps/chronos/models.py:151
-msgid "Number of period"
-msgstr "Номер урока"
-
-#: aleksis/apps/chronos/models.py:153
-msgid "Start time"
-msgstr "Час початку"
-
-#: aleksis/apps/chronos/models.py:154
-msgid "End time"
-msgstr "Час закінчення"
-
-#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
-#: aleksis/apps/chronos/models.py:1122
-msgid "Time period"
-msgstr "Час уроку"
-
-#: aleksis/apps/chronos/models.py:325
-msgid "Time periods"
-msgstr "Час уроків"
-
-#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
-#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
-msgid "Short name"
-msgstr "Коротке ім'я"
-
-#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
-#: aleksis/apps/chronos/models.py:811
-msgid "Long name"
-msgstr "Довге ім'я"
-
-#: aleksis/apps/chronos/models.py:332
-msgid "Foreground colour"
-msgstr "Основний колір"
-
-#: aleksis/apps/chronos/models.py:333
-msgid "Background colour"
-msgstr "Колір фону"
-
-#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
-#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
-#: aleksis/apps/chronos/models.py:1345
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
-msgid "Subject"
-msgstr "Предмет"
-
-#: aleksis/apps/chronos/models.py:341
-msgid "Subjects"
-msgstr "Предмети"
-
-#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
-#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
-#: aleksis/apps/chronos/models.py:1338
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
-#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
-msgid "Teachers"
-msgstr "Викладачі"
-
-#: aleksis/apps/chronos/models.py:358
-msgid "Periods"
-msgstr "Години"
-
-#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
-#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
-msgid "Groups"
-msgstr "Групи"
-
-#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
-#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
-#: aleksis/apps/chronos/models.py:1454
-msgid "Lesson"
-msgstr "Урок"
-
-#: aleksis/apps/chronos/models.py:395 aleksis/apps/chronos/models.py:1306
-msgid "Lessons"
-msgstr "Уроки"
-
-#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
-msgid "Week"
-msgstr "Тиждень"
-
-#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
-msgid "Year"
-msgstr "Рік"
-
-#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
-msgid "Lesson period"
-msgstr "Урок за розкладом"
-
-#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
-#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
-#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
-msgid "Room"
-msgstr "Кімната"
-
-#: aleksis/apps/chronos/models.py:428
-msgid "Cancelled?"
-msgstr "Скасовано?"
-
-#: aleksis/apps/chronos/models.py:430
-msgid "Cancelled for teachers?"
-msgstr "Скасовано для викладачів?"
-
-#: aleksis/apps/chronos/models.py:433 aleksis/apps/chronos/models.py:692
-#: aleksis/apps/chronos/models.py:734 aleksis/apps/chronos/models.py:1147
-#: aleksis/apps/chronos/models.py:1357
-msgid "Comment"
-msgstr "Коментар"
-
-#: aleksis/apps/chronos/models.py:437
-msgid "Lessons can only be either substituted or cancelled."
-msgstr "Уроки можуть бути лише або замінені, або скасовані."
-
-#: aleksis/apps/chronos/models.py:477
-msgid "Lesson substitution"
-msgstr "Заміна уроку"
-
-#: aleksis/apps/chronos/models.py:478
-msgid "Lesson substitutions"
-msgstr "Заміни уроків"
-
-#: aleksis/apps/chronos/models.py:621
-msgid "Lesson periods"
-msgstr "Навчальні години"
-
-#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
-msgid "Absence reason"
-msgstr "Причина відсутності"
-
-#: aleksis/apps/chronos/models.py:636
-msgid "Absence reasons"
-msgstr "Причини відсутності"
-
-#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
-#: aleksis/apps/chronos/models.py:943
-msgid "Teacher"
-msgstr "Викладач"
-
-#: aleksis/apps/chronos/models.py:665
-msgid "Group"
-msgstr "Група"
-
-#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
-msgid "Start period"
-msgstr "Початок уроків"
-
-#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
-msgid "End period"
-msgstr "Закінчення уроків"
-
-#: aleksis/apps/chronos/models.py:702
-msgid "Unknown absence"
-msgstr "Відсутність без пояснень"
-
-#: aleksis/apps/chronos/models.py:707
-msgid "Absence"
-msgstr "Відсутність"
-
-#: aleksis/apps/chronos/models.py:708
-msgid "Absences"
-msgstr "Відсутності"
-
-#: aleksis/apps/chronos/models.py:719
-msgid "Date of exam"
-msgstr "Дата іспиту"
-
-#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
-#: aleksis/apps/chronos/models.py:977
-msgid "Title"
-msgstr "Назва"
-
-#: aleksis/apps/chronos/models.py:739
-msgid "Exam"
-msgstr "Іспит"
-
-#: aleksis/apps/chronos/models.py:740
-msgid "Exams"
-msgstr "Іспити"
-
-#: aleksis/apps/chronos/models.py:749
-msgid "Comments"
-msgstr "Коментарі"
-
-#: aleksis/apps/chronos/models.py:788
-msgid "Holiday"
-msgstr "Вихідний"
-
-#: aleksis/apps/chronos/models.py:789
-msgid "Holidays"
-msgstr "Вихідні"
-
-#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
-msgid "Supervision area"
-msgstr "Зона контролю"
-
-#: aleksis/apps/chronos/models.py:804
-msgid "Supervision areas"
-msgstr "Зони контролю"
-
-#: aleksis/apps/chronos/models.py:816
-msgid "Time period after break starts"
-msgstr "Навчання після перерви починається"
-
-#: aleksis/apps/chronos/models.py:824
-msgid "Time period before break ends"
-msgstr "Навчання перед перервою закінчується"
-
-#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
-msgid "Break"
-msgstr "Перерва"
-
-#: aleksis/apps/chronos/models.py:865
-msgid "Breaks"
-msgstr "Перерви"
-
-#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
-#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
-msgid "Supervision"
-msgstr "Контроль"
-
-#: aleksis/apps/chronos/models.py:924 aleksis/apps/chronos/models.py:1622
-msgid "Supervisions"
-msgstr "Контролі"
-
-#: aleksis/apps/chronos/models.py:932
-msgid "Date"
-msgstr "Дата"
-
-#: aleksis/apps/chronos/models.py:966
-msgid "Supervision substitution"
-msgstr "Заміна контролю"
-
-#: aleksis/apps/chronos/models.py:967
-msgid "Supervision substitutions"
-msgstr "Заміни контролю"
-
-#: aleksis/apps/chronos/models.py:985
-msgid "Start time period"
-msgstr "Початок навчання"
-
-#: aleksis/apps/chronos/models.py:991
-msgid "End time period"
-msgstr "Закінчення навчання"
-
-#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
-#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
-msgid "Rooms"
-msgstr "Кімнати"
-
-#: aleksis/apps/chronos/models.py:1005
-#, python-brace-format
-msgid "Event {pk}"
-msgstr "Подія {pk}"
-
-#: aleksis/apps/chronos/models.py:1103
-msgid "Event"
-msgstr "Подія"
-
-#: aleksis/apps/chronos/models.py:1104
-msgid "Events"
-msgstr "Події"
-
-#: aleksis/apps/chronos/models.py:1152
-msgid "Related exam"
-msgstr "Пов'язані іспити"
-
-#: aleksis/apps/chronos/models.py:1181
-msgid "Extra lesson"
-msgstr "Додатковий урок"
-
-#: aleksis/apps/chronos/models.py:1182
-msgid "Extra lessons"
-msgstr "Додаткові уроки"
-
-#: aleksis/apps/chronos/models.py:1194
+#: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr "Кількість днів для відображення у плані"
 
-#: aleksis/apps/chronos/models.py:1198
+#: aleksis/apps/chronos/models.py:52
 msgid "Show header box"
 msgstr "Показати заголовок"
 
-#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
+#: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
 msgid "The header box shows affected teachers/groups."
 msgstr "Заголовок показує викладачів/групи на яких це впливає."
 
-#: aleksis/apps/chronos/models.py:1206
+#: aleksis/apps/chronos/models.py:60
 msgid "Revision which triggered the last update"
 msgstr "Ревізія, яка запустила останнє оновлення"
 
-#: aleksis/apps/chronos/models.py:1278
+#: aleksis/apps/chronos/models.py:132
 msgid "Automatic plan"
 msgstr "Автоматичний план"
 
-#: aleksis/apps/chronos/models.py:1279
+#: aleksis/apps/chronos/models.py:133
 msgid "Automatic plans"
 msgstr "Автоматичні плани"
 
-#: aleksis/apps/chronos/models.py:1293
+#: aleksis/apps/chronos/models.py:147
 msgid "Can view all room timetables"
 msgstr "Може переглядати розклади усіх кімнат"
 
-#: aleksis/apps/chronos/models.py:1294
+#: aleksis/apps/chronos/models.py:148
 msgid "Can view all group timetables"
 msgstr "Може переглядати розклади усіх груп"
 
-#: aleksis/apps/chronos/models.py:1295
+#: aleksis/apps/chronos/models.py:149
 msgid "Can view all person timetables"
 msgstr "Може переглядати розклади усіх осіб"
 
-#: aleksis/apps/chronos/models.py:1296
+#: aleksis/apps/chronos/models.py:150
 msgid "Can view timetable overview"
 msgstr "Може переглядати загальний розклад"
 
-#: aleksis/apps/chronos/models.py:1297
-msgid "Can view all lessons per day"
-msgstr "Може переглядати усі уроки окремого дня"
-
-#: aleksis/apps/chronos/models.py:1298
+#: aleksis/apps/chronos/models.py:151
 #, fuzzy
-#| msgid "Can view all lessons per day"
-msgid "Can view all supervisions per day"
-msgstr "Може переглядати усі уроки окремого дня"
+#| msgid "No substitutions available."
+msgid "Can view substitutions table"
+msgstr "Заміни недоступні."
+
+#: aleksis/apps/chronos/models.py:159
+msgid "Lessons"
+msgstr "Уроки"
+
+#: aleksis/apps/chronos/models.py:163
+msgid "Name"
+msgstr "Повне ім'я"
 
-#: aleksis/apps/chronos/models.py:1313
+#: aleksis/apps/chronos/models.py:166
 #, fuzzy
 #| msgid "Start time"
 msgid "Start slot number"
 msgstr "Час початку"
 
-#: aleksis/apps/chronos/models.py:1316
+#: aleksis/apps/chronos/models.py:169
 #, fuzzy
 #| msgid "End time"
 msgid "End slot number"
 msgstr "Час закінчення"
 
-#: aleksis/apps/chronos/models.py:1320
+#: aleksis/apps/chronos/models.py:173
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1353
+#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+msgid "Groups"
+msgstr "Групи"
+
+#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+msgid "Rooms"
+msgstr "Кімнати"
+
+#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
+#: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
+msgid "Teachers"
+msgstr "Викладачі"
+
+#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+msgid "Subject"
+msgstr "Предмет"
+
+#: aleksis/apps/chronos/models.py:206
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr "Скасовано"
 
-#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
-#: aleksis/apps/chronos/models.py:1425
+#: aleksis/apps/chronos/models.py:210
+msgid "Comment"
+msgstr "Коментар"
+
+#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
+#: aleksis/apps/chronos/models.py:278
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:1614
+#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+msgid "Lesson"
+msgstr "Урок"
+
+#: aleksis/apps/chronos/models.py:467
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Event"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:1615
+#: aleksis/apps/chronos/models.py:468
 #, fuzzy
 #| msgid "Lessons"
 msgid "Lesson Events"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:1630
+#: aleksis/apps/chronos/models.py:475
+msgid "Supervisions"
+msgstr "Контролі"
+
+#: aleksis/apps/chronos/models.py:483
 #, fuzzy
 #| msgid "Supervision"
 msgid "Supervision: {}"
@@ -459,78 +174,43 @@ msgstr "Використовувати батьківські групи в ог
 msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
 msgstr "Якщо урок або заміна має лише одну групу і ця група має батьківські групи, відображати ці батьківські групи замість дійсної групи."
 
-#: aleksis/apps/chronos/preferences.py:42
-msgid "Shorten groups in timetable views"
-msgstr "Скорочувати групи в огляді розкладу"
-
-#: aleksis/apps/chronos/preferences.py:43
-msgid "If there are more groups than the set limit, they will be collapsed."
-msgstr "Якщо груп більше за встановлений ліміт, вони будуть згруповані."
-
-#: aleksis/apps/chronos/preferences.py:51
-msgid "Limit of groups for shortening of groups"
-msgstr "Ліміт груп для скорочення груп"
-
-#: aleksis/apps/chronos/preferences.py:53
-#, fuzzy
-#| msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-msgstr "Якщо користувач активує скорочення груп, і кількість груп буде більше за ліміт, вони згруповуватимуться."
-
-#: aleksis/apps/chronos/preferences.py:65
+#: aleksis/apps/chronos/preferences.py:44
 #, fuzzy
 #| msgid "Lesson substitutions"
 msgid "Relevant days for substitution plans"
 msgstr "Заміни уроків"
 
-#: aleksis/apps/chronos/preferences.py:82
+#: aleksis/apps/chronos/preferences.py:61
 msgid "Time when substitution plans switch to the next day"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:91
+#: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
 msgstr "Кількість днів для відображення у друкованому вигляді замін"
 
-#: aleksis/apps/chronos/preferences.py:99
+#: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr "Відображати заголовок в огляді замін"
 
-#: aleksis/apps/chronos/preferences.py:109
+#: aleksis/apps/chronos/preferences.py:88
 msgid "Show parent groups in header box in substitution views instead of original groups"
 msgstr "У заголовку в огляді замін відображати батьківські групи замість дійсних груп"
 
-#: aleksis/apps/chronos/preferences.py:118
-msgid "How many days in advance users should be notified about timetable changes?"
-msgstr "За скільки днів потрібно інформувати користувачів щодо змін у розкладі?"
-
-#: aleksis/apps/chronos/preferences.py:126
-msgid "Time for sending notifications about timetable changes"
-msgstr "Час для надсилання сповіщень щодо змін у розкладі"
-
-#: aleksis/apps/chronos/preferences.py:129
-msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-msgstr "Це використовується лише для планування сповіщень, які не впливають на навчальний час, встановлений вище. Усі інші сповіщення, які впливають на найближчі дні, надсилаються негайно."
-
-#: aleksis/apps/chronos/preferences.py:140
-#: aleksis/apps/chronos/preferences.py:148
-msgid "Send notifications for current timetable changes"
-msgstr "Надіслати сповіщення щодо поточних змін у розкладі"
-
-#: aleksis/apps/chronos/preferences.py:158
+#: aleksis/apps/chronos/preferences.py:99
 #, fuzzy
 #| msgid "Shorten groups in timetable views"
 msgid "Group types to show in timetables"
 msgstr "Скорочувати групи в огляді розкладу"
 
-#: aleksis/apps/chronos/preferences.py:159
+#: aleksis/apps/chronos/preferences.py:100
 msgid "If you leave it empty, all groups will be shown."
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:169
+#: aleksis/apps/chronos/preferences.py:110
 msgid "Lesson calendar feed color"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:181
+#: aleksis/apps/chronos/preferences.py:122
 #, fuzzy
 #| msgid "Supervision area"
 msgid "Supervision calendar feed color"
@@ -556,6 +236,10 @@ msgstr "На які групи впливає"
 msgid "all day"
 msgstr ""
 
+#: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+msgid "Supervision"
+msgstr "Контроль"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:11
 msgid "Print: Substitutions"
 msgstr "Друк: Заміни"
@@ -570,6 +254,10 @@ msgstr "Заміни"
 msgid "Time"
 msgstr "Розклад"
 
+#: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+msgid "Room"
+msgstr "Кімната"
+
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:31
 msgid "Notes"
 msgstr "Нотатки"
@@ -582,99 +270,303 @@ msgstr "Заміни недоступні."
 msgid "Areas"
 msgstr ""
 
-#: aleksis/apps/chronos/util/notifications.py:48
+#~ msgid "Linked validity range"
+#~ msgstr "Пов'язаний діапазон"
+
+#~ msgid "School term"
+#~ msgstr "Навчальний рік"
+
+#~ msgid "Start date"
+#~ msgstr "Дата початку"
+
+#~ msgid "End date"
+#~ msgstr "Дата закінчення"
+
+#~ msgid "The start date must be earlier than the end date."
+#~ msgstr "Початкова дата повинна бути раніше кінцевої."
+
+#~ msgid "The validity range must be within the school term."
+#~ msgstr "Контрольний період повинен бути в рамках навчального року."
+
+#~ msgid "There is already a validity range for this time or a part of this time."
+#~ msgstr "На цей час або на частину цього часу контрольний період вже призначений."
+
+#~ msgid "Validity range"
+#~ msgstr "Контрольний період"
+
+#~ msgid "Validity ranges"
+#~ msgstr "Контрольні періоди"
+
+#~ msgid "Week day"
+#~ msgstr "День тижня"
+
+#~ msgid "Number of period"
+#~ msgstr "Номер урока"
+
+#~ msgid "Start time"
+#~ msgstr "Час початку"
+
+#~ msgid "End time"
+#~ msgstr "Час закінчення"
+
+#~ msgid "Time period"
+#~ msgstr "Час уроку"
+
+#~ msgid "Time periods"
+#~ msgstr "Час уроків"
+
+#~ msgid "Short name"
+#~ msgstr "Коротке ім'я"
+
+#~ msgid "Long name"
+#~ msgstr "Довге ім'я"
+
+#~ msgid "Foreground colour"
+#~ msgstr "Основний колір"
+
+#~ msgid "Background colour"
+#~ msgstr "Колір фону"
+
+#~ msgid "Subjects"
+#~ msgstr "Предмети"
+
+#~ msgid "Periods"
+#~ msgstr "Години"
+
+#~ msgid "Week"
+#~ msgstr "Тиждень"
+
+#~ msgid "Year"
+#~ msgstr "Рік"
+
+#~ msgid "Lesson period"
+#~ msgstr "Урок за розкладом"
+
+#~ msgid "Cancelled?"
+#~ msgstr "Скасовано?"
+
+#~ msgid "Cancelled for teachers?"
+#~ msgstr "Скасовано для викладачів?"
+
+#~ msgid "Lessons can only be either substituted or cancelled."
+#~ msgstr "Уроки можуть бути лише або замінені, або скасовані."
+
+#~ msgid "Lesson substitution"
+#~ msgstr "Заміна уроку"
+
+#~ msgid "Lesson substitutions"
+#~ msgstr "Заміни уроків"
+
+#~ msgid "Lesson periods"
+#~ msgstr "Навчальні години"
+
+#~ msgid "Absence reason"
+#~ msgstr "Причина відсутності"
+
+#~ msgid "Absence reasons"
+#~ msgstr "Причини відсутності"
+
+#~ msgid "Teacher"
+#~ msgstr "Викладач"
+
+#~ msgid "Group"
+#~ msgstr "Група"
+
+#~ msgid "Start period"
+#~ msgstr "Початок уроків"
+
+#~ msgid "End period"
+#~ msgstr "Закінчення уроків"
+
+#~ msgid "Unknown absence"
+#~ msgstr "Відсутність без пояснень"
+
+#~ msgid "Absence"
+#~ msgstr "Відсутність"
+
+#~ msgid "Absences"
+#~ msgstr "Відсутності"
+
+#~ msgid "Date of exam"
+#~ msgstr "Дата іспиту"
+
+#~ msgid "Title"
+#~ msgstr "Назва"
+
+#~ msgid "Exam"
+#~ msgstr "Іспит"
+
+#~ msgid "Exams"
+#~ msgstr "Іспити"
+
+#~ msgid "Comments"
+#~ msgstr "Коментарі"
+
+#~ msgid "Holiday"
+#~ msgstr "Вихідний"
+
+#~ msgid "Holidays"
+#~ msgstr "Вихідні"
+
+#~ msgid "Supervision area"
+#~ msgstr "Зона контролю"
+
+#~ msgid "Supervision areas"
+#~ msgstr "Зони контролю"
+
+#~ msgid "Time period after break starts"
+#~ msgstr "Навчання після перерви починається"
+
+#~ msgid "Time period before break ends"
+#~ msgstr "Навчання перед перервою закінчується"
+
+#~ msgid "Break"
+#~ msgstr "Перерва"
+
+#~ msgid "Breaks"
+#~ msgstr "Перерви"
+
+#~ msgid "Date"
+#~ msgstr "Дата"
+
+#~ msgid "Supervision substitution"
+#~ msgstr "Заміна контролю"
+
+#~ msgid "Supervision substitutions"
+#~ msgstr "Заміни контролю"
+
+#~ msgid "Start time period"
+#~ msgstr "Початок навчання"
+
+#~ msgid "End time period"
+#~ msgstr "Закінчення навчання"
+
 #, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-msgstr "Урок {subject} на {period} уроці у {day} скасований."
+#~ msgid "Event {pk}"
+#~ msgstr "Подія {pk}"
+
+#~ msgid "Event"
+#~ msgstr "Подія"
+
+#~ msgid "Events"
+#~ msgstr "Події"
+
+#~ msgid "Related exam"
+#~ msgstr "Пов'язані іспити"
+
+#~ msgid "Extra lesson"
+#~ msgstr "Додатковий урок"
+
+#~ msgid "Extra lessons"
+#~ msgstr "Додаткові уроки"
+
+#~ msgid "Can view all lessons per day"
+#~ msgstr "Може переглядати усі уроки окремого дня"
+
+#, fuzzy
+#~| msgid "Can view all lessons per day"
+#~ msgid "Can view all supervisions per day"
+#~ msgstr "Може переглядати усі уроки окремого дня"
+
+#~ msgid "Shorten groups in timetable views"
+#~ msgstr "Скорочувати групи в огляді розкладу"
+
+#~ msgid "If there are more groups than the set limit, they will be collapsed."
+#~ msgstr "Якщо груп більше за встановлений ліміт, вони будуть згруповані."
+
+#~ msgid "Limit of groups for shortening of groups"
+#~ msgstr "Ліміт груп для скорочення груп"
+
+#, fuzzy
+#~| msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgstr "Якщо користувач активує скорочення груп, і кількість груп буде більше за ліміт, вони згруповуватимуться."
+
+#~ msgid "How many days in advance users should be notified about timetable changes?"
+#~ msgstr "За скільки днів потрібно інформувати користувачів щодо змін у розкладі?"
+
+#~ msgid "Time for sending notifications about timetable changes"
+#~ msgstr "Час для надсилання сповіщень щодо змін у розкладі"
+
+#~ msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
+#~ msgstr "Це використовується лише для планування сповіщень, які не впливають на навчальний час, встановлений вище. Усі інші сповіщення, які впливають на найближчі дні, надсилаються негайно."
+
+#~ msgid "Send notifications for current timetable changes"
+#~ msgstr "Надіслати сповіщення щодо поточних змін у розкладі"
 
-#: aleksis/apps/chronos/util/notifications.py:55
 #, python-brace-format
-msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-msgstr "Урок {subject} на {period} уроці у {day} зараз дещо змінений."
+#~ msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
+#~ msgstr "Урок {subject} на {period} уроці у {day} скасований."
 
-#: aleksis/apps/chronos/util/notifications.py:64
 #, python-brace-format
-msgid "The teacher {old} is substituted by {new}."
-msgid_plural "The teachers {old} are substituted by {new}."
-msgstr[0] "Викладач {old} замінений викладачем {new}."
-msgstr[1] "Викладачі {old} замінені викладачами {new}."
-msgstr[2] "Викладачі {old} замінені викладачами {new}."
-msgstr[3] "Викладачі {old} замінені викладачами {new}."
-
-#: aleksis/apps/chronos/util/notifications.py:76
+#~ msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
+#~ msgstr "Урок {subject} на {period} уроці у {day} зараз дещо змінений."
+
 #, python-brace-format
-msgid "The subject is changed to {subject}."
-msgstr "Предмет замінений на {subject}."
+#~ msgid "The teacher {old} is substituted by {new}."
+#~ msgid_plural "The teachers {old} are substituted by {new}."
+#~ msgstr[0] "Викладач {old} замінений викладачем {new}."
+#~ msgstr[1] "Викладачі {old} замінені викладачами {new}."
+#~ msgstr[2] "Викладачі {old} замінені викладачами {new}."
+#~ msgstr[3] "Викладачі {old} замінені викладачами {new}."
 
-#: aleksis/apps/chronos/util/notifications.py:82
 #, python-brace-format
-msgid "The lesson is moved from {old} to {new}."
-msgstr "Урок перенесений з {old} до {new}."
+#~ msgid "The subject is changed to {subject}."
+#~ msgstr "Предмет замінений на {subject}."
 
-#: aleksis/apps/chronos/util/notifications.py:91
 #, python-brace-format
-msgid "There is an additional comment: {comment}."
-msgstr "Маємо додатковий коментар: {comment}."
+#~ msgid "The lesson is moved from {old} to {new}."
+#~ msgstr "Урок перенесений з {old} до {new}."
 
-#: aleksis/apps/chronos/util/notifications.py:99
 #, python-brace-format
-msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-msgstr "Маємо подію, яка розпочинається {date_start}, на {period_from}. уроці та закінчується {date_end}, на {period_to}. уроці:"
+#~ msgid "There is an additional comment: {comment}."
+#~ msgstr "Маємо додатковий коментар: {comment}."
 
-#: aleksis/apps/chronos/util/notifications.py:112
 #, python-brace-format
-msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-msgstr "Маємо подію на {date} з {period_from}. уроку до кінця {period_to}. уроку:"
+#~ msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
+#~ msgstr "Маємо подію, яка розпочинається {date_start}, на {period_from}. уроці та закінчується {date_end}, на {period_to}. уроці:"
 
-#: aleksis/apps/chronos/util/notifications.py:123
-#: aleksis/apps/chronos/util/notifications.py:143
 #, python-brace-format
-msgid "Groups: {groups}"
-msgstr "Групи: {groups}"
+#~ msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
+#~ msgstr "Маємо подію на {date} з {period_from}. уроку до кінця {period_to}. уроку:"
 
-#: aleksis/apps/chronos/util/notifications.py:125
-#: aleksis/apps/chronos/util/notifications.py:147
 #, python-brace-format
-msgid "Teachers: {teachers}"
-msgstr "Викладачі: {teachers}"
+#~ msgid "Groups: {groups}"
+#~ msgstr "Групи: {groups}"
 
-#: aleksis/apps/chronos/util/notifications.py:128
 #, python-brace-format
-msgid "Rooms: {rooms}"
-msgstr "Кімнати: {rooms}"
+#~ msgid "Teachers: {teachers}"
+#~ msgstr "Викладачі: {teachers}"
 
-#: aleksis/apps/chronos/util/notifications.py:135
 #, python-brace-format
-msgid "There is an extra lesson on {date} in the {period}. period:"
-msgstr "Маємо додатковий урок {date} на {period}. уроці:"
+#~ msgid "Rooms: {rooms}"
+#~ msgstr "Кімнати: {rooms}"
 
-#: aleksis/apps/chronos/util/notifications.py:145
 #, python-brace-format
-msgid "Subject: {subject}"
-msgstr "Предмет: {subject}"
+#~ msgid "There is an extra lesson on {date} in the {period}. period:"
+#~ msgstr "Маємо додатковий урок {date} на {period}. уроці:"
 
-#: aleksis/apps/chronos/util/notifications.py:149
 #, python-brace-format
-msgid "Room: {room}"
-msgstr "Кімната: {room}"
+#~ msgid "Subject: {subject}"
+#~ msgstr "Предмет: {subject}"
 
-#: aleksis/apps/chronos/util/notifications.py:151
 #, python-brace-format
-msgid "Comment: {comment}."
-msgstr "Коментар: {comment}."
+#~ msgid "Room: {room}"
+#~ msgstr "Кімната: {room}"
 
-#: aleksis/apps/chronos/util/notifications.py:154
 #, python-brace-format
-msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-msgstr "Контроль {old}, {date} між {period_from}. та {period_to}. уроками у зоні {area} замінений на {new}."
+#~ msgid "Comment: {comment}."
+#~ msgstr "Коментар: {comment}."
 
-#: aleksis/apps/chronos/util/notifications.py:204
-msgid "Timetable"
-msgstr "Розклад"
+#, python-brace-format
+#~ msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
+#~ msgstr "Контроль {old}, {date} між {period_from}. та {period_to}. уроками у зоні {area} замінений на {new}."
+
+#~ msgid "Timetable"
+#~ msgstr "Розклад"
 
-#: aleksis/apps/chronos/util/notifications.py:205
-msgid "There are current changes to your timetable."
-msgstr "У Вашому розкладі є зміни."
+#~ msgid "There are current changes to your timetable."
+#~ msgstr "У Вашому розкладі є зміни."
 
 #~ msgid "Options for timetables"
 #~ msgstr "Опції для розкладів"
diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index 1b41f62fb640e93902269dffedbfff401a6e368b..9689ef0b167a6706de08e037c1a1a747a6f3a1b7 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -1,77 +1,13 @@
-from collections.abc import Iterable
-from datetime import date, datetime, timedelta
 from enum import Enum
-from typing import TYPE_CHECKING, Optional, Union
+from typing import Optional, Union
 
-from django.db import models
-from django.db.models import ExpressionWrapper, F, Func, Q, QuerySet, Value
-from django.db.models.fields import DateField
-from django.db.models.functions import Concat
+from django.db.models import Q
 
-from calendarweek import CalendarWeek
-
-from aleksis.apps.chronos.util.date import week_weekday_from_date, week_weekday_to_date
 from aleksis.apps.cursus.models import Course
 from aleksis.core.managers import (
-    AlekSISBaseManagerWithoutMigrations,
-    DateRangeQuerySetMixin,
     RecurrencePolymorphicQuerySet,
-    SchoolTermRelatedQuerySet,
 )
 from aleksis.core.models import Group, Person, Room
-from aleksis.core.util.core_helpers import get_site_preferences
-
-if TYPE_CHECKING:
-    from .models import Holiday, LessonPeriod, ValidityRange
-
-
-class ValidityRangeQuerySet(QuerySet, DateRangeQuerySetMixin):
-    """Custom query set for validity ranges."""
-
-
-class ValidityRangeRelatedQuerySet(QuerySet):
-    """Custom query set for all models related to validity ranges."""
-
-    def within_dates(self, start: date, end: date) -> "ValidityRangeRelatedQuerySet":
-        """Filter for all objects within a date range."""
-        return self.filter(validity__date_start__lte=end, validity__date_end__gte=start)
-
-    def in_week(self, wanted_week: CalendarWeek) -> "ValidityRangeRelatedQuerySet":
-        """Filter for all objects within a calendar week."""
-        return self.within_dates(wanted_week[0], wanted_week[6])
-
-    def on_day(self, day: date) -> "ValidityRangeRelatedQuerySet":
-        """Filter for all objects on a certain day."""
-        return self.within_dates(day, day)
-
-    def for_validity_range(self, validity_range: "ValidityRange") -> "ValidityRangeRelatedQuerySet":
-        return self.filter(validity=validity_range)
-
-    def for_current_or_all(self) -> "ValidityRangeRelatedQuerySet":
-        """Get all objects related to current validity range.
-
-        If there is no current validity range, it will return all objects.
-        """
-        from aleksis.apps.chronos.models import ValidityRange
-
-        current_validity_range = ValidityRange.current
-        if current_validity_range:
-            return self.for_validity_range(current_validity_range)
-        else:
-            return self
-
-    def for_current_or_none(self) -> Union["ValidityRangeRelatedQuerySet", None]:
-        """Get all objects related to current validity range.
-
-        If there is no current validity range, it will return `None`.
-        """
-        from aleksis.apps.chronos.models import ValidityRange
-
-        current_validity_range = ValidityRange.current
-        if current_validity_range:
-            return self.for_validity_range(current_validity_range)
-        else:
-            return None
 
 
 class TimetableType(Enum):
@@ -88,834 +24,157 @@ class TimetableType(Enum):
         return cls.__members__.get(s.upper())
 
 
-class LessonPeriodManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to lesson periods."""
-
-    def get_queryset(self):
-        """Ensure all related lesson data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related(
-                "lesson",
-                "lesson__subject",
-                "period",
-                "room",
-                "lesson__validity",
-                "lesson__validity__school_term",
-            )
-            .prefetch_related(
-                "lesson__groups",
-                "lesson__groups__parent_groups",
-                "lesson__teachers",
-                "substitutions",
-            )
-        )
+class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
+    """Queryset with special query methods for lesson events."""
 
+    @staticmethod
+    def for_teacher_q(teacher: Union[int, Person]) -> Q:
+        """Get all lesson events for a certain person as teacher (including amends)."""
+        from .models import LessonEvent
 
-class LessonSubstitutionManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to lesson substitutions."""
-
-    def get_queryset(self):
-        """Ensure all related lesson data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related(
-                "lesson_period",
-                "lesson_period__lesson",
-                "lesson_period__lesson__subject",
-                "subject",
-                "lesson_period__period",
-                "room",
-                "lesson_period__room",
-            )
-            .prefetch_related(
-                "lesson_period__lesson__groups",
-                "lesson_period__lesson__groups__parent_groups",
-                "teachers",
-                "lesson_period__lesson__teachers",
-            )
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, teachers=teacher)
+            .values_list("amended_by__pk", flat=True)
+            .union(LessonEvent.objects.filter(teachers=teacher).values_list("pk", flat=True))
         )
+        return Q(pk__in=amended)
 
+    def for_teacher(self, teacher: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events for a certain person as teacher (including amends)."""
+        return self.filter(self.for_teacher_q(teacher)).distinct()
 
-class SupervisionManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to supervisions."""
-
-    def get_queryset(self):
-        """Ensure all related data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related(
-                "teacher",
-                "area",
-                "break_item",
-                "break_item__after_period",
-                "break_item__before_period",
-            )
-        )
-
+    @staticmethod
+    def for_participant_q(person: Union[int, Person]) -> Q:
+        """Get all lesson events the person participates in (including amends)."""
+        from .models import LessonEvent
 
-class SupervisionSubstitutionManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to supervision substitutions."""
-
-    def get_queryset(self):
-        """Ensure all related data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related(
-                "teacher",
-                "supervision",
-                "supervision__teacher",
-                "supervision__area",
-                "supervision__break_item",
-                "supervision__break_item__after_period",
-                "supervision__break_item__before_period",
-            )
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, groups__members=person)
+            .values_list("amended_by__pk", flat=True)
+            .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True))
         )
+        return Q(pk__in=amended)
 
+    def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events the person participates in (including amends)."""
+        return self.filter(self.for_participant_q(person)).distinct()
 
-class EventManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to events."""
-
-    def get_queryset(self):
-        """Ensure all related data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related("period_from", "period_to")
-            .prefetch_related(
-                "groups",
-                "groups__school_term",
-                "groups__parent_groups",
-                "teachers",
-                "rooms",
-            )
-        )
-
+    @staticmethod
+    def for_group_q(group: Union[int, Group]) -> Q:
+        """Get all lesson events for a certain group (including amends/as parent group)."""
+        from .models import LessonEvent
 
-class ExtraLessonManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to extra lessons."""
-
-    def get_queryset(self):
-        """Ensure all related data is loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related("room", "period", "subject")
-            .prefetch_related(
-                "groups",
-                "groups__school_term",
-                "groups__parent_groups",
-                "teachers",
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, groups=group)
+            .values_list("amended_by__pk", flat=True)
+            .union(
+                LessonEvent.objects.filter(
+                    amended_by__isnull=False, groups__parent_groups=group
+                ).values_list("amended_by__pk", flat=True)
+            )
+            .union(LessonEvent.objects.filter(groups=group).values_list("pk", flat=True))
+            .union(
+                LessonEvent.objects.filter(groups__parent_groups=group).values_list("pk", flat=True)
             )
         )
+        return Q(pk__in=amended)
 
+    def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
+        """Get all lesson events for a certain group (including amends/as parent group)."""
+        return self.filter(self.for_group_q(group)).distinct()
 
-class BreakManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to breaks."""
-
-    def get_queryset(self):
-        """Ensure all related data is loaded as well."""
-        return super().get_queryset().select_related("before_period", "after_period")
-
-
-class WeekQuerySetMixin:
-    def annotate_week(self, week: Union[CalendarWeek]):
-        """Annotate all lessons in the QuerySet with the number of the provided calendar week."""
-        return self.annotate(
-            _week=models.Value(week.week, models.IntegerField()),
-            _year=models.Value(week.year, models.IntegerField()),
-        )
+    @staticmethod
+    def for_room_q(room: Union[int, Room]) -> Q:
+        """Get all lesson events for a certain room (including amends)."""
+        from .models import LessonEvent
 
-    def alias_week(self, week: Union[CalendarWeek]):
-        """Add an alias to all lessons in the QuerySet with the number of the calendar week."""
-        return self.alias(
-            _week=models.Value(week.week, models.IntegerField()),
-            _year=models.Value(week.year, models.IntegerField()),
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, rooms=room)
+            .values_list("amended_by__pk", flat=True)
+            .union(LessonEvent.objects.filter(rooms=room).values_list("pk", flat=True))
         )
+        return Q(pk__in=amended)
 
+    def for_room(self, room: Union[int, Room]) -> "LessonEventQuerySet":
+        """Get all lesson events for a certain room (including amends)."""
+        return self.filter(self.for_room_q(room)).distinct()
 
-class GroupByPeriodsMixin:
-    def group_by_periods(self, is_week: bool = False) -> dict:
-        """Group a QuerySet of objects with attribute period by period numbers and weekdays."""
-        per_period = {}
-        for obj in self:
-            period = obj.period.period
-            weekday = obj.period.weekday
-
-            if period not in per_period:
-                per_period[period] = [] if not is_week else {}
-
-            if is_week and weekday not in per_period[period]:
-                per_period[period][weekday] = []
-
-            if not is_week:
-                per_period[period].append(obj)
-            else:
-                per_period[period][weekday].append(obj)
-
-        return per_period
-
-
-class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
-    """Overrides default QuerySet to add specific methods for lesson data."""
-
-    # Overridden in the subclasses. Swaps the paths to the base lesson period
-    # and to any substitutions depending on whether the query is run on a
-    # lesson period or a substitution
-    _period_path = None
-    _subst_path = None
+    @staticmethod
+    def for_course_q(course: Union[int, Course]) -> Q:
+        """Get all lesson events for a certain course (including amends)."""
+        from .models import LessonEvent
 
-    def within_dates(self, start: date, end: date):
-        """Filter for all lessons within a date range."""
-        return self.filter(
-            **{
-                self._period_path + "lesson__validity__date_start__lte": start,
-                self._period_path + "lesson__validity__date_end__gte": end,
-            }
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, course=course)
+            .values_list("amended_by__pk", flat=True)
+            .union(LessonEvent.objects.filter(course=course).values_list("pk", flat=True))
         )
+        return Q(pk__in=amended)
 
-    def in_week(self, wanted_week: CalendarWeek):
-        """Filter for all lessons within a calendar week."""
-        return self.within_dates(
-            wanted_week[0] + timedelta(days=1) * (F(self._period_path + "period__weekday")),
-            wanted_week[0] + timedelta(days=1) * (F(self._period_path + "period__weekday")),
-        ).annotate_week(wanted_week)
-
-    def on_day(self, day: date):
-        """Filter for all lessons on a certain day."""
-        week, weekday = week_weekday_from_date(day)
-
-        return (
-            self.within_dates(day, day)
-            .filter(**{self._period_path + "period__weekday": weekday})
-            .annotate_week(week)
-        )
+    def for_course(self, course: Union[int, Course]) -> "LessonEventQuerySet":
+        """Get all lesson events for a certain course (including amends)."""
+        return self.filter(self.for_course_q(course)).distinct()
 
-    def at_time(self, when: Optional[datetime] = None):
-        """Filter for the lessons taking place at a certain point in time."""
-        now = when or datetime.now()
-        week, weekday = week_weekday_from_date(now.date())
-
-        return self.filter(
-            **{
-                self._period_path + "lesson__validity__date_start__lte": now.date(),
-                self._period_path + "lesson__validity__date_end__gte": now.date(),
-                self._period_path + "period__weekday": now.weekday(),
-                self._period_path + "period__time_start__lte": now.time(),
-                self._period_path + "period__time_end__gte": now.time(),
-            }
-        ).annotate_week(week)
-
-    def filter_participant(self, person: Union[Person, int]):
-        """Filter for all lessons a participant (student) attends."""
-        return self.filter(Q(**{self._period_path + "lesson__groups__members": person}))
-
-    def filter_group(self, group: Union[Group, int]):
-        """Filter for all lessons a group (class) regularly attends."""
-        if isinstance(group, int):
-            group = Group.objects.get(pk=group)
-
-        if group.parent_groups.all():
-            # Prevent to show lessons multiple times
-            return self.filter(Q(**{self._period_path + "lesson__groups": group}))
-        else:
-            return self.filter(
-                Q(**{self._period_path + "lesson__groups": group})
-                | Q(**{self._period_path + "lesson__groups__parent_groups": group})
-            )
+    @staticmethod
+    def for_person_q(person: Union[int, Person]) -> Q:
+        """Get all lesson events for a certain person (as teacher/participant, including amends)."""
+        from .models import LessonEvent
 
-    def filter_groups(self, groups: Iterable[Group]) -> QuerySet:
-        """Filter for all lessons one of the groups regularly attends."""
-        return self.filter(
-            Q(**{self._period_path + "lesson__groups__in": groups})
-            | Q(**{self._period_path + "lesson__groups__parent_groups__in": groups})
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, teachers=person)
+            .values_list("amended_by__pk", flat=True)
+            .union(
+                LessonEvent.objects.filter(
+                    amended_by__isnull=False, groups__members=person
+                ).values_list("amended_by__pk", flat=True)
+            )
+            .union(LessonEvent.objects.filter(teachers=person).values_list("pk", flat=True))
+            .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True))
         )
+        return Q(pk__in=amended)
 
-    def filter_teacher(self, teacher: Union[Person, int], is_smart: bool = True):
-        """Filter for all lessons given by a certain teacher."""
-        qs1 = self.filter(**{self._period_path + "lesson__teachers": teacher})
-        qs2 = self.filter(
-            **{
-                self._subst_path + "teachers": teacher,
-                self._subst_path + "week": F("_week"),
-                self._subst_path + "year": F("_year"),
-            }
-        )
+    def for_person(self, person: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events for a certain person (as teacher/participant, including amends)."""
+        return self.filter(self.for_person_q(person)).distinct()
 
-        if is_smart:
-            return qs1.union(qs2)
-        else:
-            return qs1
-
-    def filter_room(self, room: Union["Room", int], is_smart: bool = True):
-        """Filter for all lessons taking part in a certain room."""
-        qs1 = self.filter(**{self._period_path + "room": room})
-        qs2 = self.filter(
-            **{
-                self._subst_path + "room": room,
-                self._subst_path + "week": F("_week"),
-                self._subst_path + "year": F("_year"),
-            }
-        )
+    @staticmethod
+    def related_to_person_q(person: Union[int, Person]) -> Q:
+        """Get all lesson events a certain person is allowed to see.
 
-        if is_smart:
-            return qs1.union(qs2)
-        else:
-            return qs1
-
-    def filter_from_type(
-        self, type_: TimetableType, obj: Union[Person, Group, "Room", int], is_smart: bool = True
-    ) -> Optional[models.QuerySet]:
-        """Filter lesson data for a group, teacher or room by provided type."""
-        if type_ == TimetableType.GROUP:
-            return self.filter_group(obj)
-        elif type_ == TimetableType.TEACHER:
-            return self.filter_teacher(obj, is_smart=is_smart)
-        elif type_ == TimetableType.ROOM:
-            return self.filter_room(obj, is_smart=is_smart)
-        else:
-            return None
-
-    def filter_from_person(self, person: Person) -> Optional[models.QuerySet]:
-        """Filter lesson data for a person."""
-        type_ = person.timetable_type
-
-        if type_ == TimetableType.TEACHER:
-            # Teacher
-
-            return self.filter_teacher(person)
-
-        elif type_ == TimetableType.GROUP:
-            # Student
-
-            return self.filter_participant(person)
-
-        else:
-            # If no student or teacher
-            return None
-
-    def daily_lessons_for_person(
-        self, person: Person, wanted_day: date
-    ) -> Optional[models.QuerySet]:
-        """Filter lesson data on a day by a person."""
-        if person.timetable_type is None:
-            return None
-
-        lesson_periods = self.on_day(wanted_day).filter_from_person(person)
-
-        return lesson_periods
-
-    def group_by_validity(self) -> dict["ValidityRange", list["LessonPeriod"]]:
-        """Group lesson periods by validity range as dictionary."""
-        lesson_periods_by_validity = {}
-        for lesson_period in self:
-            lesson_periods_by_validity.setdefault(lesson_period.lesson.validity, [])
-            lesson_periods_by_validity[lesson_period.lesson.validity].append(lesson_period)
-        return lesson_periods_by_validity
-
-    def next_lesson(
-        self, reference: "LessonPeriod", offset: Optional[int] = 1
-    ) -> Optional["LessonPeriod"]:
-        """Get another lesson in an ordered set of lessons.
-
-        By default, it returns the next lesson in the set. By passing the offset argument,
-        the n-th next lesson can be selected. By passing a negative number, the n-th
-        previous lesson can be selected.
-
-        This function will handle week, year and validity range changes automatically
-        if the queryset contains enough lesson data.
+        This includes all lesson events the person is assigned to as
+        teacher/participant/group owner/parent group owner,
+        including those amended.
         """
-        # Group lesson periods by validity to handle validity range changes correctly
-        lesson_periods_by_validity = self.group_by_validity()
-        validity_ranges = list(lesson_periods_by_validity.keys())
-
-        # List with lesson periods in the validity range of the reference lesson period
-        current_lesson_periods = lesson_periods_by_validity[reference.lesson.validity]
-        pks = [lesson_period.pk for lesson_period in current_lesson_periods]
-
-        # Position of the reference lesson period
-        index = pks.index(reference.id)
-
-        next_index = index + offset
-        if next_index > len(pks) - 1:
-            next_index %= len(pks)
-            week = reference._week + 1
-        elif next_index < 0:
-            next_index = len(pks) + next_index
-            week = reference._week - 1
-        else:
-            week = reference._week
-
-        # Check if selected week makes a year change necessary
-        year = reference._year
-        if week < 1:
-            year -= 1
-            week = CalendarWeek.get_last_week_of_year(year).week
-        elif week > CalendarWeek.get_last_week_of_year(year).week:
-            year += 1
-            week = 1
-
-        # Get the next lesson period in this validity range and it's date
-        # to check whether the validity range has to be changed
-        week = CalendarWeek(week=week, year=year)
-        next_lesson_period = current_lesson_periods[next_index]
-        next_lesson_period_date = week_weekday_to_date(week, next_lesson_period.period.weekday)
-
-        validity_index = validity_ranges.index(next_lesson_period.lesson.validity)
-
-        # If date of next lesson period is out of validity range (smaller) ...
-        if next_lesson_period_date < next_lesson_period.lesson.validity.date_start:
-            # ... we have to get the lesson period from the previous validity range
-            if validity_index == 0:
-                # There are no validity ranges (and thus no lessons)
-                # in the school term before this lesson period
-                return None
-
-            # Get new validity range and last lesson period of this validity range
-            new_validity = validity_ranges[validity_index - 1]
-            next_lesson_period = lesson_periods_by_validity[new_validity][-1]
-
-            # Build new week with the date from the new validity range/lesson period
-            week = CalendarWeek(
-                week=new_validity.date_end.isocalendar()[1], year=new_validity.date_end.year
-            )
+        from .models import LessonEvent
 
-        # If date of next lesson period is out of validity range (larger) ...
-        elif next_lesson_period_date > next_lesson_period.lesson.validity.date_end:
-            # ... we have to get the lesson period from the next validity range
-            if validity_index >= len(validity_ranges) - 1:
-                # There are no validity ranges (and thus no lessons)
-                # in the school term after this lesson period
-                return None
-
-            # Get new validity range and first lesson period of this validity range
-            new_validity = validity_ranges[validity_index + 1]
-            next_lesson_period = lesson_periods_by_validity[new_validity][0]
-
-            # Build new week with the date from the new validity range/lesson period
-            week = CalendarWeek(
-                week=new_validity.date_start.isocalendar()[1], year=new_validity.date_start.year
+        amended = (
+            LessonEvent.objects.filter(amended_by__isnull=False, teachers=person)
+            .values_list("amended_by__pk", flat=True)
+            .union(
+                LessonEvent.objects.filter(
+                    amended_by__isnull=False, groups__members=person
+                ).values_list("amended_by__pk", flat=True)
             )
-
-        # Do a new query here to be able to annotate the new week
-        return self.annotate_week(week).get(pk=next_lesson_period.pk)
-
-
-class LessonPeriodQuerySet(LessonDataQuerySet, GroupByPeriodsMixin):
-    """QuerySet with custom query methods for lesson periods."""
-
-    _period_path = ""
-    _subst_path = "substitutions__"
-
-
-class LessonSubstitutionQuerySet(LessonDataQuerySet):
-    """QuerySet with custom query methods for substitutions."""
-
-    _period_path = "lesson_period__"
-    _subst_path = ""
-
-    def within_dates(self, start: date, end: date):
-        """Filter for all substitutions within a date range."""
-        start_week = CalendarWeek.from_date(start)
-        end_week = CalendarWeek.from_date(end)
-        return self.filter(
-            week__gte=start_week.week,
-            week__lte=end_week.week,
-            year__gte=start_week.year,
-            year__lte=end_week.year,
-        ).filter(
-            Q(
-                week=start_week.week,
-                year=start_week.year,
-                lesson_period__period__weekday__gte=start.weekday(),
+            .union(
+                LessonEvent.objects.filter(
+                    amended_by__isnull=False, groups__owners=person
+                ).values_list("amended_by__pk", flat=True)
             )
-            | Q(
-                week=end_week.week,
-                year=end_week.year,
-                lesson_period__period__weekday__lte=end.weekday(),
+            .union(
+                LessonEvent.objects.filter(
+                    amended_by__isnull=False, groups__parent_groups__owners=person
+                ).values_list("amended_by__pk", flat=True)
             )
-            | (
-                ~Q(week=start_week.week, year=start_week.year)
-                & ~Q(week=end_week.week, year=end_week.year)
+            .union(LessonEvent.objects.filter(teachers=person).values_list("pk", flat=True))
+            .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True))
+            .union(LessonEvent.objects.filter(groups__owners=person).values_list("pk", flat=True))
+            .union(
+                LessonEvent.objects.filter(groups__parent_groups__owners=person).values_list(
+                    "pk", flat=True
+                )
             )
         )
-
-    def in_week(self, wanted_week: CalendarWeek):
-        """Filter for all lessons within a calendar week."""
-        return self.filter(week=wanted_week.week, year=wanted_week.year).annotate_week(wanted_week)
-
-    def on_day(self, day: date):
-        """Filter for all lessons on a certain day."""
-        week, weekday = week_weekday_from_date(day)
-
-        return self.in_week(week).filter(lesson_period__period__weekday=weekday)
-
-    def at_time(self, when: Optional[datetime] = None):
-        """Filter for the lessons taking place at a certain point in time."""
-        now = when or datetime.now()
-
-        return self.on_day(now.date()).filter(
-            lesson_period__period__time_start__lte=now.time(),
-            lesson_period__period__time_end__gte=now.time(),
-        )
-
-    def affected_lessons(self):
-        """Return all lessons which are affected by selected substitutions."""
-        from .models import Lesson  # noaq
-
-        return Lesson.objects.filter(lesson_periods__substitutions__in=self).distinct()
-
-    def affected_teachers(self):
-        """Get affected teachers.
-
-        Return all teachers which are affected by
-        selected substitutions (as substituted or substituting).
-        """
-        return (
-            Person.objects.filter(
-                Q(lessons_as_teacher__in=self.affected_lessons()) | Q(lesson_substitutions__in=self)
-            )
-            .distinct()
-            .order_by("short_name")
-        )
-
-    def affected_groups(self):
-        """Return all groups which are affected by selected substitutions."""
-        return (
-            Group.objects.filter(lessons__in=self.affected_lessons())
-            .distinct()
-            .order_by("short_name")
-        )
-
-
-class DateRangeQuerySetMixin:
-    """QuerySet with custom query methods for models with date and period ranges.
-
-    Filterable fields: date_start, date_end, period_from, period_to
-    """
-
-    def within_dates(self, start: date, end: date):
-        """Filter for all events within a date range."""
-        return self.filter(date_start__lte=end, date_end__gte=start)
-
-    def in_week(self, wanted_week: CalendarWeek):
-        """Filter for all events within a calendar week."""
-        return self.within_dates(wanted_week[0], wanted_week[6])
-
-    def on_day(self, day: date):
-        """Filter for all events on a certain day."""
-        return self.within_dates(day, day)
-
-    def at_time(self, when: Optional[datetime] = None):
-        """Filter for the events taking place at a certain point in time."""
-        now = when or datetime.now()
-
-        return self.on_day(now.date()).filter(
-            period_from__time_start__lte=now.time(), period_to__time_end__gte=now.time()
-        )
-
-    def exclude_holidays(self, holidays: Iterable["Holiday"]) -> QuerySet:
-        """Exclude all objects which are in the provided holidays."""
-        q = Q()
-        for holiday in holidays:
-            q = q | Q(date_start__lte=holiday.date_end, date_end__gte=holiday.date_start)
-        return self.exclude(q)
-
-
-class AbsenceQuerySet(DateRangeQuerySetMixin, SchoolTermRelatedQuerySet):
-    """QuerySet with custom query methods for absences."""
-
-    def absent_teachers(self):
-        return Person.objects.filter(absences__in=self).distinct().order_by("short_name")
-
-    def absent_groups(self):
-        return Group.objects.filter(absences__in=self).distinct().order_by("short_name")
-
-    def absent_rooms(self):
-        return Person.objects.filter(absences__in=self).distinct().order_by("short_name")
-
-
-class HolidayQuerySet(QuerySet, DateRangeQuerySetMixin):
-    """QuerySet with custom query methods for holidays."""
-
-    def get_all_days(self) -> list[date]:
-        """Get all days included in the selected holidays."""
-        holiday_days = []
-        for holiday in self:
-            holiday_days += list(holiday.get_days())
-        return holiday_days
-
-
-class SupervisionQuerySet(ValidityRangeRelatedQuerySet, WeekQuerySetMixin):
-    """QuerySet with custom query methods for supervisions."""
-
-    def filter_by_weekday(self, weekday: int):
-        """Filter supervisions by weekday."""
-        return self.filter(
-            Q(break_item__before_period__weekday=weekday)
-            | Q(break_item__after_period__weekday=weekday)
-        )
-
-    def filter_by_teacher(self, teacher: Union[Person, int]):
-        """Filter for all supervisions given by a certain teacher."""
-        if self.count() > 0:
-            if hasattr(self[0], "_week"):
-                week = CalendarWeek(week=self[0]._week, year=self[0]._year)
-            else:
-                week = CalendarWeek.current_week()
-
-            dates = [week[w] for w in range(0, 7)]
-
-            return self.filter(
-                Q(substitutions__teacher=teacher, substitutions__date__in=dates)
-                | Q(teacher=teacher)
-            )
-
-        return self
-
-
-class TimetableQuerySet(models.QuerySet):
-    """Common query set methods for objects in timetables.
-
-    Models need following fields:
-    - groups
-    - teachers
-    - rooms (_multiple_rooms=True)/room (_multiple_rooms=False)
-    """
-
-    _multiple_rooms = True
-
-    def filter_participant(self, person: Union[Person, int]):
-        """Filter for all objects a participant (student) attends."""
-        return self.filter(Q(groups__members=person))
-
-    def filter_group(self, group: Union[Group, int]):
-        """Filter for all objects a group (class) attends."""
-        if isinstance(group, int):
-            group = Group.objects.get(pk=group)
-
-        if group.parent_groups.all():
-            # Prevent to show lessons multiple times
-            return self.filter(groups=group)
-        else:
-            return self.filter(Q(groups=group) | Q(groups__parent_groups=group))
-
-    def filter_groups(self, groups: Iterable[Group]) -> QuerySet:
-        """Filter for all objects one of the groups attends."""
-        return self.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)).distinct()
-
-    def filter_teacher(self, teacher: Union[Person, int]):
-        """Filter for all lessons given by a certain teacher."""
-        return self.filter(teachers=teacher)
-
-    def filter_room(self, room: Union["Room", int]):
-        """Filter for all objects taking part in a certain room."""
-        if self._multiple_rooms:
-            return self.filter(rooms=room)
-        else:
-            return self.filter(room=room)
-
-    def filter_from_type(
-        self, type_: TimetableType, obj: Union[Group, Person, "Room", int]
-    ) -> Optional[models.QuerySet]:
-        """Filter data for a group, teacher or room by provided type."""
-        if type_ == TimetableType.GROUP:
-            return self.filter_group(obj)
-        elif type_ == TimetableType.TEACHER:
-            return self.filter_teacher(obj)
-        elif type_ == TimetableType.ROOM:
-            return self.filter_room(obj)
-        else:
-            return None
-
-    def filter_from_person(self, person: Person) -> Optional[models.QuerySet]:
-        """Filter data by person."""
-        type_ = person.timetable_type
-
-        if type_ == TimetableType.TEACHER:
-            # Teacher
-
-            return self.filter_teacher(person)
-
-        elif type_ == TimetableType.GROUP:
-            # Student
-
-            return self.filter_participant(person)
-
-        else:
-            # If no student or teacher
-            return None
-
-
-class EventQuerySet(DateRangeQuerySetMixin, SchoolTermRelatedQuerySet, TimetableQuerySet):
-    """QuerySet with custom query methods for events."""
-
-    def annotate_day(self, day: date):
-        """Annotate all events in the QuerySet with the provided date."""
-        return self.annotate(_date=models.Value(day, models.DateField()))
-
-    def alias_day(self, day: date):
-        """Add an alias to all events in the QuerySet with the provided date."""
-        return self.alias(_date=models.Value(day, models.DateField()))
-
-
-class ExtraLessonQuerySet(TimetableQuerySet, SchoolTermRelatedQuerySet, GroupByPeriodsMixin):
-    """QuerySet with custom query methods for extra lessons."""
-
-    _multiple_rooms = False
-
-    def within_dates(self, start: date, end: date):
-        """Filter all extra lessons within a specific time range."""
-        return self.alias_day().filter(day__gte=start, day__lte=end)
-
-    def on_day(self, day: date):
-        """Filter all extra lessons on a day."""
-        return self.within_dates(day, day)
-
-    def _get_weekday_to_date(self):
-        """Get DB function to convert a weekday to a date."""
-        return ExpressionWrapper(
-            Func(
-                Concat(F("year"), F("week")),
-                Value("IYYYIW"),
-                output_field=DateField(),
-                function="TO_DATE",
-            )
-            + F("period__weekday"),
-            output_field=DateField(),
-        )
-
-    def annotate_day(self):
-        return self.annotate(day=self._get_weekday_to_date())
-
-    def alias_day(self):
-        return self.alias(day=self._get_weekday_to_date())
-
-    def exclude_holidays(self, holidays: Iterable["Holiday"]) -> QuerySet:
-        """Exclude all extra lessons which are in the provided holidays."""
-        q = Q()
-        for holiday in holidays:
-            q = q | Q(day__lte=holiday.date_end, day__gte=holiday.date_start)
-        return self.alias_day().exclude(q)
-
-
-class GroupPropertiesMixin:
-    """Mixin for common group properties.
-
-    Necessary method: `get_groups`
-    """
-
-    @property
-    def group_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([group.short_name for group in self.get_groups()])
-
-    @property
-    def group_short_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([group.short_name for group in self.get_groups()])
-
-    @property
-    def groups_to_show(self) -> QuerySet[Group]:
-        groups = self.get_groups()
-        if (
-            groups.count() == 1
-            and groups[0].parent_groups.all()
-            and get_site_preferences()["chronos__use_parent_groups"]
-        ):
-            return groups[0].parent_groups.all()
-        else:
-            return groups
-
-    @property
-    def groups_to_show_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([group.short_name for group in self.groups_to_show])
-
-    @property
-    def groups_to_show_short_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([group.short_name for group in self.groups_to_show])
-
-
-class TeacherPropertiesMixin:
-    """Mixin for common teacher properties.
-
-    Necessary method: `get_teachers`
-    """
-
-    @property
-    def teacher_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([teacher.full_name for teacher in self.get_teachers()])
-
-    @property
-    def teacher_short_names(self, sep: str = ", ") -> str:
-        return sep.join([teacher.short_name for teacher in self.get_teachers()])
-
-
-class RoomPropertiesMixin:
-    """Mixin for common room properties.
-
-    Necessary method: `get_rooms`
-    """
-
-    @property
-    def room_names(self, sep: Optional[str] = ", ") -> str:
-        return sep.join([room.name for room in self.get_rooms()])
-
-    @property
-    def room_short_names(self, sep: str = ", ") -> str:
-        return sep.join([room.short_name for room in self.get_rooms()])
-
-
-class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
-    """Queryset with special query methods for lesson events."""
-
-    def for_teacher(self, teacher: Union[int, Person]) -> "LessonEventQuerySet":
-        """Get all lesson events for a certain person as teacher (including amends)."""
-        amended = self.filter(Q(amended_by__isnull=False) & (Q(teachers=teacher))).values_list(
-            "amended_by__pk", flat=True
-        )
-        return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct()
-
-    def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet":
-        """Get all lesson events the person participates in (including amends)."""
-        amended = self.filter(Q(amended_by__isnull=False) & Q(groups__members=person)).values_list(
-            "amended_by__pk", flat=True
-        )
-        return self.filter(Q(groups__members=person) | Q(pk__in=amended)).distinct()
-
-    def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
-        """Get all lesson events for a certain group (including amends/as parent group)."""
-        amended = self.filter(
-            Q(amended_by__isnull=False) & (Q(groups=group) | Q(groups__parent_groups=group))
-        ).values_list("amended_by__pk", flat=True)
-        return self.filter(
-            Q(groups=group) | Q(groups__parent_groups=group) | Q(pk__in=amended)
-        ).distinct()
-
-    def for_room(self, room: Union[int, Room]) -> "LessonEventQuerySet":
-        """Get all lesson events for a certain room (including amends)."""
-        amended = self.filter(Q(amended_by__isnull=False) & (Q(rooms=room))).values_list(
-            "amended_by__pk", flat=True
-        )
-        return self.filter(Q(rooms=room) | Q(pk__in=amended)).distinct()
-
-    def for_course(self, course: Union[int, Course]) -> "LessonEventQuerySet":
-        """Get all lesson events for a certain course (including amends)."""
-        amended = self.filter(Q(amended_by__isnull=False) & (Q(course=course))).values_list(
-            "amended_by__pk", flat=True
-        )
-        return self.filter(Q(course=course) | Q(pk__in=amended)).distinct()
-
-    def for_person(self, person: Union[int, Person]) -> "LessonEventQuerySet":
-        """Get all lesson events for a certain person (as teacher/participant, including amends)."""
-        amended = self.filter(
-            Q(amended_by__isnull=False) & (Q(teachers=person) | Q(groups__members=person))
-        ).values_list("amended_by__pk", flat=True)
-        return self.filter(
-            Q(teachers=person) | Q(groups__members=person) | Q(pk__in=amended)
-        ).distinct()
+        return Q(pk__in=amended)
 
     def related_to_person(self, person: Union[int, Person]) -> "LessonEventQuerySet":
         """Get all lesson events a certain person is allowed to see.
@@ -924,34 +183,34 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
         teacher/participant/group owner/parent group owner,
         including those amended.
         """
-        amended = self.filter(
-            Q(amended_by__isnull=False)
-            & (
-                Q(teachers=person)
-                | Q(groups__members=person)
-                | Q(groups__owners=person)
-                | Q(groups__parent_groups__owners=person)
-            )
-        ).values_list("amended_by__pk", flat=True)
-        return self.filter(
-            Q(teachers=person)
-            | Q(groups__members=person)
-            | Q(groups__owners=person)
-            | Q(groups__parent_groups__owners=person)
-            | Q(pk__in=amended)
-        ).distinct()
+        return self.filter(self.related_to_person_q(person)).distinct()
+
+    @staticmethod
+    def not_amended_q() -> Q:
+        """Get all lesson events that are not amended."""
+        return Q(amended_by__isnull=True)
 
     def not_amended(self) -> "LessonEventQuerySet":
         """Get all lesson events that are not amended."""
-        return self.filter(amended_by__isnull=True)
+        return self.filter(self.not_amended_q())
+
+    @staticmethod
+    def not_amending_q() -> Q:
+        """Get all lesson events that are not amending other events."""
+        return Q(amends__isnull=True)
 
     def not_amending(self) -> "LessonEventQuerySet":
         """Get all lesson events that are not amending other events."""
-        return self.filter(amends__isnull=True)
+        return self.filter(self.not_amending_q())
+
+    @staticmethod
+    def amending_q() -> Q:
+        """Get all lesson events that are amending other events."""
+        return Q(amends__isnull=False)
 
     def amending(self) -> "LessonEventQuerySet":
         """Get all lesson events that are amending other events."""
-        return self.filter(amends__isnull=False)
+        return self.filter(self.amending_q())
 
 
 class SupervisionEventQuerySet(LessonEventQuerySet):
diff --git a/aleksis/apps/chronos/migrations/0001_initial.py b/aleksis/apps/chronos/migrations/0001_initial.py
index 758f099cade786ea7cbab3cb6a4616847fa2c2d6..cf9aea3066896d3724d8f7757a00cb136f3ffc80 100644
--- a/aleksis/apps/chronos/migrations/0001_initial.py
+++ b/aleksis/apps/chronos/migrations/0001_initial.py
@@ -116,8 +116,6 @@ class Migration(migrations.Migration):
             },
             bases=(
                 models.Model,
-                aleksis.apps.chronos.managers.GroupPropertiesMixin,
-                aleksis.apps.chronos.managers.TeacherPropertiesMixin,
             ),
             managers=[("objects", aleksis.core.managers.AlekSISBaseManager()),],
         ),
@@ -699,7 +697,7 @@ class Migration(migrations.Migration):
                 "verbose_name": "Extra lesson",
                 "verbose_name_plural": "Extra lessons",
             },
-            bases=(models.Model, aleksis.apps.chronos.managers.GroupPropertiesMixin),
+            bases=(models.Model,),
         ),
         migrations.CreateModel(
             name="Exam",
@@ -832,8 +830,6 @@ class Migration(migrations.Migration):
             },
             bases=(
                 models.Model,
-                aleksis.apps.chronos.managers.GroupPropertiesMixin,
-                aleksis.apps.chronos.managers.TeacherPropertiesMixin,
             ),
         ),
         migrations.AddField(
diff --git a/aleksis/apps/chronos/migrations/0004_substitution_extra_lesson_year.py b/aleksis/apps/chronos/migrations/0004_substitution_extra_lesson_year.py
index cb7383592d167f7d3aa4bf203aaa8cee503d7551..eeb02c4822fd609781ac8b02839511237114a0a2 100644
--- a/aleksis/apps/chronos/migrations/0004_substitution_extra_lesson_year.py
+++ b/aleksis/apps/chronos/migrations/0004_substitution_extra_lesson_year.py
@@ -1,8 +1,7 @@
 # Generated by Django 3.0.9 on 2020-08-13 14:06
 
 from django.db import migrations, models
-
-import aleksis.apps.chronos.util.date
+from django.utils import timezone
 
 
 def migrate_data(apps, schema_editor):
@@ -19,7 +18,7 @@ def migrate_data(apps, schema_editor):
         sub.save()
 
     for extra_lesson in ExtraLesson.objects.using(db_alias).all():
-        year = aleksis.apps.chronos.util.date.get_current_year()
+        year = timezone.now().year
         extra_lesson.year = year
         extra_lesson.save()
 
@@ -36,7 +35,7 @@ class Migration(migrations.Migration):
             model_name="extralesson",
             name="year",
             field=models.IntegerField(
-                default=aleksis.apps.chronos.util.date.get_current_year,
+                default=lambda: timezone.now().year,
                 verbose_name="Year",
             ),
         ),
@@ -44,7 +43,7 @@ class Migration(migrations.Migration):
             model_name="lessonsubstitution",
             name="year",
             field=models.IntegerField(
-                default=aleksis.apps.chronos.util.date.get_current_year,
+                default=lambda: timezone.now().year,
                 verbose_name="Year",
             ),
         ),
diff --git a/aleksis/apps/chronos/migrations/0018_check_new_models.py b/aleksis/apps/chronos/migrations/0018_check_new_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..144a1af5ce41b1313157ac45c75aa55facf4f224
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0018_check_new_models.py
@@ -0,0 +1,30 @@
+from django.db import migrations, models
+
+from django.apps import apps as global_apps
+
+def check_for_migration(apps, schema_editor):
+    if global_apps.is_installed('aleksis.apps.lesrooster'):
+        return
+
+    ValidityRange = apps.get_model('chronos', 'ValidityRange')
+    Subject = apps.get_model('chronos', 'Subject')
+    AbsenceReason = apps.get_model('chronos', 'AbsenceReason')
+    Absence = apps.get_model('chronos', 'Absence')
+    Holiday = apps.get_model('chronos', 'Holiday')
+    SupervisionArea = apps.get_model('chronos', 'SupervisionArea')
+
+    model_types = [ValidityRange, Subject, AbsenceReason, Absence, Holiday, SupervisionArea]
+
+    for model in model_types:
+        if model.objects.exists():
+            raise RuntimeError("You have legacy data. Please install AlekSIS-App-Lesrooster to migrate them.")
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0017_optional_slot_number'),
+    ]
+
+    operations = [
+        migrations.RunPython(check_for_migration),
+    ]
diff --git a/aleksis/apps/chronos/migrations/0019_remove_old_models.py b/aleksis/apps/chronos/migrations/0019_remove_old_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6668aabe3248e1717a2b08fb9cdf90f8a37a4c9
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0019_remove_old_models.py
@@ -0,0 +1,260 @@
+# Generated by Django 5.0.8 on 2024-08-14 13:08
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0018_check_new_models'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='absence',
+            name='group',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='period_from',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='period_to',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='reason',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='room',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='school_term',
+        ),
+        migrations.RemoveField(
+            model_name='absence',
+            name='teacher',
+        ),
+        migrations.RemoveField(
+            model_name='break',
+            name='after_period',
+        ),
+        migrations.RemoveField(
+            model_name='break',
+            name='before_period',
+        ),
+        migrations.RemoveField(
+            model_name='break',
+            name='validity',
+        ),
+        migrations.RemoveField(
+            model_name='supervision',
+            name='break_item',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='groups',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='period_from',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='period_to',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='rooms',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='school_term',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='teachers',
+        ),
+        migrations.RemoveField(
+            model_name='exam',
+            name='lesson',
+        ),
+        migrations.RemoveField(
+            model_name='exam',
+            name='period_from',
+        ),
+        migrations.RemoveField(
+            model_name='exam',
+            name='period_to',
+        ),
+        migrations.RemoveField(
+            model_name='exam',
+            name='school_term',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='exam',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='groups',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='period',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='room',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='school_term',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='subject',
+        ),
+        migrations.RemoveField(
+            model_name='extralesson',
+            name='teachers',
+        ),
+        migrations.DeleteModel(
+            name='Holiday',
+        ),
+        migrations.RemoveField(
+            model_name='lesson',
+            name='groups',
+        ),
+        migrations.RemoveField(
+            model_name='lesson',
+            name='periods',
+        ),
+        migrations.RemoveField(
+            model_name='lesson',
+            name='subject',
+        ),
+        migrations.RemoveField(
+            model_name='lesson',
+            name='teachers',
+        ),
+        migrations.RemoveField(
+            model_name='lesson',
+            name='validity',
+        ),
+        migrations.RemoveField(
+            model_name='lessonperiod',
+            name='lesson',
+        ),
+        migrations.RemoveField(
+            model_name='lessonperiod',
+            name='period',
+        ),
+        migrations.RemoveField(
+            model_name='lessonperiod',
+            name='room',
+        ),
+        migrations.RemoveField(
+            model_name='lessonsubstitution',
+            name='lesson_period',
+        ),
+        migrations.RemoveField(
+            model_name='lessonsubstitution',
+            name='room',
+        ),
+        migrations.RemoveField(
+            model_name='lessonsubstitution',
+            name='subject',
+        ),
+        migrations.RemoveField(
+            model_name='lessonsubstitution',
+            name='teachers',
+        ),
+        migrations.RemoveField(
+            model_name='supervision',
+            name='area',
+        ),
+        migrations.RemoveField(
+            model_name='supervision',
+            name='teacher',
+        ),
+        migrations.RemoveField(
+            model_name='supervision',
+            name='validity',
+        ),
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='supervision',
+        ),
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='teacher',
+        ),
+        migrations.RemoveField(
+            model_name='timeperiod',
+            name='validity',
+        ),
+        migrations.RemoveField(
+            model_name='validityrange',
+            name='school_term',
+        ),
+        migrations.DeleteModel(
+            name='TimetableWidget',
+        ),
+        migrations.AlterModelOptions(
+            name='chronosglobalpermissions',
+            options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_substitutions', 'Can view substitutions table'))},
+        ),
+        migrations.AlterModelOptions(
+            name='supervisionevent',
+            options={'base_manager_name': 'objects'},
+        ),
+        migrations.DeleteModel(
+            name='AbsenceReason',
+        ),
+        migrations.DeleteModel(
+            name='Absence',
+        ),
+        migrations.DeleteModel(
+            name='Break',
+        ),
+        migrations.DeleteModel(
+            name='Event',
+        ),
+        migrations.DeleteModel(
+            name='Exam',
+        ),
+        migrations.DeleteModel(
+            name='ExtraLesson',
+        ),
+        migrations.DeleteModel(
+            name='Lesson',
+        ),
+        migrations.DeleteModel(
+            name='LessonPeriod',
+        ),
+        migrations.DeleteModel(
+            name='Subject',
+        ),
+        migrations.DeleteModel(
+            name='LessonSubstitution',
+        ),
+        migrations.DeleteModel(
+            name='SupervisionArea',
+        ),
+        migrations.DeleteModel(
+            name='Supervision',
+        ),
+        migrations.DeleteModel(
+            name='SupervisionSubstitution',
+        ),
+        migrations.DeleteModel(
+            name='TimePeriod',
+        ),
+        migrations.DeleteModel(
+            name='ValidityRange',
+        ),
+    ]
diff --git a/aleksis/apps/chronos/migrations/0018_add_global_permissions.py b/aleksis/apps/chronos/migrations/0020_add_global_permissions.py
similarity index 93%
rename from aleksis/apps/chronos/migrations/0018_add_global_permissions.py
rename to aleksis/apps/chronos/migrations/0020_add_global_permissions.py
index c51cd016c8b70e9c07818465589068168101bd61..4cb067f833d364df0b8869de44ac427a8522fe58 100644
--- a/aleksis/apps/chronos/migrations/0018_add_global_permissions.py
+++ b/aleksis/apps/chronos/migrations/0020_add_global_permissions.py
@@ -6,7 +6,7 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('chronos', '0017_optional_slot_number'),
+        ('chronos', '0019_remove_old_models'),
     ]
 
     operations = [
diff --git a/aleksis/apps/chronos/mixins.py b/aleksis/apps/chronos/mixins.py
deleted file mode 100644
index 8cb4dab29467236f950b5b9ff56f5fa7ab7da435..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/mixins.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from datetime import date
-from typing import Union
-
-from django.db import models
-from django.utils.translation import gettext as _
-
-from calendarweek import CalendarWeek
-
-from aleksis.apps.chronos.util.date import week_weekday_to_date
-from aleksis.core.managers import AlekSISBaseManagerWithoutMigrations
-from aleksis.core.mixins import ExtensibleModel
-
-from .managers import ValidityRangeRelatedQuerySet
-
-
-class ValidityRangeRelatedExtensibleModel(ExtensibleModel):
-    """Add relation to validity range."""
-
-    objects = AlekSISBaseManagerWithoutMigrations.from_queryset(ValidityRangeRelatedQuerySet)()
-
-    validity = models.ForeignKey(
-        "chronos.ValidityRange",
-        on_delete=models.CASCADE,
-        related_name="+",
-        verbose_name=_("Linked validity range"),
-        null=True,
-        blank=True,
-    )
-
-    class Meta:
-        abstract = True
-
-
-class WeekRelatedMixin:
-    @property
-    def date(self) -> date:
-        period = self.lesson_period.period if hasattr(self, "lesson_period") else self.period
-        return week_weekday_to_date(self.calendar_week, period.weekday)
-
-    @property
-    def calendar_week(self) -> CalendarWeek:
-        return CalendarWeek(week=self.week, year=self.year)
-
-
-class WeekAnnotationMixin:
-    def annotate_week(self, week: CalendarWeek):
-        """Annotate this lesson with the number of the provided calendar week."""
-        self._week = week.week
-        self._year = week.year
-
-    @property
-    def week(self) -> Union[CalendarWeek, None]:
-        """Get annotated week as `CalendarWeek`.
-
-        Defaults to `None` if no week is annotated.
-        """
-        if hasattr(self, "_week"):
-            return CalendarWeek(week=self._week, year=self._year)
-        else:
-            return None
diff --git a/aleksis/apps/chronos/model_extensions.py b/aleksis/apps/chronos/model_extensions.py
index 8046899166ec21096401b3f5aa3f7ee930989112..81f9260450b626fa56dd6b51a4e1b4cb5b579d83 100644
--- a/aleksis/apps/chronos/model_extensions.py
+++ b/aleksis/apps/chronos/model_extensions.py
@@ -1,143 +1,7 @@
-from datetime import date
-from typing import Optional, Union
-
-from django.dispatch import receiver
 from django.utils.translation import gettext_lazy as _
 
-from reversion.models import Revision
-
 from aleksis.apps.cursus.models import Course
-from aleksis.core.models import Announcement, Group, Person
-from aleksis.core.util.core_helpers import get_site_preferences
-
-from .managers import TimetableType
-from .models import Lesson, LessonPeriod
-from .util.change_tracker import timetable_data_changed
-from .util.notifications import send_notifications_for_object
-
-
-@Person.property_
-def is_teacher(self):
-    """Check if the user has lessons as a teacher."""
-    return self.lesson_periods_as_teacher.exists()
-
-
-@Person.property_
-def timetable_type(self) -> Optional[TimetableType]:
-    """Return which type of timetable this user has."""
-    if self.is_teacher:
-        return TimetableType.TEACHER
-    elif self.primary_group:
-        return TimetableType.GROUP
-    else:
-        return None
-
-
-@Person.property_
-def timetable_object(self) -> Optional[Union[Group, Person]]:
-    """Return the object which has the user's timetable."""
-    type_ = self.timetable_type
-
-    if type_ == TimetableType.TEACHER:
-        return self
-    elif type_ == TimetableType.GROUP:
-        return self.primary_group
-    else:
-        return None
-
-
-@Person.property_
-def lessons_as_participant(self):
-    """Return a `QuerySet` containing all `Lesson`s this person participates in (as student).
-
-    .. note:: Only available when AlekSIS-App-Chronos is installed.
-
-    :Date: 2019-11-07
-    :Authors:
-        - Dominik George <dominik.george@teckids.org>
-    """
-    return Lesson.objects.filter(groups__members=self)
-
-
-@Person.property_
-def lesson_periods_as_participant(self):
-    """Return a `QuerySet` containing all `LessonPeriod`s this person participates in (as student).
-
-    .. note:: Only available when AlekSIS-App-Chronos is installed.
-
-    :Date: 2019-11-07
-    :Authors:
-        - Dominik George <dominik.george@teckids.org>
-    """
-    return LessonPeriod.objects.filter(lesson__groups__members=self)
-
-
-@Person.property_
-def lesson_periods_as_teacher(self):
-    """Return a `QuerySet` containing all `Lesson`s this person gives (as teacher).
-
-    .. note:: Only available when AlekSIS-App-Chronos is installed.
-
-    :Date: 2019-11-07
-    :Authors:
-        - Dominik George <dominik.george@teckids.org>
-    """
-    return LessonPeriod.objects.filter(lesson__teachers=self)
-
-
-@Person.method
-def lessons_on_day(self, day: date):
-    """Get all lessons of this person (either as participant or teacher) on the given day."""
-    qs = LessonPeriod.objects.on_day(day).filter_from_person(self)
-    if qs:
-        # This is a union queryset, so order by must be after the union.
-        return qs.order_by("period__period")
-    return None
-
-
-@Person.method
-def _adjacent_lesson(
-    self, lesson_period: "LessonPeriod", day: date, offset: int = 1
-) -> Union["LessonPeriod", None]:
-    """Get next/previous lesson of the person (either as participant or teacher) on the same day."""
-    daily_lessons = self.lessons_on_day(day)
-
-    if not daily_lessons:
-        return None
-
-    ids = list(daily_lessons.values_list("id", flat=True))
-
-    # Check if the lesson period is one of the person's lesson periods on this day
-    # and return None if it's not so
-    if lesson_period.pk not in ids:
-        return None
-
-    index = ids.index(lesson_period.pk)
-
-    if (offset > 0 and index + offset < len(ids)) or (offset < 0 and index >= -offset):
-        return daily_lessons[index + offset]
-    else:
-        return None
-
-
-@Person.method
-def next_lesson(self, lesson_period: "LessonPeriod", day: date) -> Union["LessonPeriod", None]:
-    """Get next lesson of the person (either as participant or teacher) on the same day."""
-    return self._adjacent_lesson(lesson_period, day)
-
-
-@Person.method
-def previous_lesson(self, lesson_period: "LessonPeriod", day: date) -> Union["LessonPeriod", None]:
-    """Get previous lesson of the person (either as participant or teacher) on the same day."""
-    return self._adjacent_lesson(lesson_period, day, offset=-1)
-
-
-def for_timetables(cls):
-    """Return all announcements that should be shown in timetable views."""
-    return cls.objects.all()
-
-
-Announcement.class_method(for_timetables)
+from aleksis.core.models import Group, Person
 
 # Dynamically add extra permissions to Group and Person models in core and Course model in cursus
 # Note: requires migrate afterwards
@@ -153,16 +17,3 @@ Course.add_permission(
     "view_course_timetable",
     _("Can view course timetable"),
 )
-
-
-@receiver(timetable_data_changed)
-def send_notifications(sender: Revision, **kwargs):
-    """Send notifications to users about the changes."""
-    if not get_site_preferences()["chronos__send_notifications_site"]:
-        return
-
-    for change in sender.changes.values():
-        if change.deleted:
-            continue
-
-        send_notifications_for_object(change.instance)
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 880b92fcf361c9d690130202747101ad062debdb..d4856154598b401a0c5136cca0c77119842c625a 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -2,1188 +2,41 @@
 from __future__ import annotations
 
 import itertools
-from collections.abc import Iterable, Iterator
-from datetime import date, datetime, time, timedelta
-from itertools import chain
+from collections.abc import Iterable
+from datetime import date
 from typing import Any
 
 from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import PermissionDenied, ValidationError
 from django.core.validators import MinValueValidator
 from django.db import models
-from django.db.models import Max, Min, Q, QuerySet
-from django.db.models.functions import Coalesce
+from django.db.models import Q, QuerySet
 from django.dispatch import receiver
 from django.http import HttpRequest
 from django.template.loader import render_to_string
-from django.urls import reverse
 from django.utils import timezone
-from django.utils.formats import date_format
-from django.utils.functional import classproperty
 from django.utils.translation import gettext_lazy as _
 
-from cache_memoize import cache_memoize
-from calendarweek.django import CalendarWeek, i18n_day_abbr_choices_lazy, i18n_day_name_choices_lazy
-from colorfield.fields import ColorField
-from model_utils import FieldTracker
 from reversion.models import Revision, Version
 
 from aleksis.apps.chronos.managers import (
-    AbsenceQuerySet,
-    BreakManager,
-    EventManager,
-    EventQuerySet,
-    ExtraLessonManager,
-    ExtraLessonQuerySet,
-    GroupPropertiesMixin,
-    HolidayQuerySet,
     LessonEventQuerySet,
-    LessonPeriodManager,
-    LessonPeriodQuerySet,
-    LessonSubstitutionManager,
-    LessonSubstitutionQuerySet,
     SupervisionEventQuerySet,
-    SupervisionManager,
-    SupervisionQuerySet,
-    SupervisionSubstitutionManager,
-    TeacherPropertiesMixin,
-    ValidityRangeQuerySet,
-)
-from aleksis.apps.chronos.mixins import (
-    ValidityRangeRelatedExtensibleModel,
-    WeekAnnotationMixin,
-    WeekRelatedMixin,
 )
 from aleksis.apps.chronos.util.change_tracker import _get_substitution_models, substitutions_changed
-from aleksis.apps.chronos.util.date import get_current_year
-from aleksis.apps.chronos.util.format import format_m2m
 from aleksis.apps.cursus import models as cursus_models
 from aleksis.apps.cursus.models import Course
 from aleksis.apps.resint.models import LiveDocument
 from aleksis.core.managers import (
-    AlekSISBaseManagerWithoutMigrations,
     RecurrencePolymorphicManager,
 )
 from aleksis.core.mixins import (
-    ExtensibleModel,
     GlobalPermissionModel,
-    SchoolTermRelatedExtensibleModel,
 )
-from aleksis.core.models import CalendarEvent, Group, Person, Room, SchoolTerm
+from aleksis.core.models import CalendarEvent, Group, Person, Room
 from aleksis.core.util.core_helpers import get_site_preferences, has_person
 from aleksis.core.util.predicates import check_global_permission
 
 
-class ValidityRange(ExtensibleModel):
-    """Validity range model.
-
-    This is used to link data to a validity range.
-    """
-
-    objects = AlekSISBaseManagerWithoutMigrations.from_queryset(ValidityRangeQuerySet)()
-
-    school_term = models.ForeignKey(
-        SchoolTerm,
-        on_delete=models.CASCADE,
-        verbose_name=_("School term"),
-        related_name="validity_ranges",
-    )
-    name = models.CharField(verbose_name=_("Name"), max_length=255, blank=True)
-
-    date_start = models.DateField(verbose_name=_("Start date"))
-    date_end = models.DateField(verbose_name=_("End date"))
-
-    @classmethod
-    @cache_memoize(3600)
-    def get_current(cls, day: date | None = None):
-        if not day:
-            day = timezone.now().date()
-        try:
-            return cls.objects.on_day(day).first()
-        except ValidityRange.DoesNotExist:
-            return None
-
-    @classproperty
-    def current(cls):
-        return cls.get_current()
-
-    def clean(self):
-        """Ensure there is only one validity range at each point of time."""
-        if self.date_end < self.date_start:
-            raise ValidationError(_("The start date must be earlier than the end date."))
-
-        if self.school_term and (
-            self.date_end > self.school_term.date_end
-            or self.date_start < self.school_term.date_start
-        ):
-            raise ValidationError(_("The validity range must be within the school term."))
-
-        qs = ValidityRange.objects.within_dates(self.date_start, self.date_end)
-        if self.pk:
-            qs = qs.exclude(pk=self.pk)
-        if qs.exists():
-            raise ValidationError(
-                _("There is already a validity range for this time or a part of this time.")
-            )
-
-    def __str__(self):
-        return self.name or f"{date_format(self.date_start)}–{date_format(self.date_end)}"
-
-    class Meta:
-        verbose_name = _("Validity range")
-        verbose_name_plural = _("Validity ranges")
-        constraints = [
-            models.UniqueConstraint(
-                fields=["school_term", "date_start", "date_end"], name="unique_dates_per_term"
-            ),
-        ]
-        indexes = [
-            models.Index(fields=["date_start", "date_end"], name="validity_date_start_date_end")
-        ]
-
-
-class TimePeriod(ValidityRangeRelatedExtensibleModel):
-    WEEKDAY_CHOICES = i18n_day_name_choices_lazy()
-    WEEKDAY_CHOICES_SHORT = i18n_day_abbr_choices_lazy()
-
-    weekday = models.PositiveSmallIntegerField(
-        verbose_name=_("Week day"), choices=i18n_day_name_choices_lazy()
-    )
-    period = models.PositiveSmallIntegerField(verbose_name=_("Number of period"))
-
-    time_start = models.TimeField(verbose_name=_("Start time"))
-    time_end = models.TimeField(verbose_name=_("End time"))
-
-    def __str__(self) -> str:
-        return f"{self.get_weekday_display()}, {self.period}."
-
-    @classmethod
-    def get_times_dict(cls) -> dict[int, tuple[datetime, datetime]]:
-        periods = {}
-        for period in cls.objects.for_current_or_all().all():
-            periods[period.period] = (period.time_start, period.time_end)
-
-        return periods
-
-    def get_date(self, week: CalendarWeek | None = None) -> date:
-        if isinstance(week, CalendarWeek):
-            wanted_week = week
-        else:
-            year = getattr(self, "_year", None) or date.today().year
-            week_number = getattr(self, "_week", None) or CalendarWeek().week
-
-            wanted_week = CalendarWeek(year=year, week=week_number)
-
-        return wanted_week[self.weekday]
-
-    def get_datetime_start(self, date_ref: CalendarWeek | int | date | None = None) -> datetime:
-        """Get datetime of lesson start in a specific week."""
-        day = date_ref if isinstance(date_ref, date) else self.get_date(date_ref)
-        return datetime.combine(day, self.time_start)
-
-    def get_datetime_end(self, date_ref: CalendarWeek | int | date | None = None) -> datetime:
-        """Get datetime of lesson end in a specific week."""
-        day = date_ref if isinstance(date_ref, date) else self.get_date(date_ref)
-        return datetime.combine(day, self.time_end)
-
-    @classmethod
-    def get_next_relevant_day(
-        cls, day: date | None = None, time: time | None = None, prev: bool = False
-    ) -> date:
-        """Return next (previous) day with lessons depending on date and time."""
-        if day is None:
-            day = timezone.now().date()
-
-        if time is not None and cls.time_max and not prev and time > cls.time_max:
-            day += timedelta(days=1)
-
-        cw = CalendarWeek.from_date(day)
-
-        if day.weekday() > cls.weekday_max:
-            if prev:
-                day = cw[cls.weekday_max]
-            else:
-                cw += 1
-                day = cw[cls.weekday_min]
-        elif day.weekday() < TimePeriod.weekday_min:
-            if prev:
-                cw -= 1
-                day = cw[cls.weekday_max]
-            else:
-                day = cw[cls.weekday_min]
-
-        return day
-
-    @classmethod
-    def get_relevant_week_from_datetime(cls, when: datetime | None = None) -> CalendarWeek:
-        """Return currently relevant week depending on current date and time."""
-        if not when:
-            when = timezone.now()
-
-        day = when.date()
-        time = when.time()
-
-        week = CalendarWeek.from_date(day)
-
-        if (cls.weekday_max and day.weekday() > cls.weekday_max) or (
-            cls.time_max and time > cls.time_max and day.weekday() == cls.weekday_max
-        ):
-            week += 1
-
-        return week
-
-    @classmethod
-    def get_prev_next_by_day(cls, day: date, url: str) -> tuple[str, str]:
-        """Build URLs for previous/next day."""
-        day_prev = cls.get_next_relevant_day(day - timedelta(days=1), prev=True)
-        day_next = cls.get_next_relevant_day(day + timedelta(days=1))
-
-        url_prev = reverse(url, args=[day_prev.year, day_prev.month, day_prev.day])
-        url_next = reverse(url, args=[day_next.year, day_next.month, day_next.day])
-
-        return url_prev, url_next
-
-    @classmethod
-    def from_period(cls, period: int, day: date) -> TimePeriod:
-        """Get `TimePeriod` object for a period on a specific date.
-
-        This will respect the relation to validity ranges.
-        """
-        return cls.objects.on_day(day).filter(period=period, weekday=day.weekday()).first()
-
-    @classproperty
-    @cache_memoize(3600)
-    def period_min(cls) -> int:
-        return (
-            cls.objects.for_current_or_all()
-            .aggregate(period__min=Coalesce(Min("period"), 1))
-            .get("period__min")
-        )
-
-    @classproperty
-    @cache_memoize(3600)
-    def period_max(cls) -> int:
-        return (
-            cls.objects.for_current_or_all()
-            .aggregate(period__max=Coalesce(Max("period"), 7))
-            .get("period__max")
-        )
-
-    @classproperty
-    @cache_memoize(3600)
-    def time_min(cls) -> time | None:
-        return cls.objects.for_current_or_all().aggregate(Min("time_start")).get("time_start__min")
-
-    @classproperty
-    @cache_memoize(3600)
-    def time_max(cls) -> time | None:
-        return cls.objects.for_current_or_all().aggregate(Max("time_end")).get("time_end__max")
-
-    @classproperty
-    @cache_memoize(3600)
-    def weekday_min(cls) -> int:
-        return (
-            cls.objects.for_current_or_all()
-            .aggregate(weekday__min=Coalesce(Min("weekday"), 0))
-            .get("weekday__min")
-        )
-
-    @classproperty
-    @cache_memoize(3600)
-    def weekday_max(cls) -> int:
-        return (
-            cls.objects.for_current_or_all()
-            .aggregate(weekday__max=Coalesce(Max("weekday"), 6))
-            .get("weekday__max")
-        )
-
-    @classproperty
-    @cache_memoize(3600)
-    def period_choices(cls) -> list[tuple[str | int, str]]:
-        """Build choice list of periods for usage within Django."""
-        time_periods = (
-            cls.objects.filter(weekday=cls.weekday_min)
-            .for_current_or_all()
-            .values("period", "time_start", "time_end")
-            .distinct()
-        )
-
-        period_choices = [("", "")] + [
-            (period, f"{period}.") for period in time_periods.values_list("period", flat=True)
-        ]
-
-        return period_choices
-
-    class Meta:
-        constraints = [
-            models.UniqueConstraint(
-                fields=["weekday", "period", "validity"], name="unique_period_per_range"
-            ),
-        ]
-        ordering = ["weekday", "period"]
-        indexes = [models.Index(fields=["time_start", "time_end"])]
-        verbose_name = _("Time period")
-        verbose_name_plural = _("Time periods")
-
-
-class Subject(ExtensibleModel):
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True)
-    name = models.CharField(verbose_name=_("Long name"), max_length=255)
-
-    colour_fg = ColorField(verbose_name=_("Foreground colour"), blank=True)
-    colour_bg = ColorField(verbose_name=_("Background colour"), blank=True)
-
-    def __str__(self) -> str:
-        return f"{self.short_name} ({self.name})"
-
-    class Meta:
-        ordering = ["name", "short_name"]
-        verbose_name = _("Subject")
-        verbose_name_plural = _("Subjects")
-
-
-class Lesson(ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
-    subject = models.ForeignKey(
-        "Subject",
-        on_delete=models.CASCADE,
-        related_name="lessons",
-        verbose_name=_("Subject"),
-    )
-    teachers = models.ManyToManyField(
-        "core.Person", related_name="lessons_as_teacher", verbose_name=_("Teachers")
-    )
-    periods = models.ManyToManyField(
-        "TimePeriod",
-        related_name="lessons",
-        through="LessonPeriod",
-        verbose_name=_("Periods"),
-    )
-    groups = models.ManyToManyField("core.Group", related_name="lessons", verbose_name=_("Groups"))
-
-    def get_year(self, week: int) -> int:
-        year = self.validity.date_start.year
-        if week < int(self.validity.date_start.strftime("%V")):
-            year += 1
-        return year
-
-    def get_calendar_week(self, week: int):
-        year = self.get_year(week)
-
-        return CalendarWeek(year=year, week=week)
-
-    def get_teachers(self) -> models.query.QuerySet:
-        """Get teachers relation."""
-        return self.teachers
-
-    @property
-    def _equal_lessons(self):
-        """Get all lesson periods with equal lessons in the whole school term."""
-
-        qs = Lesson.objects.filter(
-            subject=self.subject,
-            validity__school_term=self.validity.school_term,
-        )
-        for group in self.groups.all():
-            qs = qs.filter(groups=group)
-        return qs
-
-    def __str__(self):
-        return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}"
-
-    class Meta:
-        ordering = ["validity__date_start", "subject"]
-        verbose_name = _("Lesson")
-        verbose_name_plural = _("Lessons")
-
-
-class LessonSubstitution(ExtensibleModel, TeacherPropertiesMixin, WeekRelatedMixin):
-    objects = LessonSubstitutionManager.from_queryset(LessonSubstitutionQuerySet)()
-
-    tracker = FieldTracker()
-
-    week = models.IntegerField(verbose_name=_("Week"), default=CalendarWeek.current_week)
-    year = models.IntegerField(verbose_name=_("Year"), default=get_current_year)
-
-    lesson_period = models.ForeignKey(
-        "LessonPeriod", models.CASCADE, "substitutions", verbose_name=_("Lesson period")
-    )
-
-    subject = models.ForeignKey(
-        "Subject",
-        on_delete=models.CASCADE,
-        related_name="lesson_substitutions",
-        null=True,
-        blank=True,
-        verbose_name=_("Subject"),
-    )
-    teachers = models.ManyToManyField(
-        "core.Person",
-        related_name="lesson_substitutions",
-        blank=True,
-        verbose_name=_("Teachers"),
-    )
-    room = models.ForeignKey(
-        "core.Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room")
-    )
-
-    cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?"))
-    cancelled_for_teachers = models.BooleanField(
-        default=False, verbose_name=_("Cancelled for teachers?")
-    )
-
-    comment = models.TextField(verbose_name=_("Comment"), blank=True)
-
-    def clean(self) -> None:
-        if self.subject and self.cancelled:
-            raise ValidationError(_("Lessons can only be either substituted or cancelled."))
-
-    @property
-    def date(self):
-        week = CalendarWeek(week=self.week, year=self.year)
-        return week[self.lesson_period.period.weekday]
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this substitution."""
-        return timezone.datetime.combine(
-            self.date, self.lesson_period.period.time_start
-        ), timezone.datetime.combine(self.date, self.lesson_period.period.time_end)
-
-    def get_teachers(self):
-        return self.teachers
-
-    def __str__(self):
-        return f"{self.lesson_period}, {date_format(self.date)}"
-
-    class Meta:
-        ordering = [
-            "year",
-            "week",
-            "lesson_period__period__weekday",
-            "lesson_period__period__period",
-        ]
-        constraints = [
-            models.CheckConstraint(
-                check=~Q(cancelled=True, subject__isnull=False),
-                name="either_substituted_or_cancelled",
-            ),
-            models.UniqueConstraint(
-                fields=["lesson_period", "week", "year"], name="unique_period_per_week"
-            ),
-        ]
-        indexes = [
-            models.Index(fields=["week", "year"], name="substitution_week_year"),
-            models.Index(fields=["lesson_period"], name="substitution_lesson_period"),
-        ]
-        verbose_name = _("Lesson substitution")
-        verbose_name_plural = _("Lesson substitutions")
-
-
-class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel):
-    label_ = "lesson_period"
-
-    objects = LessonPeriodManager.from_queryset(LessonPeriodQuerySet)()
-
-    lesson = models.ForeignKey(
-        "Lesson",
-        models.CASCADE,
-        related_name="lesson_periods",
-        verbose_name=_("Lesson"),
-    )
-    period = models.ForeignKey(
-        "TimePeriod",
-        models.CASCADE,
-        related_name="lesson_periods",
-        verbose_name=_("Time period"),
-    )
-
-    room = models.ForeignKey(
-        "core.Room",
-        models.CASCADE,
-        null=True,
-        related_name="lesson_periods",
-        verbose_name=_("Room"),
-    )
-
-    def get_substitution(self, week: CalendarWeek | None = None) -> LessonSubstitution:
-        wanted_week = week or self.week or CalendarWeek()
-
-        # We iterate over all substitutions because this can make use of
-        # prefetching when this model is loaded from outside, in contrast
-        # to .filter()
-        for substitution in self.substitutions.all():
-            if substitution.week == wanted_week.week and substitution.year == wanted_week.year:
-                return substitution
-        return None
-
-    def get_subject(self) -> Subject | None:
-        sub = self.get_substitution()
-        if sub and sub.subject:
-            return sub.subject
-        else:
-            return self.lesson.subject
-
-    def get_teachers(self) -> models.query.QuerySet:
-        sub = self.get_substitution()
-        if sub and sub.teachers.all():
-            return sub.teachers
-        else:
-            return self.lesson.teachers
-
-    def get_room(self) -> Room | None:
-        if self.get_substitution() and self.get_substitution().room:
-            return self.get_substitution().room
-        else:
-            return self.room
-
-    def get_groups(self) -> models.query.QuerySet:
-        return self.lesson.groups
-
-    @property
-    def group_names(self):
-        """Get group names as joined string."""
-        return self.lesson.group_names
-
-    @property
-    def group_short_names(self):
-        """Get group short names as joined string."""
-        return self.lesson.group_short_names
-
-    def __str__(self) -> str:
-        return f"{self.period}, {self.lesson}"
-
-    @property
-    def _equal_lesson_periods(self):
-        """Get all lesson periods with equal lessons in the whole school term."""
-
-        return LessonPeriod.objects.filter(lesson__in=self.lesson._equal_lessons)
-
-    @property
-    def next(self) -> LessonPeriod:  # noqa
-        """Get next lesson period of this lesson.
-
-        .. warning::
-            To use this property,  the provided lesson period must be annotated with a week.
-        """
-        return self._equal_lesson_periods.next_lesson(self)
-
-    @property
-    def prev(self) -> LessonPeriod:
-        """Get previous lesson period of this lesson.
-
-        .. warning::
-            To use this property,  the provided lesson period must be annotated with a week.
-        """
-        return self._equal_lesson_periods.next_lesson(self, -1)
-
-    def is_replaced_by_event(
-        self, events: Iterable[Event], groups: Iterable[Group] | None = None
-    ) -> bool:
-        """Check if this lesson period is replaced by an event."""
-        groups_of_event = set(chain(*[event.groups.all() for event in events]))
-
-        if groups:
-            # If the current group is a part of the event,
-            # there are no other lessons for the group.
-            groups = set(groups)
-            if groups.issubset(groups_of_event):
-                return True
-        else:
-            groups_lesson_period = set(self.lesson.groups.all())
-
-            # The lesson period isn't replacable if the lesson has no groups at all
-            if not groups_lesson_period:
-                return False
-
-            # This lesson period is replaced by an event ...
-            # ... if all groups of this lesson period are a part of the event ...
-            if groups_lesson_period.issubset(groups_of_event):
-                return True
-
-            all_parent_groups = set(
-                chain(*[group.parent_groups.all() for group in groups_lesson_period])
-            )
-            # ... or if all parent groups of this lesson period are a part of the event.
-            if all_parent_groups.issubset(groups_of_event):
-                return True
-
-    class Meta:
-        ordering = [
-            "lesson__validity__date_start",
-            "period__weekday",
-            "period__period",
-            "lesson__subject",
-        ]
-        indexes = [
-            models.Index(fields=["lesson", "period"], name="lesson_period_lesson_period"),
-            models.Index(fields=["room"], include=["lesson", "period"], name="lesson_period_room"),
-        ]
-        verbose_name = _("Lesson period")
-        verbose_name_plural = _("Lesson periods")
-
-
-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)
-
-    def __str__(self):
-        if self.name:
-            return f"{self.short_name} ({self.name})"
-        else:
-            return self.short_name
-
-    class Meta:
-        verbose_name = _("Absence reason")
-        verbose_name_plural = _("Absence reasons")
-
-
-class Absence(SchoolTermRelatedExtensibleModel):
-    objects = AlekSISBaseManagerWithoutMigrations.from_queryset(AbsenceQuerySet)()
-
-    reason = models.ForeignKey(
-        "AbsenceReason",
-        on_delete=models.SET_NULL,
-        related_name="absences",
-        blank=True,
-        null=True,
-        verbose_name=_("Absence reason"),
-    )
-
-    teacher = models.ForeignKey(
-        "core.Person",
-        on_delete=models.CASCADE,
-        related_name="absences",
-        null=True,
-        blank=True,
-        verbose_name=_("Teacher"),
-    )
-    group = models.ForeignKey(
-        "core.Group",
-        on_delete=models.CASCADE,
-        related_name="absences",
-        null=True,
-        blank=True,
-        verbose_name=_("Group"),
-    )
-    room = models.ForeignKey(
-        "core.Room",
-        on_delete=models.CASCADE,
-        related_name="absences",
-        null=True,
-        blank=True,
-        verbose_name=_("Room"),
-    )
-
-    date_start = models.DateField(verbose_name=_("Start date"), null=True)
-    date_end = models.DateField(verbose_name=_("End date"), null=True)
-    period_from = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("Start period"),
-        null=True,
-        related_name="+",
-    )
-    period_to = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("End period"),
-        null=True,
-        related_name="+",
-    )
-    comment = models.TextField(verbose_name=_("Comment"), blank=True)
-
-    def __str__(self):
-        if self.teacher:
-            return str(self.teacher)
-        elif self.group:
-            return str(self.group)
-        elif self.room:
-            return str(self.room)
-        else:
-            return _("Unknown absence")
-
-    class Meta:
-        ordering = ["date_start"]
-        indexes = [models.Index(fields=["date_start", "date_end"])]
-        verbose_name = _("Absence")
-        verbose_name_plural = _("Absences")
-
-
-class Exam(SchoolTermRelatedExtensibleModel):
-    lesson = models.ForeignKey(
-        "Lesson",
-        on_delete=models.CASCADE,
-        related_name="exams",
-        verbose_name=_("Lesson"),
-    )
-
-    date = models.DateField(verbose_name=_("Date of exam"))
-    period_from = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("Start period"),
-        related_name="+",
-    )
-    period_to = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("End period"),
-        related_name="+",
-    )
-
-    title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True)
-    comment = models.TextField(verbose_name=_("Comment"), blank=True)
-
-    class Meta:
-        ordering = ["date"]
-        indexes = [models.Index(fields=["date"])]
-        verbose_name = _("Exam")
-        verbose_name_plural = _("Exams")
-
-
-class Holiday(ExtensibleModel):
-    objects = AlekSISBaseManagerWithoutMigrations.from_queryset(HolidayQuerySet)()
-
-    title = models.CharField(verbose_name=_("Title"), max_length=255)
-    date_start = models.DateField(verbose_name=_("Start date"), null=True)
-    date_end = models.DateField(verbose_name=_("End date"), null=True)
-    comments = models.TextField(verbose_name=_("Comments"), blank=True)
-
-    def get_days(self) -> Iterator[date]:
-        delta = self.date_end - self.date_start
-        for i in range(delta.days + 1):
-            yield self.date_start + timedelta(days=i)
-
-    @classmethod
-    def on_day(cls, day: date) -> Holiday | None:
-        holidays = cls.objects.on_day(day)
-        if holidays.exists():
-            return holidays[0]
-        else:
-            return None
-
-    @classmethod
-    def in_week(cls, week: CalendarWeek) -> dict[int, Holiday | None]:
-        per_weekday = {}
-        holidays = Holiday.objects.in_week(week)
-
-        for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
-            holiday_date = week[weekday]
-            filtered_holidays = list(
-                filter(
-                    lambda h: holiday_date >= h.date_start and holiday_date <= h.date_end,
-                    holidays,
-                )
-            )
-            if filtered_holidays:
-                per_weekday[weekday] = filtered_holidays[0]
-
-        return per_weekday
-
-    def __str__(self):
-        return self.title
-
-    class Meta:
-        ordering = ["date_start"]
-        indexes = [models.Index(fields=["date_start", "date_end"])]
-        verbose_name = _("Holiday")
-        verbose_name_plural = _("Holidays")
-
-
-class SupervisionArea(ExtensibleModel):
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True)
-    name = models.CharField(verbose_name=_("Long name"), max_length=255)
-    colour_fg = ColorField(default="#000000")
-    colour_bg = ColorField()
-
-    def __str__(self):
-        return f"{self.name} ({self.short_name})"
-
-    class Meta:
-        ordering = ["name"]
-        verbose_name = _("Supervision area")
-        verbose_name_plural = _("Supervision areas")
-
-
-class Break(ValidityRangeRelatedExtensibleModel):
-    objects = BreakManager()
-
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
-    name = models.CharField(verbose_name=_("Long name"), max_length=255)
-
-    after_period = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("Time period after break starts"),
-        related_name="break_after",
-        blank=True,
-        null=True,
-    )
-    before_period = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("Time period before break ends"),
-        related_name="break_before",
-        blank=True,
-        null=True,
-    )
-
-    @property
-    def weekday(self):
-        return self.after_period.weekday if self.after_period else self.before_period.weekday
-
-    @property
-    def after_period_number(self):
-        return self.after_period.period if self.after_period else self.before_period.period - 1
-
-    @property
-    def before_period_number(self):
-        return self.before_period.period if self.before_period else self.after_period.period + 1
-
-    @property
-    def time_start(self):
-        return self.after_period.time_end if self.after_period else None
-
-    @property
-    def time_end(self):
-        return self.before_period.time_start if self.before_period else None
-
-    @classmethod
-    def get_breaks_dict(cls) -> dict[int, tuple[datetime, datetime]]:
-        breaks = {}
-        for break_ in cls.objects.all():
-            breaks[break_.before_period_number] = break_
-
-        return breaks
-
-    def __str__(self):
-        return f"{self.name} ({self.short_name})"
-
-    class Meta:
-        ordering = ["after_period"]
-        indexes = [models.Index(fields=["after_period", "before_period"])]
-        verbose_name = _("Break")
-        verbose_name_plural = _("Breaks")
-        constraints = [
-            models.UniqueConstraint(
-                fields=["validity", "short_name"], name="unique_short_name_per_validity_break"
-            ),
-        ]
-
-
-class Supervision(ValidityRangeRelatedExtensibleModel, WeekAnnotationMixin):
-    objects = SupervisionManager.from_queryset(SupervisionQuerySet)()
-
-    area = models.ForeignKey(
-        SupervisionArea,
-        models.CASCADE,
-        verbose_name=_("Supervision area"),
-        related_name="supervisions",
-    )
-    break_item = models.ForeignKey(
-        Break, models.CASCADE, verbose_name=_("Break"), related_name="supervisions"
-    )
-    teacher = models.ForeignKey(
-        "core.Person",
-        models.CASCADE,
-        related_name="supervisions",
-        verbose_name=_("Teacher"),
-    )
-
-    def get_year(self, week: int) -> int:
-        year = self.validity.date_start.year
-        if week < int(self.validity.date_start.strftime("%V")):
-            year += 1
-        return year
-
-    def get_calendar_week(self, week: int):
-        year = self.get_year(week)
-
-        return CalendarWeek(year=year, week=week)
-
-    def get_substitution(self, week: CalendarWeek | None = None) -> SupervisionSubstitution | None:
-        wanted_week = week or self.week or CalendarWeek()
-        # We iterate over all substitutions because this can make use of
-        # prefetching when this model is loaded from outside, in contrast
-        # to .filter()
-        for substitution in self.substitutions.all():
-            for weekday in range(0, 7):
-                if substitution.date == wanted_week[weekday]:
-                    return substitution
-        return None
-
-    @property
-    def teachers(self):
-        return [self.teacher]
-
-    def __str__(self):
-        return f"{self.break_item}, {self.area}, {self.teacher}"
-
-    class Meta:
-        ordering = ["area", "break_item"]
-        verbose_name = _("Supervision")
-        verbose_name_plural = _("Supervisions")
-
-
-class SupervisionSubstitution(ExtensibleModel):
-    objects = SupervisionSubstitutionManager()
-
-    tracker = FieldTracker()
-
-    date = models.DateField(verbose_name=_("Date"))
-    supervision = models.ForeignKey(
-        Supervision,
-        models.CASCADE,
-        verbose_name=_("Supervision"),
-        related_name="substitutions",
-    )
-    teacher = models.ForeignKey(
-        "core.Person",
-        models.CASCADE,
-        related_name="substituted_supervisions",
-        verbose_name=_("Teacher"),
-    )
-
-    @property
-    def teachers(self):
-        return [self.teacher]
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this supervision substitution."""
-        return timezone.datetime.combine(
-            self.date,
-            self.supervision.break_item.time_start or self.supervision.break_item.time_end,
-        ), timezone.datetime.combine(
-            self.date,
-            self.supervision.break_item.time_end or self.supervision.break_item.time_start,
-        )
-
-    def __str__(self):
-        return f"{self.supervision}, {date_format(self.date)}"
-
-    class Meta:
-        ordering = ["date", "supervision"]
-        verbose_name = _("Supervision substitution")
-        verbose_name_plural = _("Supervision substitutions")
-
-
-class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
-    label_ = "event"
-
-    tracker = FieldTracker()
-
-    objects = EventManager.from_queryset(EventQuerySet)()
-
-    title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True)
-
-    date_start = models.DateField(verbose_name=_("Start date"), null=True)
-    date_end = models.DateField(verbose_name=_("End date"), null=True)
-
-    period_from = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("Start time period"),
-        related_name="+",
-    )
-    period_to = models.ForeignKey(
-        "TimePeriod",
-        on_delete=models.CASCADE,
-        verbose_name=_("End time period"),
-        related_name="+",
-    )
-
-    groups = models.ManyToManyField("core.Group", related_name="events", verbose_name=_("Groups"))
-    rooms = models.ManyToManyField("core.Room", related_name="events", verbose_name=_("Rooms"))
-    teachers = models.ManyToManyField(
-        "core.Person", related_name="events", verbose_name=_("Teachers")
-    )
-
-    def __str__(self):
-        if self.title:
-            return self.title
-        else:
-            return _("Event {pk}").format(pk=self.pk)
-
-    def get_period_min(self, day) -> int:
-        return (
-            TimePeriod.objects.on_day(day)
-            .aggregate(period__min=Coalesce(Min("period"), 1))
-            .get("period__min")
-        )
-
-    def get_period_max(self, day) -> int:
-        return (
-            TimePeriod.objects.on_day(day)
-            .aggregate(period__max=Coalesce(Max("period"), 7))
-            .get("period__max")
-        )
-
-    @property
-    def raw_period_from_on_day(self) -> TimePeriod:
-        """Get start period on the annotated day (as TimePeriod object).
-
-        If there is no date annotated, it will use the current date.
-        """
-        day = getattr(self, "_date", timezone.now().date())
-        if day != self.date_start:
-            return TimePeriod.from_period(self.get_period_min(day), day)
-        else:
-            return self.period_from
-
-    @property
-    def raw_period_to_on_day(self) -> TimePeriod:
-        """Get end period on the annotated day (as TimePeriod object).
-
-        If there is no date annotated, it will use the current date.
-        """
-        day = getattr(self, "_date", timezone.now().date())
-        if day != self.date_end:
-            return TimePeriod.from_period(self.get_period_max(day), day)
-        else:
-            return self.period_to
-
-    @property
-    def period_from_on_day(self) -> int:
-        """Get start period on the annotated day (as period number).
-
-        If there is no date annotated, it will use the current date.
-        """
-        return self.raw_period_from_on_day.period
-
-    @property
-    def period_to_on_day(self) -> int:
-        """Get end period on the annotated day (as period number).
-
-        If there is no date annotated, it will use the current date.
-        """
-        return self.raw_period_to_on_day.period
-
-    def get_start_weekday(self, week: CalendarWeek) -> int:
-        """Get start date of an event in a specific week."""
-        if self.date_start < week[TimePeriod.weekday_min]:
-            return TimePeriod.weekday_min
-        else:
-            return self.date_start.weekday()
-
-    def get_end_weekday(self, week: CalendarWeek) -> int:
-        """Get end date of an event in a specific week."""
-        if self.date_end > week[TimePeriod.weekday_max]:
-            return TimePeriod.weekday_max
-        else:
-            return self.date_end.weekday()
-
-    def annotate_day(self, day: date):
-        """Annotate event with the provided date."""
-        self._date = day
-
-    def get_groups(self) -> models.query.QuerySet:
-        """Get groups relation."""
-        return self.groups
-
-    def get_teachers(self) -> models.query.QuerySet:
-        """Get teachers relation."""
-        return self.teachers
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this event."""
-        return timezone.datetime.combine(
-            self.date_start, self.period_from.time_start
-        ), timezone.datetime.combine(self.date_end, self.period_to.time_end)
-
-    class Meta:
-        ordering = ["date_start"]
-        indexes = [
-            models.Index(
-                fields=["date_start", "date_end"],
-                include=["period_from", "period_to"],
-                name="event_date_start_date_end",
-            )
-        ]
-        verbose_name = _("Event")
-        verbose_name_plural = _("Events")
-
-
-class ExtraLesson(
-    GroupPropertiesMixin, TeacherPropertiesMixin, WeekRelatedMixin, SchoolTermRelatedExtensibleModel
-):
-    label_ = "extra_lesson"
-
-    tracker = FieldTracker()
-
-    objects = ExtraLessonManager.from_queryset(ExtraLessonQuerySet)()
-
-    week = models.IntegerField(verbose_name=_("Week"), default=CalendarWeek.current_week)
-    year = models.IntegerField(verbose_name=_("Year"), default=get_current_year)
-    period = models.ForeignKey(
-        "TimePeriod",
-        models.CASCADE,
-        related_name="extra_lessons",
-        verbose_name=_("Time period"),
-    )
-
-    subject = models.ForeignKey(
-        "Subject",
-        on_delete=models.CASCADE,
-        related_name="extra_lessons",
-        verbose_name=_("Subject"),
-    )
-    groups = models.ManyToManyField(
-        "core.Group", related_name="extra_lessons", verbose_name=_("Groups")
-    )
-    teachers = models.ManyToManyField(
-        "core.Person",
-        related_name="extra_lessons_as_teacher",
-        verbose_name=_("Teachers"),
-    )
-    room = models.ForeignKey(
-        "core.Room",
-        models.CASCADE,
-        null=True,
-        related_name="extra_lessons",
-        verbose_name=_("Room"),
-    )
-
-    comment = models.CharField(verbose_name=_("Comment"), blank=True, max_length=255)
-
-    exam = models.ForeignKey(
-        "Exam",
-        on_delete=models.CASCADE,
-        verbose_name=_("Related exam"),
-        related_name="extra_lessons",
-        blank=True,
-        null=True,
-    )
-
-    def __str__(self):
-        return f"{self.week}, {self.period}, {self.subject}"
-
-    def get_groups(self) -> models.query.QuerySet:
-        """Get groups relation."""
-        return self.groups
-
-    def get_teachers(self) -> models.query.QuerySet:
-        """Get teachers relation."""
-        return self.teachers
-
-    def get_subject(self) -> Subject:
-        """Get subject."""
-        return self.subject
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this extra lesson."""
-        return timezone.datetime.combine(
-            self.date, self.period.time_start
-        ), timezone.datetime.combine(self.date, self.period.time_end)
-
-    class Meta:
-        verbose_name = _("Extra lesson")
-        verbose_name_plural = _("Extra lessons")
-        indexes = [models.Index(fields=["week", "year"], name="extra_lesson_week_year")]
-
-
 class AutomaticPlan(LiveDocument):
     """Model for configuring automatically updated PDF substitution plans."""
 
@@ -1296,8 +149,7 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
             ("view_all_person_timetables", _("Can view all person timetables")),
             ("view_all_course_timetables", _("Can view all course timetables")),
             ("view_timetable_overview", _("Can view timetable overview")),
-            ("view_lessons_day", _("Can view all lessons per day")),
-            ("view_supervisions_day", _("Can view all supervisions per day")),
+            ("view_substitutions", _("Can view substitutions table")),
         )
 
 
@@ -1556,23 +408,22 @@ class LessonEvent(CalendarEvent):
         params: dict[str, any] | None = None,
         no_effect: bool = False,
         **kwargs,
-    ) -> Iterable:
+    ) -> QuerySet:
         """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)
-            .not_instance_of(SupervisionEvent)
-            .select_related("subject", "course")
-            .prefetch_related("groups", "teachers", "rooms", "groups__members")
-        )
 
         if request and not has_person(request.user):
-            raise PermissionDenied()
+            return cls.objects.none()
+
+        q = Q()
 
         if params:
-            obj_id = int(params.get("id", 0))
+            try:
+                obj_id = int(params.get("id", 0))
+            except ValueError:
+                obj_id = None
+
             type_ = params.get("type", None)
             not_amended = params.get("not_amended", False)
             not_amending = params.get("not_amending", False)
@@ -1580,19 +431,19 @@ class LessonEvent(CalendarEvent):
             own = params.get("own", False)
 
             if not_amended:
-                objs = objs.not_amended()
+                q = q & LessonEventQuerySet.not_amended_q()
 
             if not_amending:
-                objs = objs.not_amending()
+                q = q & LessonEventQuerySet.not_amending_q()
 
             if amending:
-                objs = objs.amending()
+                q = q & LessonEventQuerySet.amending_q()
 
             if request and "own" in params:
                 if own:
-                    objs = objs.for_person(request.user.person)
+                    q = q & LessonEventQuerySet.for_person_q(request.user.person)
                 else:
-                    objs = objs.related_to_person(request.user.person)
+                    q = q & LessonEventQuerySet.related_to_person_q(request.user.person)
 
             if type_ and obj_id:
                 if request and not (
@@ -1629,20 +480,28 @@ class LessonEvent(CalendarEvent):
                     if not request.user.has_perm("chronos.view_timetable_rule", obj):
                         raise PermissionDenied()
                 if type_ == "TEACHER":
-                    return objs.for_teacher(obj_id)
+                    q = q & LessonEventQuerySet.for_teacher_q(obj_id)
                 elif type_ == "PARTICIPANT":
-                    return objs.for_participant(obj_id)
+                    q = q & LessonEventQuerySet.for_participant_q(obj_id)
                 elif type_ == "GROUP":
-                    return objs.for_group(obj_id)
+                    q = q & LessonEventQuerySet.for_group_q(obj_id)
                 elif type_ == "ROOM":
-                    return objs.for_room(obj_id)
+                    q = q & LessonEventQuerySet.for_room_q(obj_id)
                 elif type_ == "COURSE":
-                    return objs.for_course(obj_id)
-
-            if "own" in params:
-                return objs
-        if request:
-            return objs.for_person(request.user.person)
+                    q = q & LessonEventQuerySet.for_course_q(obj_id)
+
+        elif request:
+            q = q & LessonEventQuerySet.for_person_q(request.user.person)
+
+        objs = super().get_objects(
+            request,
+            params,
+            start_qs=cls.objects.not_instance_of(SupervisionEvent),
+            additional_filter=q,
+            select_related=["subject", "course"],
+            prefetch_related=["groups", "teachers", "rooms", "groups__members"],
+            **kwargs,
+        )
         return objs
 
     class Meta:
@@ -1681,7 +540,7 @@ 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, no_effect=True, **kwargs)
+        q = Q()
         if params:
             obj_id = int(params.get("id", 0))
             type_ = params.get("type", None)
@@ -1690,21 +549,30 @@ class SupervisionEvent(LessonEvent):
             amending = params.get("amending", False)
 
             if not_amended:
-                objs = objs.not_amended()
+                q = q & SupervisionEventQuerySet.not_amended_q()
 
             if not_amending:
-                objs = objs.not_amending()
+                q = q & SupervisionEventQuerySet.not_amending_q()
 
             if amending:
-                objs = objs.amending()
+                q = q & SupervisionEventQuerySet.amending_q()
 
             if type_ and obj_id:
                 if type_ == "TEACHER":
-                    return objs.for_teacher(obj_id)
+                    q = q & SupervisionEventQuerySet.for_teacher_q(obj_id)
                 elif type_ == "GROUP":
-                    return objs.for_group(obj_id)
+                    q = q & SupervisionEventQuerySet.for_group_q(obj_id)
                 elif type_ == "ROOM":
-                    return objs.for_room(obj_id)
-        if request:
-            return objs.for_person(request.user.person)
-        return objs
+                    q = q & SupervisionEventQuerySet.for_room_q(obj_id)
+        elif request:
+            q = q & SupervisionEventQuerySet.for_person_q(request.user.person)
+
+        return super().get_objects(
+            request,
+            params,
+            no_effect=True,
+            additional_filter=q,
+            select_related=["subject"],
+            prefetch_related=["teachers", "rooms"],
+            **kwargs,
+        )
diff --git a/aleksis/apps/chronos/preferences.py b/aleksis/apps/chronos/preferences.py
index b3dc8362091693b313bda2d5f9493d8afa7e7701..a2bd26ef9eac55d0ddca38091619b3d008279803 100644
--- a/aleksis/apps/chronos/preferences.py
+++ b/aleksis/apps/chronos/preferences.py
@@ -16,7 +16,7 @@ from dynamic_preferences.types import (
 )
 
 from aleksis.core.models import GroupType
-from aleksis.core.registries import person_preferences_registry, site_preferences_registry
+from aleksis.core.registries import site_preferences_registry
 
 chronos = Section("chronos", verbose_name=_("Timetables"))
 
@@ -34,27 +34,6 @@ class UseParentGroups(BooleanPreference):
     )
 
 
-@person_preferences_registry.register
-class ShortenGroups(BooleanPreference):
-    section = chronos
-    name = "shorten_groups"
-    default = True
-    verbose_name = _("Shorten groups in timetable views")
-    help_text = _("If there are more groups than the set limit, they will be collapsed.")
-
-
-@site_preferences_registry.register
-class ShortenGroupsLimit(IntegerPreference):
-    section = chronos
-    name = "shorten_groups_limit"
-    default = 4
-    verbose_name = _("Limit of groups for shortening of groups")
-    help_text = _(
-        "If a user activates shortening of groups,"
-        "they will be collapsed if there are more groups than this limit."
-    )
-
-
 @site_preferences_registry.register
 class SubstitutionsRelevantDays(MultipleChoicePreference):
     """Relevant days which have substitution plans."""
@@ -110,44 +89,6 @@ class AffectedGroupsUseParentGroups(BooleanPreference):
     )
 
 
-@site_preferences_registry.register
-class DaysInAdvanceNotifications(IntegerPreference):
-    section = chronos
-    name = "days_in_advance_notifications"
-    default = 1
-    verbose_name = _("How many days in advance users should be notified about timetable changes?")
-
-
-@site_preferences_registry.register
-class TimeForSendingNotifications(TimePreference):
-    section = chronos
-    name = "time_for_sending_notifications"
-    default = time(17, 00)
-    verbose_name = _("Time for sending notifications about timetable changes")
-    required = True
-    help_text = _(
-        "This is only used for scheduling notifications "
-        "which doesn't affect the time period configured above. "
-        "All other notifications affecting the next days are sent immediately."
-    )
-
-
-@site_preferences_registry.register
-class SendNotifications(BooleanPreference):
-    section = chronos
-    name = "send_notifications_site"
-    default = True
-    verbose_name = _("Send notifications for current timetable changes")
-
-
-@person_preferences_registry.register
-class SendNotificationsPerson(BooleanPreference):
-    section = chronos
-    name = "send_notifications"
-    default = True
-    verbose_name = _("Send notifications for current timetable changes")
-
-
 @site_preferences_registry.register
 class GroupTypesTimetables(ModelMultipleChoicePreference):
     section = chronos
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index 34d58360ce18353231f56acccf1a00818f479fbb..c7d0065c6bc2c6180a0fa81188484472e6ed8af7 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -6,7 +6,7 @@ from aleksis.core.util.predicates import (
     has_person,
 )
 
-from .util.predicates import has_any_timetable_object, has_room_timetable_perm, has_timetable_perm
+from .util.predicates import has_any_timetable_object, has_timetable_perm
 
 # View timetable overview
 view_timetable_overview_predicate = has_person & (
@@ -14,51 +14,27 @@ view_timetable_overview_predicate = has_person & (
 )
 add_perm("chronos.view_timetable_overview_rule", view_timetable_overview_predicate)
 
-# View my timetable
-add_perm("chronos.view_my_timetable_rule", has_person)
-
 # View timetable
 view_timetable_predicate = has_person & has_timetable_perm
 add_perm("chronos.view_timetable_rule", view_timetable_predicate)
 
-# View all lessons per day
-view_lessons_day_predicate = has_person & has_global_perm("chronos.view_lessons_day")
-add_perm("chronos.view_lessons_day_rule", view_lessons_day_predicate)
 
 # Edit substition
 edit_substitution_predicate = has_person & (
-    has_global_perm("chronos.change_lessonsubstitution")
-    | has_object_perm("chronos.change_lessonsubstitution")
+    has_global_perm("chronos.change_lessonevent") | has_object_perm("chronos.change_lessonevent")
 )
 add_perm("chronos.edit_substitution_rule", edit_substitution_predicate)
 
 # Delete substitution
 delete_substitution_predicate = has_person & (
-    has_global_perm("chronos.delete_lessonsubstitution")
-    | has_object_perm("chronos.delete_lessonsubstitution")
+    has_global_perm("chronos.delete_lessonevent") | has_object_perm("chronos.delete_lessonevent")
 )
 add_perm("chronos.delete_substitution_rule", delete_substitution_predicate)
 
 # View substitutions
-view_substitutions_predicate = has_person & (has_global_perm("chronos.view_lessonsubstitution"))
+view_substitutions_predicate = has_person & (has_global_perm("chronos.view_substitutions"))
 add_perm("chronos.view_substitutions_rule", view_substitutions_predicate)
 
-# View all supervisions per day
-view_supervisions_day_predicate = has_person & has_global_perm("chronos.view_supervisions_day")
-add_perm("chronos.view_supervisions_day_rule", view_supervisions_day_predicate)
-
-# Edit supervision substitution
-edit_supervision_substitution_predicate = has_person & (
-    has_global_perm("chronos.change_supervisionsubstitution")
-)
-add_perm("chronos.edit_supervision_substitution_rule", edit_supervision_substitution_predicate)
-
-# Delete supervision substitution
-delete_supervision_substitution_predicate = has_person & (
-    has_global_perm("chronos.delete_supervisionsubstitution")
-)
-add_perm("chronos.delete_supervision_substitution_rule", delete_supervision_substitution_predicate)
-
-# View room (timetable)
-view_room_predicate = has_person & has_room_timetable_perm
-add_perm("chronos.view_room_rule", view_room_predicate)
+# View parent menu entry
+view_menu_predicate = has_person & (view_timetable_overview_predicate)
+add_perm("chronos.view_menu_rule", view_menu_predicate)
diff --git a/aleksis/apps/chronos/schema/__init__.py b/aleksis/apps/chronos/schema/__init__.py
index d9194e6718112025b4e41713cd00ff5d043bebf6..d23eb0c7c8bf056b66f3b7c074f2a73e6b6f1182 100644
--- a/aleksis/apps/chronos/schema/__init__.py
+++ b/aleksis/apps/chronos/schema/__init__.py
@@ -7,9 +7,13 @@ from aleksis.core.schema.base import (
     BaseBatchDeleteMutation,
     BaseBatchPatchMutation,
 )
+from aleksis.core.schema.group import GroupType
+from aleksis.core.schema.person import PersonType
+from aleksis.core.schema.room import RoomType
 
 from ..models import LessonEvent
-from ..util.chronos_helpers import get_groups, get_rooms, get_teachers
+from ..util.build import build_substitutions_list
+from ..util.chronos_helpers import get_groups, get_next_relevant_day, get_rooms, get_teachers
 
 
 class TimetablePersonType(DjangoObjectType):
@@ -122,11 +126,114 @@ class TimetableObjectType(graphene.ObjectType):
         return f"{root.type.value}-{root.id}"
 
 
+class SubstitutionType(graphene.ObjectType):
+    """This type contains the logic also contained in the pdf templates."""
+
+    old_groups = graphene.List(GroupType)
+    new_groups = graphene.List(GroupType)
+    start_slot = graphene.Int()
+    end_slot = graphene.Int()
+    start_time = graphene.DateTime()
+    end_time = graphene.DateTime()
+    old_teachers = graphene.List(PersonType)
+    new_teachers = graphene.List(PersonType)
+    old_subject = graphene.String()
+    new_subject = graphene.String()
+    old_rooms = graphene.List(RoomType)
+    new_rooms = graphene.List(RoomType)
+    cancelled = graphene.Boolean()
+    notes = graphene.String()
+
+    # TODO: Extract old/new-pattern into own method and reuse?
+
+    def resolve_old_groups(root, info):
+        le = root["REFERENCE_OBJECT"]
+        return le.amends.groups.all() or le.groups.all()
+
+    def resolve_new_groups(root, info):
+        le = root["REFERENCE_OBJECT"]
+        if le.groups.all() and le.amends.groups.all():
+            return le.groups.all()
+        else:
+            return []
+
+    def resolve_start_slot(root, info):
+        return root["REFERENCE_OBJECT"].slot_number_start
+
+    def resolve_end_slot(root, info):
+        return root["REFERENCE_OBJECT"].slot_number_end
+
+    def resolve_start_time(root, info):
+        return root["DTSTART"].dt
+
+    def resolve_end_time(root, info):
+        return root["DTEND"].dt
+
+    def resolve_old_teachers(root, info):
+        le = root["REFERENCE_OBJECT"]
+        return le.amends.teachers.all() or le.teachers.all()
+
+    def resolve_new_teachers(root, info):
+        le = root["REFERENCE_OBJECT"]
+        if le.teachers.all() and le.amends.teachers.all():
+            return le.teachers.all()
+        else:
+            return []
+
+    def resolve_old_subject(root, info):
+        le = root["REFERENCE_OBJECT"]
+        if le.name == "supervision":
+            return "SUPERVISION"
+        elif not le.amends.subject and not le.subject:
+            return le.amends.title
+        else:
+            subject = le.amends.subject or le.subject
+            return subject.short_name or subject.name
+
+    def resolve_new_subject(root, info):
+        le = root["REFERENCE_OBJECT"]
+        if le.name == "supervision":
+            return None
+        elif not le.amends.subject and not le.subject:
+            return le.title
+        elif le.subject and le.amends.subject:
+            return le.subject.short_name or le.subject.name
+        else:
+            return None
+
+    def resolve_old_rooms(root, info):
+        le = root["REFERENCE_OBJECT"]
+        return le.amends.rooms.all() or le.rooms.all()
+
+    def resolve_new_rooms(root, info):
+        le = root["REFERENCE_OBJECT"]
+        if le.rooms.all() and le.amends.rooms.all():
+            return le.rooms.all()
+        else:
+            return []
+
+    def resolve_cancelled(root, info):
+        return root["REFERENCE_OBJECT"].cancelled
+
+    def resolve_notes(root, info):
+        return root["REFERENCE_OBJECT"].title or root["REFERENCE_OBJECT"].comment
+
+
+class SubstitutionsForDateType(graphene.ObjectType):
+    affected_teachers = graphene.List(PersonType)
+    affected_groups = graphene.List(GroupType)
+    substitutions = graphene.List(SubstitutionType)
+
+
 class Query(graphene.ObjectType):
     timetable_teachers = graphene.List(TimetablePersonType)
     timetable_groups = graphene.List(TimetableGroupType)
     timetable_rooms = graphene.List(TimetableRoomType)
     available_timetables = graphene.List(TimetableObjectType)
+    substitutions_for_date = graphene.Field(
+        SubstitutionsForDateType,
+        date=graphene.Date(),
+    )
 
     def resolve_timetable_teachers(self, info, **kwargs):
         return get_teachers(info.context.user)
@@ -168,6 +275,16 @@ class Query(graphene.ObjectType):
 
         return all_timetables
 
+    def resolve_substitutions_for_date(root, info, date):
+        substitutions, affected_teachers, affected_groups = build_substitutions_list(
+            get_next_relevant_day(date)
+        )
+        return SubstitutionsForDateType(
+            affected_teachers=affected_teachers,
+            affected_groups=affected_groups,
+            substitutions=[sub["el"] for sub in substitutions],
+        )
+
 
 class Mutation(graphene.ObjectType):
     create_amend_lessons = AmendLessonBatchCreateMutation.Field()
diff --git a/aleksis/apps/chronos/static/css/chronos/timetable_print.css b/aleksis/apps/chronos/static/css/chronos/timetable_print.css
deleted file mode 100644
index 343864ae919cb68ab0bb4571d1736fd87aa5a573..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/static/css/chronos/timetable_print.css
+++ /dev/null
@@ -1,34 +0,0 @@
-.timetable-plan .row,
-.timetable-plan .col {
-  display: flex;
-  padding: 0rem;
-}
-
-.timetable-plan .row {
-  margin-bottom: 0rem;
-}
-
-.lesson-card,
-.timetable-title-card {
-  margin: 0;
-  display: flex;
-  flex-grow: 1;
-  min-height: 40px;
-  box-shadow: none;
-  border: 1px solid black;
-  margin-right: -1px;
-  margin-top: -1px;
-  border-radius: 0px;
-  font-size: 11px;
-}
-.lesson-card .card-content > div {
-  padding: 1px;
-}
-
-.card .card-title {
-  font-size: 18px;
-}
-
-.timetable-title-card .card-content {
-  padding: 7px;
-}
diff --git a/aleksis/apps/chronos/static/js/chronos/date_select.js b/aleksis/apps/chronos/static/js/chronos/date_select.js
deleted file mode 100644
index 04a0cf0e6623c772c007ca8f4a85e41038f0c48c..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/static/js/chronos/date_select.js
+++ /dev/null
@@ -1,21 +0,0 @@
-var data = getJSONScript("datepicker_data");
-var activeDate = new Date(data.date);
-
-function updateDatepicker() {
-  $("#date").val(formatDate(activeDate));
-}
-
-function loadNew() {
-  window.location.href = data.dest + formatDateForDjango(activeDate);
-}
-
-function onDateChanged() {
-  activeDate = M.Datepicker.getInstance($("#date")).date;
-  loadNew();
-}
-
-$(document).ready(function () {
-  $("#date").change(onDateChanged);
-
-  updateDatepicker();
-});
diff --git a/aleksis/apps/chronos/static/js/chronos/week_select.js b/aleksis/apps/chronos/static/js/chronos/week_select.js
deleted file mode 100644
index 1854b17d284b4bcb8953a97a3e049d51821d3368..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/static/js/chronos/week_select.js
+++ /dev/null
@@ -1,21 +0,0 @@
-var data = getJSONScript("week_select");
-
-function goToCalendarWeek(cw, year) {
-  window.location.href = data.dest.replace("year", year).replace("cw", cw);
-}
-
-function onCalendarWeekChanged(where) {
-  goToCalendarWeek($(where).val(), data.year);
-}
-
-$(document).ready(function () {
-  $("#calendar-week-1").change(function () {
-    onCalendarWeekChanged("#calendar-week-1");
-  });
-  $("#calendar-week-2").change(function () {
-    onCalendarWeekChanged("#calendar-week-2");
-  });
-  $("#calendar-week-3").change(function () {
-    onCalendarWeekChanged("#calendar-week-3");
-  });
-});
diff --git a/aleksis/apps/chronos/templatetags/__init__.py b/aleksis/apps/chronos/templatetags/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/aleksis/apps/chronos/templatetags/common.py b/aleksis/apps/chronos/templatetags/common.py
deleted file mode 100644
index f3b4ca74d952a6f9fda5034360aa517f2fea475c..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templatetags/common.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from django import template
-
-register = template.Library()
-
-
-class SetVarNode(template.Node):
-    def __init__(self, var_name, var_value):
-        self.var_name = var_name
-        self.var_value = var_value
-
-    def render(self, context):
-        try:
-            value = template.Variable(self.var_value).resolve(context)
-        except template.VariableDoesNotExist:
-            value = ""
-        context[self.var_name] = value
-
-        return ""
-
-
-@register.tag(name="set")
-def set_var(parser, token):
-    """Set var.
-
-    {% set some_var = '123' %}
-    """
-    parts = token.split_contents()
-    if len(parts) < 4:
-        raise template.TemplateSyntaxError(
-            "'set' tag must be of the form: {% set <var_name> = <var_value> %}"
-        )
-
-    return SetVarNode(parts[1], parts[3])
diff --git a/aleksis/apps/chronos/templatetags/week_helpers.py b/aleksis/apps/chronos/templatetags/week_helpers.py
deleted file mode 100644
index cdba9b3f014913a2525b3ca59bfc514dc0e98ef1..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/templatetags/week_helpers.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from datetime import date, datetime
-from typing import Optional, Union
-
-from django import template
-from django.db.models.query import QuerySet
-
-from aleksis.apps.chronos.util.date import CalendarWeek, week_period_to_date, week_weekday_to_date
-
-register = template.Library()
-
-
-@register.filter
-def week_start(week: CalendarWeek) -> date:
-    return week[0]
-
-
-@register.filter
-def week_end(week: CalendarWeek) -> date:
-    return week[-1]
-
-
-@register.filter
-def only_week(qs: QuerySet, week: Optional[CalendarWeek]) -> QuerySet:
-    wanted_week = week or CalendarWeek()
-    return qs.filter(week=wanted_week.week, year=wanted_week.year)
-
-
-@register.simple_tag
-def weekday_to_date(week: CalendarWeek, weekday: int) -> date:
-    return week_weekday_to_date(week, weekday)
-
-
-@register.simple_tag
-def period_to_date(week: CalendarWeek, period) -> date:
-    return week_period_to_date(week, period)
-
-
-@register.simple_tag
-def period_to_time_start(date_ref: Union[CalendarWeek, int, date], period) -> date:
-    return period.get_datetime_start(date_ref)
-
-
-@register.simple_tag
-def period_to_time_end(date_ref: Union[CalendarWeek, int, date], period) -> date:
-    return period.get_datetime_end(date_ref)
-
-
-@register.simple_tag
-def today() -> date:
-    return date.today()
-
-
-@register.simple_tag
-def now_datetime() -> datetime:
-    return datetime.now()
diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
index dd24a0452b93b3e34785e71f5ccdd5b58b908baa..66168ce2496758b6525c98fb31afbd5c686a84b5 100644
--- a/aleksis/apps/chronos/util/build.py
+++ b/aleksis/apps/chronos/util/build.py
@@ -1,385 +1,7 @@
-from collections import OrderedDict
 from datetime import date, datetime, time
-from typing import Union
 
-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")
-LessonSubstitution = apps.get_model("chronos", "LessonSubstitution")
-SupervisionSubstitution = apps.get_model("chronos", "SupervisionSubstitution")
-Event = apps.get_model("chronos", "Event")
-Holiday = apps.get_model("chronos", "Holiday")
-ExtraLesson = apps.get_model("chronos", "ExtraLesson")
-
-
-def build_timetable(
-    type_: Union[TimetableType, str],
-    obj: Union[Group, Room, Person],
-    date_ref: Union[CalendarWeek, date],
-    with_holidays: bool = True,
-):
-    needed_breaks = []
-
-    is_person = False
-    if type_ == "person":
-        is_person = True
-        type_ = obj.timetable_type
-
-    is_week = False
-    if isinstance(date_ref, CalendarWeek):
-        is_week = True
-
-    if type_ is None:
-        return None
-
-    # Get matching holidays
-    if is_week:
-        holidays_per_weekday = Holiday.in_week(date_ref) if with_holidays else {}
-    else:
-        holiday = Holiday.on_day(date_ref) if with_holidays else None
-
-    # Get matching lesson periods
-    lesson_periods = LessonPeriod.objects
-    lesson_periods = (
-        lesson_periods.select_related(None)
-        .select_related("lesson", "lesson__subject", "period", "room")
-        .only(
-            "lesson",
-            "period",
-            "room",
-            "lesson__subject",
-            "period__weekday",
-            "period__period",
-            "lesson__subject__short_name",
-            "lesson__subject__name",
-            "lesson__subject__colour_fg",
-            "lesson__subject__colour_bg",
-            "room__short_name",
-            "room__name",
-        )
-    )
-
-    if is_week:
-        lesson_periods = lesson_periods.in_week(date_ref)
-    else:
-        lesson_periods = lesson_periods.on_day(date_ref)
-
-    if is_person:
-        lesson_periods = lesson_periods.filter_from_person(obj)
-    else:
-        lesson_periods = lesson_periods.filter_from_type(type_, obj, is_smart=with_holidays)
-
-    # Sort lesson periods in a dict
-    lesson_periods_per_period = lesson_periods.group_by_periods(is_week=is_week)
-
-    # Get events
-    extra_lessons = ExtraLesson.objects
-    if is_week:
-        extra_lessons = extra_lessons.filter(week=date_ref.week, year=date_ref.year)
-    else:
-        extra_lessons = extra_lessons.on_day(date_ref)
-    if is_person:
-        extra_lessons = extra_lessons.filter_from_person(obj)
-    else:
-        extra_lessons = extra_lessons.filter_from_type(type_, obj)
-
-    extra_lessons = extra_lessons.only(
-        "week",
-        "year",
-        "period",
-        "subject",
-        "room",
-        "comment",
-        "period__weekday",
-        "period__period",
-        "subject__short_name",
-        "subject__name",
-        "subject__colour_fg",
-        "subject__colour_bg",
-        "room__short_name",
-        "room__name",
-    )
-
-    # Sort lesson periods in a dict
-    extra_lessons_per_period = extra_lessons.group_by_periods(is_week=is_week)
-
-    # Get events
-    events = Event.objects
-    events = events.in_week(date_ref) if is_week else events.on_day(date_ref)
-
-    events = events.only(
-        "id",
-        "title",
-        "date_start",
-        "date_end",
-        "period_from",
-        "period_to",
-        "period_from__weekday",
-        "period_from__period",
-        "period_to__weekday",
-        "period_to__period",
-    )
-
-    if is_person:
-        events_to_display = events.filter_from_person(obj)
-    else:
-        events_to_display = events.filter_from_type(type_, obj)
-
-    # Sort events in a dict
-    events_per_period = {}
-    events_for_replacement_per_period = {}
-    for event in events:
-        if is_week and event.date_start < date_ref[TimePeriod.weekday_min]:
-            # If start date not in current week, set weekday and period to min
-            weekday_from = TimePeriod.weekday_min
-            period_from_first_weekday = TimePeriod.period_min
-        else:
-            weekday_from = event.date_start.weekday()
-            period_from_first_weekday = event.period_from.period
-
-        if is_week and event.date_end > date_ref[TimePeriod.weekday_max]:
-            # If end date not in current week, set weekday and period to max
-            weekday_to = TimePeriod.weekday_max
-            period_to_last_weekday = TimePeriod.period_max
-        else:
-            weekday_to = event.date_end.weekday()
-            period_to_last_weekday = event.period_to.period
-
-        for weekday in range(weekday_from, weekday_to + 1):
-            if not is_week and weekday != date_ref.weekday():
-                # If daily timetable for person, skip other weekdays
-                continue
-
-            # If start day, use start period else use min period
-            period_from = (
-                period_from_first_weekday if weekday == weekday_from else TimePeriod.period_min
-            )
-
-            # If end day, use end period else use max period
-            period_to = period_to_last_weekday if weekday == weekday_to else TimePeriod.periox_max
-
-            for period in range(period_from, period_to + 1):
-                # The following events are possibly replacing some lesson periods
-                if period not in events_for_replacement_per_period:
-                    events_for_replacement_per_period[period] = {} if is_week else []
-
-                if is_week and weekday not in events_for_replacement_per_period[period]:
-                    events_for_replacement_per_period[period][weekday] = []
-
-                if not is_week:
-                    events_for_replacement_per_period[period].append(event)
-                else:
-                    events_for_replacement_per_period[period][weekday].append(event)
-
-                # and the following will be displayed in the timetable
-                if event in events_to_display:
-                    if period not in events_per_period:
-                        events_per_period[period] = {} if is_week else []
-
-                    if is_week and weekday not in events_per_period[period]:
-                        events_per_period[period][weekday] = []
-
-                    if not is_week:
-                        events_per_period[period].append(event)
-                    else:
-                        events_per_period[period][weekday].append(event)
-
-    if type_ == TimetableType.TEACHER:
-        # Get matching supervisions
-        week = CalendarWeek.from_date(date_ref) if not is_week else date_ref
-        supervisions = (
-            Supervision.objects.in_week(week)
-            .all()
-            .annotate_week(week)
-            .filter_by_teacher(obj)
-            .only(
-                "area",
-                "break_item",
-                "teacher",
-                "area",
-                "area__short_name",
-                "area__name",
-                "area__colour_fg",
-                "area__colour_bg",
-                "break_item__short_name",
-                "break_item__name",
-                "break_item__after_period__period",
-                "break_item__after_period__weekday",
-                "break_item__before_period__period",
-                "break_item__before_period__weekday",
-                "teacher__short_name",
-                "teacher__first_name",
-                "teacher__last_name",
-            )
-        )
-
-        if not is_week:
-            supervisions = supervisions.filter_by_weekday(date_ref.weekday())
-
-        supervisions_per_period_after = {}
-        for supervision in supervisions:
-            weekday = supervision.break_item.weekday
-            period_after_break = supervision.break_item.before_period_number
-
-            if period_after_break not in needed_breaks:
-                needed_breaks.append(period_after_break)
-
-            if is_week and period_after_break not in supervisions_per_period_after:
-                supervisions_per_period_after[period_after_break] = {}
-
-            if not is_week:
-                supervisions_per_period_after[period_after_break] = supervision
-            else:
-                supervisions_per_period_after[period_after_break][weekday] = supervision
-
-    # Get ordered breaks
-    breaks = OrderedDict(sorted(Break.get_breaks_dict().items()))
-
-    rows = []
-    for period, break_ in breaks.items():  # period is period after break
-        # Break
-        if type_ == TimetableType.TEACHER and period in needed_breaks:
-            row = {
-                "type": "break",
-                "after_period": break_.after_period_number,
-                "before_period": break_.before_period_number,
-                "time_start": break_.time_start,
-                "time_end": break_.time_end,
-            }
-
-            if is_week:
-                cols = []
-
-                for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
-                    col = None
-                    if (
-                        period in supervisions_per_period_after
-                        and weekday not in holidays_per_weekday
-                    ) and weekday in supervisions_per_period_after[period]:
-                        col = supervisions_per_period_after[period][weekday]
-                    cols.append(col)
-
-                row["cols"] = cols
-            else:
-                col = None
-                if period in supervisions_per_period_after and not holiday:
-                    col = supervisions_per_period_after[period]
-                row["col"] = col
-            rows.append(row)
-
-        # Period
-        if period <= TimePeriod.period_max:
-            row = {
-                "type": "period",
-                "period": period,
-                "time_start": break_.before_period.time_start,
-                "time_end": break_.before_period.time_end,
-            }
-
-            if is_week:
-                cols = []
-                for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
-                    # Skip this period if there are holidays
-                    if weekday in holidays_per_weekday:
-                        cols.append([])
-                        continue
-
-                    col = []
-
-                    events_for_this_period = (
-                        events_per_period[period].get(weekday, [])
-                        if period in events_per_period
-                        else []
-                    )
-                    events_for_replacement_for_this_period = (
-                        events_for_replacement_per_period[period].get(weekday, [])
-                        if period in events_for_replacement_per_period
-                        else []
-                    )
-                    lesson_periods_for_this_period = (
-                        lesson_periods_per_period[period].get(weekday, [])
-                        if period in lesson_periods_per_period
-                        else []
-                    )
-
-                    # Add lesson periods
-                    if lesson_periods_for_this_period:
-                        if events_for_replacement_for_this_period:
-                            # If there is a event in this period,
-                            # we have to check whether the actual lesson is taking place.
-
-                            for lesson_period in lesson_periods_for_this_period:
-                                replaced_by_event = lesson_period.is_replaced_by_event(
-                                    events_for_replacement_for_this_period,
-                                    [obj] if type_ == TimetableType.GROUP else None,
-                                )
-                                lesson_period.replaced_by_event = replaced_by_event
-                                if not replaced_by_event or (
-                                    replaced_by_event and type_ != TimetableType.GROUP
-                                ):
-                                    col.append(lesson_period)
-
-                        else:
-                            col += lesson_periods_for_this_period
-
-                    # Add extra lessons
-                    if period in extra_lessons_per_period:
-                        col += extra_lessons_per_period[period].get(weekday, [])
-
-                    # Add events
-                    col += events_for_this_period
-
-                    cols.append(col)
-
-                row["cols"] = cols
-            else:
-                col = []
-
-                # Skip this period if there are holidays
-                if holiday:
-                    continue
-
-                events_for_this_period = events_per_period.get(period, [])
-                events_for_replacement_for_this_period = events_for_replacement_per_period.get(
-                    period, []
-                )
-                lesson_periods_for_this_period = lesson_periods_per_period.get(period, [])
-
-                # Add lesson periods
-                if lesson_periods_for_this_period:
-                    if events_for_replacement_for_this_period:
-                        # If there is a event in this period,
-                        # we have to check whether the actual lesson is taking place.
-
-                        lesson_periods_to_keep = []
-                        for lesson_period in lesson_periods_for_this_period:
-                            if not lesson_period.is_replaced_by_event(
-                                events_for_replacement_for_this_period
-                            ):
-                                lesson_periods_to_keep.append(lesson_period)
-                        col += lesson_periods_to_keep
-                    else:
-                        col += lesson_periods_for_this_period
-
-                # Add events and extra lessons
-                col += extra_lessons_per_period.get(period, [])
-                col += events_for_this_period
-
-                row["col"] = col
-
-            rows.append(row)
-
-    return rows
+from aleksis.apps.chronos.models import LessonEvent, SupervisionEvent
+from aleksis.core.models import Group, Person
 
 
 def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:
@@ -433,23 +55,3 @@ def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person],
     rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
 
     return rows, affected_teachers, affected_groups
-
-
-def build_weekdays(
-    base: list[tuple[int, str]], wanted_week: CalendarWeek, with_holidays: bool = True
-) -> list[dict]:
-    if with_holidays:
-        holidays_per_weekday = Holiday.in_week(wanted_week)
-
-    weekdays = []
-    for key, name in base[TimePeriod.weekday_min : TimePeriod.weekday_max + 1]:
-        weekday = {
-            "key": key,
-            "name": name,
-            "date": wanted_week[key],
-        }
-        if with_holidays:
-            weekday["holiday"] = holidays_per_weekday[key] if key in holidays_per_weekday else None
-        weekdays.append(weekday)
-
-    return weekdays
diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
index f76fa4663585a5be61570d7afffbdc5264ee13be..7f3ab9a5ddf8e3d3ed3b5d58f6185d17b12bb930 100644
--- a/aleksis/apps/chronos/util/chronos_helpers.py
+++ b/aleksis/apps/chronos/util/chronos_helpers.py
@@ -2,24 +2,17 @@ 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.http import HttpRequest
 from django.shortcuts import get_object_or_404
 
-from guardian.core import ObjectPermissionChecker
+from guardian.shortcuts import get_objects_for_user
 
 from aleksis.apps.cursus.models import Course
 from aleksis.core.models import Announcement, Group, Person, Room
 from aleksis.core.util.core_helpers import get_site_preferences
-from aleksis.core.util.predicates import check_global_permission
 
-from ..managers import TimetableType
-from ..models import (
-    LessonPeriod,
-    LessonSubstitution,
-    Supervision,
-    SupervisionSubstitution,
-)
 from .build import build_substitutions_list
+from ..managers import TimetableType
 
 if TYPE_CHECKING:
     from django.contrib.auth import get_user_model
@@ -50,41 +43,20 @@ def get_el_by_pk(
         return HttpResponseNotFound()
 
 
-def get_substitution_by_id(request: HttpRequest, id_: int, week: int):
-    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
-    wanted_week = lesson_period.lesson.get_calendar_week(week)
-
-    return LessonSubstitution.objects.filter(
-        week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period
-    ).first()
-
-
-def get_supervision_substitution_by_id(request: HttpRequest, id_: int, date: datetime.date):
-    supervision = get_object_or_404(Supervision, pk=id_)
-
-    return SupervisionSubstitution.objects.filter(date=date, supervision=supervision).first()
-
-
 def get_teachers(user: "User"):
     """Get the teachers whose timetables are allowed to be seen by current user."""
-    checker = ObjectPermissionChecker(user)
 
     teachers = (
-        Person.objects.annotate(lessons_count=Count("lesson_events_as_teacher"))
-        .filter(lessons_count__gt=0)
+        Person.objects.annotate(course_count=Count("courses_as_teacher"))
+        .filter(course_count__gt=0)
         .order_by("short_name", "last_name")
     )
 
-    if not check_global_permission(user, "chronos.view_all_person_timetables"):
-        checker.prefetch_perms(teachers)
-
-        wanted_teachers = set()
-
-        for teacher in teachers:
-            if checker.has_perm("core.view_person_timetable", teacher):
-                wanted_teachers.add(teacher.pk)
-
-        teachers = teachers.filter(Q(pk=user.person.pk) | Q(pk__in=wanted_teachers))
+    if not user.has_perm("chronos.view_all_person_timetables"):
+        teachers.filter(
+            Q(pk=user.person.pk)
+            | Q(pk__in=get_objects_for_user(user, "core.view_person_timetable", teachers))
+        )
 
     teachers = teachers.distinct()
 
@@ -93,16 +65,8 @@ def get_teachers(user: "User"):
 
 def get_groups(user: "User"):
     """Get the groups whose timetables are allowed to be seen by current user."""
-    checker = ObjectPermissionChecker(user)
 
-    groups = (
-        Group.objects.for_current_school_term_or_all()
-        .annotate(
-            lessons_count=Count("lesson_events"),
-            child_lessons_count=Count("child_groups__lesson_events"),
-        )
-        .filter(Q(lessons_count__gt=0) | Q(child_lessons_count__gt=0))
-    )
+    groups = Group.objects.for_current_school_term_or_all()
 
     group_types = get_site_preferences()["chronos__group_types_timetables"]
 
@@ -111,20 +75,15 @@ def get_groups(user: "User"):
 
     groups = groups.order_by("short_name", "name")
 
-    if not check_global_permission(user, "chronos.view_all_group_timetables"):
-        checker.prefetch_perms(groups)
-
-        wanted_classes = set()
-
-        for _class in groups:
-            if checker.has_perm("core.view_group_timetable", _class):
-                wanted_classes.add(_class.pk)
+    if not user.has_perm("chronos.view_all_group_timetables"):
+        wanted_groups = get_objects_for_user(user, "core.view_group_timetable", groups)
 
         groups = groups.filter(
-            Q(pk__in=wanted_classes) | Q(members=user.person) | Q(owners=user.person)
+            Q(pk__in=wanted_groups)
+            | Q(members=user.person)
+            | Q(owners=user.person)
+            | Q(pk=user.person.primary_group.pk if user.person.primary_group else None)
         )
-        if user.person.primary_group:
-            groups = groups.filter(Q(pk=user.person.primary_group.pk))
 
     groups = groups.distinct()
 
@@ -133,24 +92,11 @@ def get_groups(user: "User"):
 
 def get_rooms(user: "User"):
     """Get the rooms whose timetables are allowed to be seen by current user."""
-    checker = ObjectPermissionChecker(user)
-
-    rooms = (
-        Room.objects.annotate(lessons_count=Count("lesson_events"))
-        .filter(lessons_count__gt=0)
-        .order_by("short_name", "name")
-    )
-
-    if not check_global_permission(user, "chronos.view_all_room_timetables"):
-        checker.prefetch_perms(rooms)
-
-        wanted_rooms = set()
 
-        for room in rooms:
-            if checker.has_perm("core.view_room_timetable", room):
-                wanted_rooms.add(room.pk)
+    rooms = Room.objects.all().order_by("short_name", "name")
 
-        rooms = rooms.filter(Q(pk__in=wanted_rooms))
+    if not user.has_perm("chronos.view_all_room_timetables"):
+        rooms = get_objects_for_user(user, "core.view_room_timetable", rooms)
 
     rooms = rooms.distinct()
 
diff --git a/aleksis/apps/chronos/util/date.py b/aleksis/apps/chronos/util/date.py
deleted file mode 100644
index 9a2eb10592509b12f4e915a5e115b96d95ce939a..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/util/date.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from datetime import date
-
-from django.utils import timezone
-
-from calendarweek import CalendarWeek
-
-
-def week_weekday_from_date(when: date) -> tuple[CalendarWeek, int]:
-    """Return a tuple of week and weekday from a given date."""
-    return (CalendarWeek.from_date(when), when.weekday())
-
-
-def week_weekday_to_date(week: CalendarWeek, weekday: int) -> date:
-    """Return a date object for one day in a calendar week."""
-    return week[weekday]
-
-
-def week_period_to_date(week: CalendarWeek, period) -> date:
-    """Return the date of a lesson period in a given week."""
-    return period.get_date(week)
-
-
-def get_weeks_for_year(year: int) -> list[CalendarWeek]:
-    """Generate all weeks for one year."""
-    weeks = []
-
-    # Go for all weeks in year and create week list
-    current_week = CalendarWeek(year=year, week=1)
-
-    while current_week.year == year:
-        weeks.append(current_week)
-        current_week += 1
-
-    return weeks
-
-
-def get_current_year() -> int:
-    """Get current year."""
-    return timezone.now().year
diff --git a/aleksis/apps/chronos/util/format.py b/aleksis/apps/chronos/util/format.py
deleted file mode 100644
index 0dd640f3c6e892741eb81d8de61421681160c9c9..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/util/format.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from datetime import date
-from typing import TYPE_CHECKING
-
-from django.utils.formats import date_format
-
-if TYPE_CHECKING:
-    from ..models import TimePeriod
-
-
-def format_m2m(f, attr: str = "short_name") -> str:
-    """Join a attribute of all elements of a ManyToManyField."""
-    return ", ".join([getattr(x, attr) for x in f.all()])
-
-
-def format_date_period(day: date, period: "TimePeriod") -> str:
-    """Format date and time period."""
-    return f"{date_format(day)}, {period.period}."
diff --git a/aleksis/apps/chronos/util/js.py b/aleksis/apps/chronos/util/js.py
deleted file mode 100644
index 96612411ac9d4c25772c258f688706ab583b22cb..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/util/js.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from datetime import date, datetime, time
-
-
-def date_unix(value: date) -> int:
-    """Convert a date object to an UNIX timestamp."""
-    value = datetime.combine(value, time(hour=0, minute=0))
-    return int(value.timestamp()) * 1000
diff --git a/aleksis/apps/chronos/util/notifications.py b/aleksis/apps/chronos/util/notifications.py
deleted file mode 100644
index 847dc599ab47699ec4867d22822f81c8f1ec00ff..0000000000000000000000000000000000000000
--- a/aleksis/apps/chronos/util/notifications.py
+++ /dev/null
@@ -1,210 +0,0 @@
-from datetime import datetime, timedelta
-from typing import Union
-from urllib.parse import urljoin
-
-from django.conf import settings
-from django.urls import reverse
-from django.utils import timezone
-from django.utils.formats import date_format
-from django.utils.translation import gettext_lazy as _
-from django.utils.translation import ngettext
-
-import zoneinfo
-
-from aleksis.core.models import Notification, Person
-from aleksis.core.util.core_helpers import get_site_preferences
-
-from ..models import Event, ExtraLesson, LessonSubstitution, SupervisionSubstitution
-
-
-def send_notifications_for_object(
-    instance: Union[ExtraLesson, LessonSubstitution, Event, SupervisionSubstitution],
-):
-    """Send notifications for a change object."""
-    recipients = []
-    if isinstance(instance, LessonSubstitution):
-        recipients += instance.lesson_period.lesson.teachers.all()
-        recipients += instance.teachers.all()
-        recipients += Person.objects.filter(
-            member_of__in=instance.lesson_period.lesson.groups.all()
-        )
-    elif isinstance(instance, (Event, ExtraLesson)):
-        recipients += instance.teachers.all()
-        recipients += Person.objects.filter(member_of__in=instance.groups.all())
-    elif isinstance(instance, SupervisionSubstitution):
-        recipients.append(instance.teacher)
-        recipients.append(instance.supervision.teacher)
-
-    description = ""
-    if isinstance(instance, LessonSubstitution):
-        # Date, lesson, subject
-        subject = instance.lesson_period.lesson.subject
-        day = instance.date
-        period = instance.lesson_period.period
-
-        if instance.cancelled:
-            description += (
-                _(
-                    "The {subject} lesson in the {period}. period on {day} has been cancelled."
-                ).format(subject=subject.name, period=period.period, day=date_format(day))
-                + " "
-            )
-        else:
-            description += (
-                _(
-                    "The {subject} lesson in the {period}. period "
-                    "on {day} has some current changes."
-                ).format(subject=subject.name, period=period.period, day=date_format(day))
-                + " "
-            )
-
-            if instance.teachers.all():
-                description += (
-                    ngettext(
-                        "The teacher {old} is substituted by {new}.",
-                        "The teachers {old} are substituted by {new}.",
-                        instance.teachers.count(),
-                    ).format(
-                        old=instance.lesson_period.lesson.teacher_names,
-                        new=instance.teacher_names,
-                    )
-                    + " "
-                )
-
-            if instance.subject:
-                description += (
-                    _("The subject is changed to {subject}.").format(subject=instance.subject.name)
-                    + " "
-                )
-
-            if instance.room:
-                description += (
-                    _("The lesson is moved from {old} to {new}.").format(
-                        old=instance.lesson_period.room.name,
-                        new=instance.room.name,
-                    )
-                    + " "
-                )
-
-        if instance.comment:
-            description += (
-                _("There is an additional comment: {comment}.").format(comment=instance.comment)
-                + " "
-            )
-
-    elif isinstance(instance, Event):
-        if instance.date_start != instance.date_end:
-            description += (
-                _(
-                    "There is an event that starts on {date_start}, {period_from}. period "
-                    "and ends on {date_end}, {period_to}. period:"
-                ).format(
-                    date_start=date_format(instance.date_start),
-                    date_end=date_format(instance.date_end),
-                    period_from=instance.period_from.period,
-                    period_to=instance.period_to.period,
-                )
-                + "\n"
-            )
-        else:
-            description += (
-                _(
-                    "There is an event on {date} from the "
-                    "{period_from}. period to the {period_to}. period:"
-                ).format(
-                    date=date_format(instance.date_start),
-                    period_from=instance.period_from.period,
-                    period_to=instance.period_to.period,
-                )
-                + "\n"
-            )
-
-        if instance.groups.all():
-            description += _("Groups: {groups}").format(groups=instance.group_names) + "\n"
-        if instance.teachers.all():
-            description += _("Teachers: {teachers}").format(teachers=instance.teacher_names) + "\n"
-        if instance.rooms.all():
-            description += (
-                _("Rooms: {rooms}").format(
-                    rooms=", ".join([room.name for room in instance.rooms.all()])
-                )
-                + "\n"
-            )
-    elif isinstance(instance, ExtraLesson):
-        description += (
-            _("There is an extra lesson on {date} in the {period}. period:").format(
-                date=date_format(instance.date),
-                period=instance.period.period,
-            )
-            + "\n"
-        )
-
-        if instance.groups.all():
-            description += _("Groups: {groups}").format(groups=instance.group_names) + "\n"
-        if instance.room:
-            description += _("Subject: {subject}").format(subject=instance.subject.name) + "\n"
-        if instance.teachers.all():
-            description += _("Teachers: {teachers}").format(teachers=instance.teacher_names) + "\n"
-        if instance.room:
-            description += _("Room: {room}").format(room=instance.room.name) + "\n"
-        if instance.comment:
-            description += _("Comment: {comment}.").format(comment=instance.comment) + "\n"
-    elif isinstance(instance, SupervisionSubstitution):
-        description += _(
-            "The supervision of {old} on {date} between the {period_from}. period "
-            "and the {period_to}. period in the area {area} is substituted by {new}."
-        ).format(
-            old=instance.supervision.teacher.full_name,
-            date=date_format(instance.date),
-            period_from=instance.supervision.break_item.after_period_number,
-            period_to=instance.supervision.break_item.before_period_number,
-            area=instance.supervision.area.name,
-            new=instance.teacher.full_name,
-        )
-
-    day = instance.date if hasattr(instance, "date") else instance.date_start
-
-    url = urljoin(
-        settings.BASE_URL,
-        reverse(
-            "my_timetable_by_date",
-            args=[day.year, day.month, day.day],
-        ),
-    )
-
-    dt_start, dt_end = instance.time_range
-    dt_start = dt_start.replace(tzinfo=zoneinfo.ZoneInfo(settings.TIME_ZONE))
-    dt_end = dt_end.replace(tzinfo=zoneinfo.ZoneInfo(settings.TIME_ZONE))
-
-    send_time = get_site_preferences()["chronos__time_for_sending_notifications"]
-    number_of_days = get_site_preferences()["chronos__days_in_advance_notifications"]
-
-    start_range = timezone.now().replace(hour=send_time.hour, minute=send_time.minute)
-    if timezone.now().time() > send_time:
-        start_range = start_range - timedelta(days=1)
-    end_range = start_range + timedelta(days=number_of_days)
-
-    if dt_start < start_range and dt_end < end_range:
-        # Skip this, because the change is in the past
-        return
-
-    if dt_start <= end_range and dt_end >= start_range:
-        # Send immediately
-        send_at = timezone.now()
-    else:
-        # Schedule for later
-        send_at = datetime.combine(
-            dt_start.date() - timedelta(days=number_of_days), send_time
-        ).replace(tzinfo=zoneinfo.ZoneInfo(settings.TIME_ZONE))
-
-    for recipient in recipients:
-        if recipient.preferences["chronos__send_notifications"]:
-            n = Notification(
-                recipient=recipient,
-                sender=_("Timetable"),
-                title=_("There are current changes to your timetable."),
-                description=description,
-                link=url,
-                send_at=send_at,
-            )
-            n.save()
diff --git a/pyproject.toml b/pyproject.toml
index bc23360f5990c46b80900f56cf2cf62d7c516fbc..923710bea23b988bf914f23afba9c2f6f98c0c3e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Chronos"
-version = "4.0.0.dev6"
+version = "4.0.0.dev9"
 packages = [
     { include = "aleksis" }
 ]
@@ -51,9 +51,9 @@ priority = "supplemental"
 [tool.poetry.dependencies]
 python = "^3.10"
 calendarweek = "^0.5.0"
-aleksis-core = "^4.0.0.dev11"
+aleksis-core = "^4.0.0.dev13"
 aleksis-app-resint = "^4.0.0.dev1"
-aleksis-app-cursus = "^0.1.0.dev3"
+aleksis-app-cursus = "^0.1.0.dev4"
 
 
 [tool.poetry.plugins."aleksis.app"]