diff --git a/aleksis/apps/alsijil/actions.py b/aleksis/apps/alsijil/actions.py
deleted file mode 100644
index 623a96b95ad28e319d16b4b2595f74352a03655d..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/actions.py
+++ /dev/null
@@ -1,92 +0,0 @@
-from typing import Callable, Sequence
-
-from django.contrib import messages
-from django.contrib.humanize.templatetags.humanize import apnumber
-from django.http import HttpRequest
-from django.template.loader import get_template
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from aleksis.apps.alsijil.models import PersonalNote
-from aleksis.core.models import Notification
-
-
-def mark_as_excused(modeladmin, request, queryset):
-    queryset.filter(absent=True).update(excused=True, excuse_type=None)
-
-
-mark_as_excused.short_description = _("Mark as excused")
-
-
-def mark_as_unexcused(modeladmin, request, queryset):
-    queryset.filter(absent=True).update(excused=False, excuse_type=None)
-
-
-mark_as_unexcused.short_description = _("Mark as unexcused")
-
-
-def mark_as_excuse_type_generator(excuse_type) -> Callable:
-    def mark_as_excuse_type(modeladmin, request, queryset):
-        queryset.filter(absent=True).update(excused=True, excuse_type=excuse_type)
-
-    mark_as_excuse_type.short_description = _(f"Mark as {excuse_type.name}")
-    mark_as_excuse_type.__name__ = f"mark_as_excuse_type_{excuse_type.short_name}"
-
-    return mark_as_excuse_type
-
-
-def delete_personal_note(modeladmin, request, queryset):
-    notes = []
-    for personal_note in queryset:
-        personal_note.reset_values()
-        notes.append(personal_note)
-    PersonalNote.objects.bulk_update(
-        notes, fields=["absent", "excused", "tardiness", "excuse_type", "remarks"]
-    )
-
-
-delete_personal_note.short_description = _("Delete")
-
-
-def send_request_to_check_entry(modeladmin, request: HttpRequest, selected_items: Sequence[dict]):
-    """Send notifications to the teachers of the selected register objects.
-
-    Action for use with ``RegisterObjectTable`` and ``RegisterObjectActionForm``.
-    """
-    # Group class register entries by teachers so each teacher gets just one notification
-    grouped_by_teachers = {}
-    for entry in selected_items:
-        teachers = entry["register_object"].get_teachers().all()
-        for teacher in teachers:
-            grouped_by_teachers.setdefault(teacher, [])
-            grouped_by_teachers[teacher].append(entry)
-
-    template = get_template("alsijil/notifications/check.html")
-    for teacher, items in grouped_by_teachers.items():
-        msg = template.render({"items": items})
-
-        title = _("{} asks you to check some class register entries.").format(
-            request.user.person.addressing_name
-        )
-
-        n = Notification(
-            title=title,
-            description=msg,
-            sender=request.user.person.addressing_name,
-            recipient=teacher,
-            link=request.build_absolute_uri(reverse("overview_me")),
-        )
-        n.save()
-
-    count_teachers = len(grouped_by_teachers.keys())
-    count_items = len(selected_items)
-    messages.success(
-        request,
-        _(
-            "We have successfully sent notifications to "
-            "{count_teachers} persons for {count_items} lessons."
-        ).format(count_teachers=apnumber(count_teachers), count_items=apnumber(count_items)),
-    )
-
-
-send_request_to_check_entry.short_description = _("Ask teacher to check data")
diff --git a/aleksis/apps/alsijil/checks.py b/aleksis/apps/alsijil/checks.py
new file mode 100644
index 0000000000000000000000000000000000000000..310e5d2dbc17cbe95d89f81a4aa83fb0ef7f25b0
--- /dev/null
+++ b/aleksis/apps/alsijil/checks.py
@@ -0,0 +1,157 @@
+import logging
+from datetime import datetime, time
+from typing import TYPE_CHECKING
+
+from django.db.models.query_utils import Q
+from django.utils.translation import gettext as _
+
+from aleksis.apps.chronos.models import LessonEvent
+from aleksis.core.data_checks import DataCheck, IgnoreSolveOption, SolveOption
+
+if TYPE_CHECKING:
+    from aleksis.core.models import DataCheckResult
+
+
+class DeleteRelatedObjectSolveOption(SolveOption):
+    name = "delete"
+    verbose_name = _("Delete object")
+
+    @classmethod
+    def solve(cls, check_result: "DataCheckResult"):
+        check_result.related_object.delete()
+        check_result.delete()
+
+
+class SetGroupsWithCurrentGroupsSolveOption(SolveOption):
+    name = "set_groups_of_person"
+    verbose_name = _("Set current groups")
+
+    @classmethod
+    def solve(cls, check_result: "DataCheckResult"):
+        person = check_result.related_object.person
+        check_result.related_object.groups_of_person.set(person.member_of.all())
+        check_result.delete()
+
+
+class NoParticipationStatusesPersonalNotesInCancelledLessonsDataCheck(DataCheck):
+    name = "no_personal_notes_participation_statuses_in_cancelled_lessons"
+    verbose_name = _(
+        "Ensure that there are no participation statuses and personal notes in cancelled lessons"
+    )
+    problem_name = _("The participation status or personal note is related to a cancelled lesson.")
+    solve_options = {
+        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
+        IgnoreSolveOption.name: IgnoreSolveOption,
+    }
+
+    @classmethod
+    def check_data(cls):
+        from .models import NewPersonalNote, ParticipationStatus
+
+        participation_statuses = ParticipationStatus.objects.filter(
+            related_documentation__amends__in=LessonEvent.objects.filter(cancelled=True)
+        )
+        personal_notes = NewPersonalNote.objects.filter(
+            documentation__amends__in=LessonEvent.objects.filter(cancelled=True)
+        )
+
+        for status in participation_statuses:
+            logging.info(f"Check participation status {status}")
+            cls.register_result(status)
+
+        for note in personal_notes:
+            logging.info(f"Check personal note {note}")
+            cls.register_result(note)
+
+
+class NoGroupsOfPersonsSetInParticipationStatusesDataCheck(DataCheck):
+    name = "no_groups_of_persons_set_in_participation_statuses"
+    verbose_name = _("Ensure that 'groups_of_person' is set for every participation status")
+    problem_name = _("The participation status has no group in 'groups_of_person'.")
+    solve_options = {
+        SetGroupsWithCurrentGroupsSolveOption.name: SetGroupsWithCurrentGroupsSolveOption,
+        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
+        IgnoreSolveOption.name: IgnoreSolveOption,
+    }
+
+    @classmethod
+    def check_data(cls):
+        from .models import ParticipationStatus
+
+        participation_statuses = ParticipationStatus.objects.filter(groups_of_person__isnull=True)
+
+        for status in participation_statuses:
+            logging.info(f"Check participation status {status}")
+            cls.register_result(status)
+
+
+class DocumentationOnHolidaysDataCheck(DataCheck):
+    """Checks for documentation objects on holidays."""
+
+    name = "documentation_on_holidays"
+    verbose_name = _("Ensure that there are no documentations on holidays")
+    problem_name = _("The documentation is on holidays.")
+    solve_options = {
+        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
+        IgnoreSolveOption.name: IgnoreSolveOption,
+    }
+
+    @classmethod
+    def check_data(cls):
+        from aleksis.apps.chronos.models import Holiday
+
+        from .models import Documentation
+
+        holidays = Holiday.objects.all()
+
+        q = Q(pk__in=[])
+        for holiday in holidays:
+            q = q | Q(
+                datetime_start__gte=datetime.combine(holiday.date_start, time.min),
+                datetime_end__lte=datetime.combine(holiday.date_end, time.max),
+            )
+        documentations = Documentation.objects.filter(q)
+
+        for doc in documentations:
+            logging.info(f"Documentation {doc} is on holidays")
+            cls.register_result(doc)
+
+
+class ParticipationStatusPersonalNoteOnHolidaysDataCheck(DataCheck):
+    """Checks for participation status and personal note objects on holidays."""
+
+    name = "participation_status_personal_note_on_holidays"
+    verbose_name = _(
+        "Ensure that there are no participation statuses or personal notes on holidays"
+    )
+    problem_name = _("The participation status or personal note is on holidays.")
+    solve_options = {
+        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
+        IgnoreSolveOption.name: IgnoreSolveOption,
+    }
+
+    @classmethod
+    def check_data(cls):
+        from aleksis.apps.chronos.models import Holiday
+
+        from .models import NewPersonalNote, ParticipationStatus
+
+        holidays = Holiday.objects.all()
+
+        q = Q(pk__in=[])
+        for holiday in holidays:
+            q = q | Q(
+                datetime_start__gte=datetime.combine(holiday.date_start, time.min),
+                datetime_end__lte=datetime.combine(holiday.date_end, time.max),
+            )
+
+        participation_statuses = ParticipationStatus.objects.filter(q)
+        personal_notes = NewPersonalNote.objects.filter(q)
+
+        for status in participation_statuses:
+            logging.info(f"Participation status {status} is on holidays")
+            cls.register_result(status)
+
+        for note in personal_notes:
+            logging.info(f"Personal note {note} is on holidays")
+            cls.register_result(note)
diff --git a/aleksis/apps/alsijil/data_checks.py b/aleksis/apps/alsijil/data_checks.py
deleted file mode 100644
index 87e703a0cb4fb17cff6be360cbea52895a6fa775..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/data_checks.py
+++ /dev/null
@@ -1,181 +0,0 @@
-import logging
-from typing import TYPE_CHECKING
-
-from django.db.models import F
-from django.db.models.query_utils import Q
-from django.utils.translation import gettext as _
-
-from aleksis.core.data_checks import DataCheck, IgnoreSolveOption, SolveOption
-
-if TYPE_CHECKING:
-    from aleksis.core.models import DataCheckResult
-
-
-class DeleteRelatedObjectSolveOption(SolveOption):
-    name = "delete"
-    verbose_name = _("Delete object")
-
-    @classmethod
-    def solve(cls, check_result: "DataCheckResult"):
-        check_result.related_object.delete()
-        check_result.delete()
-
-
-class SetGroupsWithCurrentGroupsSolveOption(SolveOption):
-    name = "set_groups_of_person"
-    verbose_name = _("Set current groups")
-
-    @classmethod
-    def solve(cls, check_result: "DataCheckResult"):
-        person = check_result.related_object.person
-        check_result.related_object.groups_of_person.set(person.member_of.all())
-        check_result.delete()
-
-
-class ResetPersonalNoteSolveOption(SolveOption):
-    name = "reset_personal_note"
-    verbose_name = _("Reset personal note to defaults")
-
-    @classmethod
-    def solve(cls, check_result: "DataCheckResult"):
-        note = check_result.related_object
-        note.reset_values()
-        note.save()
-        check_result.delete()
-
-
-class NoPersonalNotesInCancelledLessonsDataCheck(DataCheck):
-    name = "no_personal_notes_in_cancelled_lessons"
-    verbose_name = _("Ensure that there are no personal notes in cancelled lessons")
-    problem_name = _("The personal note is related to a cancelled lesson.")
-    solve_options = {
-        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
-        IgnoreSolveOption.name: IgnoreSolveOption,
-    }
-
-    @classmethod
-    def check_data(cls):
-        from .models import PersonalNote
-
-        personal_notes = (
-            PersonalNote.objects.not_empty()
-            .filter(
-                lesson_period__substitutions__cancelled=True,
-                lesson_period__substitutions__week=F("week"),
-                lesson_period__substitutions__year=F("year"),
-            )
-            .prefetch_related("lesson_period", "lesson_period__substitutions")
-        )
-
-        for note in personal_notes:
-            logging.info(f"Check personal note {note}")
-            cls.register_result(note)
-
-
-class NoGroupsOfPersonsSetInPersonalNotesDataCheck(DataCheck):
-    name = "no_groups_of_persons_set_in_personal_notes"
-    verbose_name = _("Ensure that 'groups_of_person' is set for every personal note")
-    problem_name = _("The personal note has no group in 'groups_of_person'.")
-    solve_options = {
-        SetGroupsWithCurrentGroupsSolveOption.name: SetGroupsWithCurrentGroupsSolveOption,
-        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
-        IgnoreSolveOption.name: IgnoreSolveOption,
-    }
-
-    @classmethod
-    def check_data(cls):
-        from .models import PersonalNote
-
-        personal_notes = PersonalNote.objects.filter(groups_of_person__isnull=True)
-
-        for note in personal_notes:
-            logging.info(f"Check personal note {note}")
-            cls.register_result(note)
-
-
-class LessonDocumentationOnHolidaysDataCheck(DataCheck):
-    """Checks for lesson documentation objects on holidays.
-
-    This ignores empty lesson documentation as they are created by default.
-    """
-
-    name = "lesson_documentation_on_holidays"
-    verbose_name = _("Ensure that there are no filled out lesson documentations on holidays")
-    problem_name = _("The lesson documentation is on holidays.")
-    solve_options = {
-        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
-        IgnoreSolveOption.name: IgnoreSolveOption,
-    }
-
-    @classmethod
-    def check_data(cls):
-        from aleksis.apps.chronos.models import Holiday
-
-        from .models import LessonDocumentation
-
-        holidays = Holiday.objects.all()
-
-        documentations = LessonDocumentation.objects.not_empty().annotate_date_range()
-
-        q = Q(pk__in=[])
-        for holiday in holidays:
-            q = q | Q(day_end__gte=holiday.date_start, day_start__lte=holiday.date_end)
-        documentations = documentations.filter(q)
-
-        for doc in documentations:
-            logging.info(f"Lesson documentation {doc} is on holidays")
-            cls.register_result(doc)
-
-
-class PersonalNoteOnHolidaysDataCheck(DataCheck):
-    """Checks for personal note objects on holidays.
-
-    This ignores empty personal notes as they are created by default.
-    """
-
-    name = "personal_note_on_holidays"
-    verbose_name = _("Ensure that there are no filled out personal notes on holidays")
-    problem_name = _("The personal note is on holidays.")
-    solve_options = {
-        DeleteRelatedObjectSolveOption.name: DeleteRelatedObjectSolveOption,
-        IgnoreSolveOption.name: IgnoreSolveOption,
-    }
-
-    @classmethod
-    def check_data(cls):
-        from aleksis.apps.chronos.models import Holiday
-
-        from .models import PersonalNote
-
-        holidays = Holiday.objects.all()
-
-        personal_notes = PersonalNote.objects.not_empty().annotate_date_range()
-
-        q = Q(pk__in=[])
-        for holiday in holidays:
-            q = q | Q(day_end__gte=holiday.date_start, day_start__lte=holiday.date_end)
-        personal_notes = personal_notes.filter(q)
-
-        for note in personal_notes:
-            logging.info(f"Personal note {note} is on holidays")
-            cls.register_result(note)
-
-
-class ExcusesWithoutAbsences(DataCheck):
-    name = "excuses_without_absences"
-    verbose_name = _("Ensure that there are no excused personal notes without an absence")
-    problem_name = _("The personal note is marked as excused, but not as absent.")
-    solve_options = {
-        ResetPersonalNoteSolveOption.name: ResetPersonalNoteSolveOption,
-        IgnoreSolveOption.name: IgnoreSolveOption,
-    }
-
-    @classmethod
-    def check_data(cls):
-        from .models import PersonalNote
-
-        personal_notes = PersonalNote.objects.filter(excused=True, absent=False)
-
-        for note in personal_notes:
-            logging.info(f"Check personal note {note}")
-            cls.register_result(note)
diff --git a/aleksis/apps/alsijil/filters.py b/aleksis/apps/alsijil/filters.py
deleted file mode 100644
index 0de3f04876edf978403287ed1031be79ba88790c..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/filters.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from django.utils.translation import gettext as _
-
-from django_filters import CharFilter, DateFilter, FilterSet
-from material import Layout, Row
-
-from aleksis.core.models import SchoolTerm
-
-from .models import PersonalNote
-
-
-class PersonalNoteFilter(FilterSet):
-    day_start = DateFilter(lookup_expr="gte", label=_("After"))
-    day_end = DateFilter(lookup_expr="lte", label=_("Before"))
-    subject = CharFilter(lookup_expr="icontains", label=_("Subject"))
-
-    def __init__(self, data=None, *args, **kwargs):
-        if data is not None:
-            data = data.copy()
-
-            current_school_term = SchoolTerm.current
-            if not data.get("day_start") and current_school_term:
-                data["day_start"] = current_school_term.date_start
-
-            for name, f in self.base_filters.items():
-                initial = f.extra.get("initial")
-                if not data.get(name) and initial:
-                    data[name] = initial
-
-        super().__init__(data, *args, **kwargs)
-        self.form.fields["tardiness__lt"].label = _("Tardiness is lower than")
-        self.form.fields["tardiness__gt"].label = _("Tardiness is bigger than")
-        self.form.layout = Layout(
-            Row("subject"),
-            Row("day_start", "day_end"),
-            Row("absent", "excused", "excuse_type"),
-            Row("tardiness__gt", "tardiness__lt", "extra_marks"),
-        )
-
-    class Meta:
-        model = PersonalNote
-        fields = {
-            "excused": ["exact"],
-            "tardiness": ["lt", "gt"],
-            "absent": ["exact"],
-            "excuse_type": ["exact"],
-            "extra_marks": ["exact"],
-        }
diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 35cdf3b70552de594a0c7ef8811c53220ddef071..45784137cb114b360709eedfbdab7f5a16b3c94c 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -1,268 +1,19 @@
-from datetime import datetime, timedelta
-from typing import Optional, Sequence
-
 from django import forms
-from django.core.exceptions import ValidationError
-from django.db.models import Count, Q
-from django.http import HttpRequest
-from django.utils import timezone
+from django.db.models import Q
 from django.utils.translation import gettext_lazy as _
 
-from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
-from guardian.shortcuts import get_objects_for_user
-from material import Fieldset, Layout, Row
+from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget
+from material import Layout, Row
 
-from aleksis.apps.chronos.managers import TimetableType
-from aleksis.apps.chronos.models import LessonPeriod, Subject, TimePeriod
-from aleksis.core.forms import ActionForm, ListActionForm
-from aleksis.core.models import Group, Person, SchoolTerm
+from aleksis.core.models import Group, Person
 from aleksis.core.util.core_helpers import get_site_preferences
-from aleksis.core.util.predicates import check_global_permission
 
-from .actions import (
-    delete_personal_note,
-    mark_as_excuse_type_generator,
-    mark_as_excused,
-    mark_as_unexcused,
-    send_request_to_check_entry,
-)
 from .models import (
-    ExcuseType,
-    ExtraMark,
     GroupRole,
     GroupRoleAssignment,
-    LessonDocumentation,
-    PersonalNote,
-)
-
-
-class LessonDocumentationForm(forms.ModelForm):
-    class Meta:
-        model = LessonDocumentation
-        fields = ["topic", "homework", "group_note"]
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        self.fields["homework"].label = _("Homework for the next lesson")
-        if (
-            self.instance.lesson_period
-            and get_site_preferences()["alsijil__allow_carry_over_same_week"]
-        ):
-            self.fields["carry_over_week"] = forms.BooleanField(
-                label=_("Carry over data to all other lessons with the same subject in this week"),
-                initial=True,
-                required=False,
-            )
-
-    def save(self, **kwargs):
-        lesson_documentation = super().save(commit=True)
-        if (
-            get_site_preferences()["alsijil__allow_carry_over_same_week"]
-            and self.cleaned_data["carry_over_week"]
-            and (
-                lesson_documentation.topic
-                or lesson_documentation.homework
-                or lesson_documentation.group_note
-            )
-            and lesson_documentation.lesson_period
-        ):
-            lesson_documentation.carry_over_data(
-                LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson)
-            )
-
-
-class PersonalNoteForm(forms.ModelForm):
-    class Meta:
-        model = PersonalNote
-        fields = ["absent", "tardiness", "excused", "excuse_type", "extra_marks", "remarks"]
-
-    person_name = forms.CharField(disabled=True)
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.fields["person_name"].widget.attrs.update(
-            {"class": "alsijil-lesson-personal-note-name"}
-        )
-        self.fields["person_name"].widget = forms.HiddenInput()
-
-        if self.instance and getattr(self.instance, "person", None):
-            self.fields["person_name"].initial = str(self.instance.person)
-
-
-class SelectForm(forms.Form):
-    layout = Layout(Row("group", "teacher"))
-
-    group = forms.ModelChoiceField(
-        queryset=None,
-        label=_("Group"),
-        required=False,
-        widget=Select2Widget,
-    )
-    teacher = forms.ModelChoiceField(
-        queryset=None,
-        label=_("Teacher"),
-        required=False,
-        widget=Select2Widget,
-    )
-
-    def clean(self) -> dict:
-        data = super().clean()
-
-        if data.get("group") and not data.get("teacher"):
-            type_ = TimetableType.GROUP
-            instance = data["group"]
-        elif data.get("teacher") and not data.get("group"):
-            type_ = TimetableType.TEACHER
-            instance = data["teacher"]
-        elif not data.get("teacher") and not data.get("group"):
-            return data
-        else:
-            raise ValidationError(_("You can't select a group and a teacher both."))
-
-        data["type_"] = type_
-        data["instance"] = instance
-        return data
-
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super().__init__(*args, **kwargs)
-
-        person = self.request.user.person
-
-        group_qs = Group.get_groups_with_lessons()
-
-        # Filter selectable groups by permissions
-        if not check_global_permission(self.request.user, "alsijil.view_week"):
-            # 1) All groups the user is allowed to see the week view by object permissions
-            # 2) All groups the user is a member of an owner of
-            # 3) If the corresponding preference is turned on:
-            # All groups that have a parent group the user is an owner of
-            group_qs = (
-                group_qs.filter(
-                    pk__in=get_objects_for_user(
-                        self.request.user, "core.view_week_class_register_group", Group
-                    ).values_list("pk", flat=True)
-                )
-            ).union(
-                group_qs.filter(
-                    Q(members=person) | Q(owners=person) | Q(parent_groups__owners=person)
-                    if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"]
-                    else Q(members=person) | Q(owners=person)
-                )
-            )
-
-        # Flatten query by filtering groups by pk
-        self.fields["group"].queryset = Group.objects.filter(
-            pk__in=list(group_qs.values_list("pk", flat=True))
-        )
-
-        teacher_qs = Person.objects.annotate(lessons_count=Count("lessons_as_teacher")).filter(
-            lessons_count__gt=0
-        )
-
-        # Filter selectable teachers by permissions
-        if not check_global_permission(self.request.user, "alsijil.view_week"):
-            # If the user hasn't got the global permission and the inherit privileges preference is
-            # turned off, the user is only allowed to see their own person. Otherwise, the user
-            # is allowed to see all persons that teach lessons that the given groups attend.
-            if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"]:
-                teacher_pks = []
-                for group in group_qs:
-                    for lesson in group.lessons.all():
-                        for teacher in lesson.teachers.all():
-                            teacher_pks.append(teacher.pk)
-                teacher_qs = teacher_qs.filter(pk__in=teacher_pks)
-            else:
-                teacher_qs = teacher_qs.filter(pk=person.pk)
-
-        self.fields["teacher"].queryset = teacher_qs
-
-
-PersonalNoteFormSet = forms.modelformset_factory(
-    PersonalNote, form=PersonalNoteForm, max_num=0, extra=0
 )
 
 
-class RegisterAbsenceForm(forms.Form):
-    layout = Layout(
-        Fieldset("", "person"),
-        Fieldset("", Row("date_start", "date_end"), Row("from_period", "to_period")),
-        Fieldset("", Row("absent", "excused"), Row("excuse_type"), Row("remarks")),
-    )
-    person = forms.ModelChoiceField(label=_("Person"), queryset=None, widget=Select2Widget)
-    date_start = forms.DateField(label=_("Start date"), initial=datetime.today)
-    date_end = forms.DateField(label=_("End date"), initial=datetime.today)
-    from_period = forms.ChoiceField(label=_("Start period"))
-    to_period = forms.ChoiceField(label=_("End period"))
-    absent = forms.BooleanField(label=_("Absent"), initial=True, required=False)
-    excused = forms.BooleanField(label=_("Excused"), initial=True, required=False)
-    excuse_type = forms.ModelChoiceField(
-        label=_("Excuse type"),
-        queryset=ExcuseType.objects.all(),
-        widget=Select2Widget,
-        required=False,
-    )
-    remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False)
-
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super().__init__(*args, **kwargs)
-        period_choices = TimePeriod.period_choices
-
-        if self.request.user.has_perm("alsijil.register_absence"):
-            self.fields["person"].queryset = Person.objects.all()
-        else:
-            persons_qs = Person.objects.filter(
-                Q(
-                    pk__in=get_objects_for_user(
-                        self.request.user, "core.register_absence_person", Person
-                    )
-                )
-                | Q(primary_group__owners=self.request.user.person)
-                | Q(
-                    member_of__in=get_objects_for_user(
-                        self.request.user, "core.register_absence_group", Group
-                    )
-                )
-            ).distinct()
-
-            self.fields["person"].queryset = persons_qs
-
-        self.fields["from_period"].choices = period_choices
-        self.fields["to_period"].choices = period_choices
-        self.fields["from_period"].initial = TimePeriod.period_min
-        self.fields["to_period"].initial = TimePeriod.period_max
-
-
-class ExtraMarkForm(forms.ModelForm):
-    layout = Layout("short_name", "name")
-
-    class Meta:
-        model = ExtraMark
-        fields = ["short_name", "name"]
-
-
-class ExcuseTypeForm(forms.ModelForm):
-    layout = Layout("short_name", "name", "count_as_absent")
-
-    class Meta:
-        model = ExcuseType
-        fields = ["short_name", "name", "count_as_absent"]
-
-
-class PersonOverviewForm(ActionForm):
-    def get_actions(self):
-        return (
-            [mark_as_excused, mark_as_unexcused]
-            + [
-                mark_as_excuse_type_generator(excuse_type)
-                for excuse_type in ExcuseType.objects.all()
-            ]
-            + [delete_personal_note]
-        )
-
-
 class GroupRoleForm(forms.ModelForm):
     layout = Layout("name", "icon", "colour")
 
@@ -357,74 +108,3 @@ class GroupRoleAssignmentEditForm(forms.ModelForm):
     class Meta:
         model = GroupRoleAssignment
         fields = ["date_start", "date_end"]
-
-
-class FilterRegisterObjectForm(forms.Form):
-    """Form for filtering register objects in ``RegisterObjectTable``."""
-
-    layout = Layout(
-        Row("school_term", "date_start", "date_end"), Row("has_documentation", "group", "subject")
-    )
-    school_term = forms.ModelChoiceField(queryset=None, label=_("School term"))
-    has_documentation = forms.NullBooleanField(label=_("Has lesson documentation"))
-    group = forms.ModelChoiceField(queryset=None, label=_("Group"), required=False)
-    subject = forms.ModelChoiceField(queryset=None, label=_("Subject"), required=False)
-    date_start = forms.DateField(label=_("Start date"))
-    date_end = forms.DateField(label=_("End date"))
-
-    @classmethod
-    def get_initial(cls, has_documentation: Optional[bool] = None):
-        date_end = timezone.now().date()
-        date_start = date_end - timedelta(days=30)
-        school_term = SchoolTerm.current
-
-        # If there is no current school year, use last known school year.
-        if not school_term:
-            school_term = SchoolTerm.objects.all().last()
-
-        return {
-            "school_term": school_term,
-            "date_start": date_start,
-            "date_end": date_end,
-            "has_documentation": has_documentation,
-        }
-
-    def __init__(
-        self,
-        request: HttpRequest,
-        *args,
-        for_person: bool = True,
-        default_documentation: Optional[bool] = None,
-        groups: Optional[Sequence[Group]] = None,
-        **kwargs,
-    ):
-        self.request = request
-        person = self.request.user.person
-
-        kwargs["initial"] = self.get_initial(has_documentation=default_documentation)
-        super().__init__(*args, **kwargs)
-
-        self.fields["school_term"].queryset = SchoolTerm.objects.all()
-
-        if not groups and for_person:
-            groups = Group.objects.filter(
-                Q(lessons__teachers=person)
-                | Q(lessons__lesson_periods__substitutions__teachers=person)
-                | Q(events__teachers=person)
-                | Q(extra_lessons__teachers=person)
-            ).distinct()
-        elif not for_person:
-            groups = Group.objects.all()
-        self.fields["group"].queryset = groups
-
-        # Filter subjects by selectable groups
-        subject_qs = Subject.objects.filter(
-            Q(lessons__groups__in=groups) | Q(extra_lessons__groups__in=groups)
-        ).distinct()
-        self.fields["subject"].queryset = subject_qs
-
-
-class RegisterObjectActionForm(ListActionForm):
-    """Action form for managing register objects for use with ``RegisterObjectTable``."""
-
-    actions = [send_request_to_check_entry]
diff --git a/aleksis/apps/alsijil/managers.py b/aleksis/apps/alsijil/managers.py
index dc29fae25c189c7c2250279a6e8d86206f52e520..983a29b8d73196278afc3de7b1e25f484e62f02f 100644
--- a/aleksis/apps/alsijil/managers.py
+++ b/aleksis/apps/alsijil/managers.py
@@ -1,141 +1,21 @@
 from datetime import date, datetime
 from typing import TYPE_CHECKING, Optional, Sequence, Union
 
-from django.db.models import Case, ExpressionWrapper, F, Func, QuerySet, Value, When
-from django.db.models.fields import DateField
-from django.db.models.functions import Concat
+from django.db.models import QuerySet
 from django.db.models.query import Prefetch
 from django.db.models.query_utils import Q
-from django.utils.translation import gettext as _
 
 from calendarweek import CalendarWeek
 
-from aleksis.apps.chronos.managers import DateRangeQuerySetMixin
-from aleksis.core.managers import AlekSISBaseManagerWithoutMigrations, RecurrencePolymorphicManager
+from aleksis.core.managers import (
+    AlekSISBaseManagerWithoutMigrations,
+    RecurrencePolymorphicManager,
+)
 
 if TYPE_CHECKING:
     from aleksis.core.models import Group
 
 
-class RegisterObjectRelatedQuerySet(QuerySet):
-    """Common queryset for personal notes and lesson documentations with shared API."""
-
-    def _get_weekday_to_date(self, weekday_name, year_name="year", week_name="week"):
-        """Get a ORM function which converts a weekday, a week and a year to a date."""
-        return ExpressionWrapper(
-            Func(
-                Concat(F(year_name), F(week_name)),
-                Value("IYYYIW"),
-                output_field=DateField(),
-                function="TO_DATE",
-            )
-            + F(weekday_name),
-            output_field=DateField(),
-        )
-
-    def annotate_day(self) -> QuerySet:
-        """Annotate every personal note/lesson documentation with the real date.
-
-        Attribute name: ``day``
-
-        .. note::
-            For events, this will annotate ``None``.
-        """
-        return self.annotate(
-            day=Case(
-                When(
-                    lesson_period__isnull=False,
-                    then=self._get_weekday_to_date("lesson_period__period__weekday"),
-                ),
-                When(
-                    extra_lesson__isnull=False,
-                    then=self._get_weekday_to_date(
-                        "extra_lesson__period__weekday", "extra_lesson__year", "extra_lesson__week"
-                    ),
-                ),
-            )
-        )
-
-    def annotate_date_range(self) -> QuerySet:
-        """Annotate every personal note/lesson documentation with the real date.
-
-        Attribute names: ``day_start``, ``day_end``
-
-        .. note::
-            For lesson periods and extra lessons,
-            this will annotate the same date for start and end day.
-        """
-        return self.annotate_day().annotate(
-            day_start=Case(
-                When(day__isnull=False, then="day"),
-                When(day__isnull=True, then="event__date_start"),
-            ),
-            day_end=Case(
-                When(day__isnull=False, then="day"),
-                When(day__isnull=True, then="event__date_end"),
-            ),
-        )
-
-    def annotate_subject(self) -> QuerySet:
-        """Annotate lesson documentations with the subjects."""
-        return self.annotate(
-            subject=Case(
-                When(
-                    lesson_period__isnull=False,
-                    then="lesson_period__lesson__subject__name",
-                ),
-                When(
-                    extra_lesson__isnull=False,
-                    then="extra_lesson__subject__name",
-                ),
-                default=Value(_("Event")),
-            )
-        )
-
-
-class PersonalNoteManager(AlekSISBaseManagerWithoutMigrations):
-    """Manager adding specific methods to personal notes."""
-
-    def get_queryset(self):
-        """Ensure all related lesson and person data are loaded as well."""
-        return (
-            super()
-            .get_queryset()
-            .select_related(
-                "person",
-                "excuse_type",
-                "lesson_period",
-                "lesson_period__lesson",
-                "lesson_period__lesson__subject",
-                "lesson_period__period",
-                "lesson_period__lesson__validity",
-                "lesson_period__lesson__validity__school_term",
-                "event",
-                "extra_lesson",
-                "extra_lesson__subject",
-            )
-            .prefetch_related("extra_marks")
-        )
-
-
-class PersonalNoteQuerySet(RegisterObjectRelatedQuerySet, QuerySet):
-    def not_empty(self):
-        """Get all not empty personal notes."""
-        return self.filter(
-            ~Q(remarks="") | Q(absent=True) | ~Q(tardiness=0) | Q(extra_marks__isnull=False)
-        )
-
-
-class LessonDocumentationManager(AlekSISBaseManagerWithoutMigrations):
-    pass
-
-
-class LessonDocumentationQuerySet(RegisterObjectRelatedQuerySet, QuerySet):
-    def not_empty(self):
-        """Get all not empty lesson documentations."""
-        return self.filter(~Q(topic="") | ~Q(group_note="") | ~Q(homework=""))
-
-
 class GroupRoleManager(AlekSISBaseManagerWithoutMigrations):
     pass
 
@@ -164,7 +44,7 @@ class GroupRoleAssignmentManager(AlekSISBaseManagerWithoutMigrations):
     pass
 
 
-class GroupRoleAssignmentQuerySet(DateRangeQuerySetMixin, QuerySet):
+class GroupRoleAssignmentQuerySet(QuerySet):
     def within_dates(self, start: date, end: date):
         """Filter for all role assignments within a date range."""
         return self.filter(
diff --git a/aleksis/apps/alsijil/migrations/0001_initial.py b/aleksis/apps/alsijil/migrations/0001_initial.py
index 344efdac0df75135647a3093ee20b8d228570b22..faa5eab2c749d234ccadaf31eeff97bb448dbc8e 100644
--- a/aleksis/apps/alsijil/migrations/0001_initial.py
+++ b/aleksis/apps/alsijil/migrations/0001_initial.py
@@ -41,7 +41,6 @@ class Migration(migrations.Migration):
                     models.CharField(
                         max_length=30,
                         unique=True,
-                        validators=[aleksis.apps.alsijil.models.isidentifier],
                         verbose_name="Identifier",
                     ),
                 ),
diff --git a/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py b/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py
index 4cf8743b0ba5a2e16e0e1dcb491fe49b72dbb9c4..133cff82fb04e5a04e31dfe16f3efa58cc7f2133 100644
--- a/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py
+++ b/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py
@@ -1,8 +1,7 @@
 # Generated by Django 3.0.9 on 2020-08-15 09:39
 
 from django.db import migrations, models
-
-import aleksis.apps.chronos.util.date
+from django.utils import timezone
 
 
 def migrate_data(apps, schema_editor):
@@ -39,7 +38,7 @@ class Migration(migrations.Migration):
             model_name="lessondocumentation",
             name="year",
             field=models.IntegerField(
-                default=aleksis.apps.chronos.util.date.get_current_year,
+                default=lambda: timezone.now().year,
                 verbose_name="Year",
             ),
         ),
@@ -47,7 +46,7 @@ class Migration(migrations.Migration):
             model_name="personalnote",
             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/alsijil/migrations/0009_group_roles.py b/aleksis/apps/alsijil/migrations/0009_group_roles.py
index 78f6f3666cb385a10e6d7101738e66b987fc68f2..ce978b3833b2c9b961c237ef24a4873710b5b0fd 100644
--- a/aleksis/apps/alsijil/migrations/0009_group_roles.py
+++ b/aleksis/apps/alsijil/migrations/0009_group_roles.py
@@ -44,6 +44,6 @@ class Migration(migrations.Migration):
                 'verbose_name': 'Group role assignment',
                 'verbose_name_plural': 'Group role assignments',
             },
-            bases=(aleksis.apps.chronos.managers.GroupPropertiesMixin, models.Model),
+            bases=(models.Model,),
         ),
     ]
diff --git a/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py b/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py
index 1c3bf9ec00c3d3242fd17cbc2ef60d1b097a4458..39878398d3aae13a0c0f870f4fb8f34d88c1450e 100644
--- a/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py
+++ b/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py
@@ -1,6 +1,5 @@
 # Generated by Django 3.1.5 on 2021-01-10 15:48
 
-import aleksis.apps.chronos.util.date
 from django.db import migrations, models
 import django.db.models.deletion
 
diff --git a/aleksis/apps/alsijil/migrations/0024_check_new_models.py b/aleksis/apps/alsijil/migrations/0024_check_new_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..da52a87226e6591939b5888c37667ea79ab7f47f
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0024_check_new_models.py
@@ -0,0 +1,27 @@
+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
+
+    ExcuseType = apps.get_model('alsijil', 'ExcuseType')
+    PersonalNote = apps.get_model('alsijil', 'PersonalNote')
+    LessonDocumentation = apps.get_model('alsijil', 'LessonDocumentation')
+
+    model_types = [ExcuseType, PersonalNote, LessonDocumentation]
+
+    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 = [
+        ('alsijil', '0023_add_tardiness_and_rework_constraints'),
+    ]
+
+    operations = [
+        migrations.RunPython(check_for_migration),
+    ]
diff --git a/aleksis/apps/alsijil/migrations/0025_remove_old_models.py b/aleksis/apps/alsijil/migrations/0025_remove_old_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..3093767e6a32330540ea6e90ea398bca03eeb7c6
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0025_remove_old_models.py
@@ -0,0 +1,62 @@
+# Generated by Django 5.0.8 on 2024-08-06 16:18
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('alsijil', '0024_check_new_models'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='excuse_type',
+        ),
+        migrations.RemoveField(
+            model_name='lessondocumentation',
+            name='event',
+        ),
+        migrations.RemoveField(
+            model_name='lessondocumentation',
+            name='extra_lesson',
+        ),
+        migrations.RemoveField(
+            model_name='lessondocumentation',
+            name='lesson_period',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='event',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='extra_lesson',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='extra_marks',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='groups_of_person',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='lesson_period',
+        ),
+        migrations.RemoveField(
+            model_name='personalnote',
+            name='person',
+        ),
+        migrations.DeleteModel(
+            name='ExcuseType',
+        ),
+        migrations.DeleteModel(
+            name='LessonDocumentation',
+        ),
+        migrations.DeleteModel(
+            name='PersonalNote',
+        ),
+    ]
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index abbedd709dd3604172b831c2c931a2ea5f198b5a..b3186f6103de2d0f6781d03d9562e468124e80f3 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -1,188 +1,6 @@
-from datetime import date
-from typing import Dict, Iterable, Iterator, Optional, Union
-
-from django.db.models import Exists, FilteredRelation, OuterRef, Q, QuerySet
-from django.db.models.aggregates import Count, Sum
-from django.urls import reverse
 from django.utils.translation import gettext as _
 
-from calendarweek import CalendarWeek
-
-from aleksis.apps.alsijil.managers import PersonalNoteQuerySet
-from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.core.models import Group, Person
-from aleksis.core.util.core_helpers import get_site_preferences
-
-from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
-
-
-def alsijil_url(
-    self: Union[LessonPeriod, Event, ExtraLesson], week: Optional[CalendarWeek] = None
-) -> str:
-    """Build URL for the detail page of register objects.
-
-    Works with `LessonPeriod`, `Event` and `ExtraLesson`.
-
-    On `LessonPeriod` objects, it will work with annotated or passed weeks.
-    """
-    if isinstance(self, LessonPeriod):
-        week = week or self.week
-        return reverse("lesson_period", args=[week.year, week.week, self.pk])
-    else:
-        return reverse(self.label_, args=[self.pk])
-
-
-LessonPeriod.property_(alsijil_url)
-LessonPeriod.method(alsijil_url, "get_alsijil_url")
-Event.property_(alsijil_url)
-Event.method(alsijil_url, "get_alsijil_url")
-ExtraLesson.property_(alsijil_url)
-ExtraLesson.method(alsijil_url, "get_alsijil_url")
-
-
-@Person.method
-def mark_absent(
-    self,
-    day: date,
-    from_period: int = 0,
-    absent: bool = True,
-    excused: bool = False,
-    excuse_type: Optional[ExcuseType] = None,
-    remarks: str = "",
-    to_period: Optional[int] = None,
-    dry_run: bool = False,
-):
-    """Mark a person absent for all lessons in a day, optionally starting with a period number.
-
-    This function creates `PersonalNote` objects for every `LessonPeriod` and `ExtraLesson`
-    the person participates in on the selected day and marks them as absent/excused.
-
-    :param dry_run: With this activated, the function won't change any data
-        and just return the count of affected lessons
-
-    :return: Count of affected lesson periods
-
-    ..note:: Only available when AlekSIS-App-Alsijil is installed.
-
-    :Date: 2019-11-10
-    :Authors:
-        - Dominik George <dominik.george@teckids.org>
-    """
-    wanted_week = CalendarWeek.from_date(day)
-
-    # Get all lessons of this person on the specified day
-    lesson_periods = (
-        self.lesson_periods_as_participant.on_day(day)
-        .filter(period__period__gte=from_period)
-        .annotate_week(wanted_week)
-    )
-    extra_lessons = (
-        ExtraLesson.objects.filter(groups__members=self)
-        .on_day(day)
-        .filter(period__period__gte=from_period)
-    )
-
-    if to_period:
-        lesson_periods = lesson_periods.filter(period__period__lte=to_period)
-        extra_lessons = extra_lessons.filter(period__period__lte=to_period)
-
-    # Create and update all personal notes for the discovered lesson periods
-    if not dry_run:
-        for register_object in list(lesson_periods) + list(extra_lessons):
-            if isinstance(register_object, LessonPeriod):
-                sub = register_object.get_substitution()
-                q_attrs = dict(
-                    week=wanted_week.week, year=wanted_week.year, lesson_period=register_object
-                )
-            else:
-                sub = None
-                q_attrs = dict(extra_lesson=register_object)
-
-            if sub and sub.cancelled:
-                continue
-
-            personal_note, created = (
-                PersonalNote.objects.select_related(None)
-                .prefetch_related(None)
-                .update_or_create(
-                    person=self,
-                    defaults={
-                        "absent": absent,
-                        "excused": excused,
-                        "excuse_type": excuse_type,
-                    },
-                    **q_attrs,
-                )
-            )
-            personal_note.groups_of_person.set(self.member_of.all())
-
-            if remarks:
-                if personal_note.remarks:
-                    personal_note.remarks += "; %s" % remarks
-                else:
-                    personal_note.remarks = remarks
-                personal_note.save()
-
-    return lesson_periods.count() + extra_lessons.count()
-
-
-def get_personal_notes(
-    self, persons: QuerySet, wanted_week: Optional[CalendarWeek] = None
-) -> PersonalNoteQuerySet:
-    """Get all personal notes for that register object in a specified week.
-
-    The week is optional for extra lessons and events as they have own date information.
-
-    Returns all linked `PersonalNote` objects,
-    filtered by the given week for `LessonPeriod` objects,
-    creating those objects that haven't been created yet.
-
-    ..note:: Only available when AlekSIS-App-Alsijil is installed.
-
-    :Date: 2019-11-09
-    :Authors:
-        - Dominik George <dominik.george@teckids.org>
-    """
-    # Find all persons in the associated groups that do not yet have a personal note for this lesson
-    if isinstance(self, LessonPeriod):
-        q_attrs = dict(week=wanted_week.week, year=wanted_week.year, lesson_period=self)
-    elif isinstance(self, Event):
-        q_attrs = dict(event=self)
-    else:
-        q_attrs = dict(extra_lesson=self)
-
-    missing_persons = persons.annotate(
-        no_personal_notes=~Exists(PersonalNote.objects.filter(person__pk=OuterRef("pk"), **q_attrs))
-    ).filter(
-        member_of__in=Group.objects.filter(pk__in=self.get_groups().all()),
-        no_personal_notes=True,
-    )
-
-    # Create all missing personal notes
-    new_personal_notes = [
-        PersonalNote(
-            person=person,
-            **q_attrs,
-        )
-        for person in missing_persons
-    ]
-    PersonalNote.objects.bulk_create(new_personal_notes)
-
-    for personal_note in new_personal_notes:
-        personal_note.groups_of_person.set(personal_note.person.member_of.all())
-
-    return (
-        PersonalNote.objects.filter(**q_attrs, person__in=persons)
-        .select_related(None)
-        .prefetch_related(None)
-        .select_related("person", "excuse_type")
-        .prefetch_related("extra_marks")
-    )
-
-
-LessonPeriod.method(get_personal_notes)
-Event.method(get_personal_notes)
-ExtraLesson.method(get_personal_notes)
 
 # Dynamically add extra permissions to Group and Person models in core
 # Note: requires migrate afterwards
@@ -208,288 +26,3 @@ Group.add_permission(
 )
 Group.add_permission("assign_grouprole", _("Can assign a group role for this group"))
 Person.add_permission("register_absence_person", _("Can register an absence for a person"))
-
-
-@LessonPeriod.method
-def get_lesson_documentation(
-    self, week: Optional[CalendarWeek] = None
-) -> Union[LessonDocumentation, None]:
-    """Get lesson documentation object for this lesson."""
-    if not week:
-        week = self.week
-    # Use all to make effect of prefetched data
-    doc_filter = filter(
-        lambda d: d.week == week.week and d.year == week.year,
-        self.documentations.all(),
-    )
-    try:
-        return next(doc_filter)
-    except StopIteration:
-        return None
-
-
-def get_lesson_documentation_single(
-    self, week: Optional[CalendarWeek] = None
-) -> Union[LessonDocumentation, None]:
-    """Get lesson documentation object for this event/extra lesson."""
-    if self.documentations.exists():
-        return self.documentations.all()[0]
-    return None
-
-
-Event.method(get_lesson_documentation_single, "get_lesson_documentation")
-ExtraLesson.method(get_lesson_documentation_single, "get_lesson_documentation")
-
-
-@LessonPeriod.method
-def get_or_create_lesson_documentation(
-    self, week: Optional[CalendarWeek] = None
-) -> LessonDocumentation:
-    """Get or create lesson documentation object for this lesson."""
-    if not week:
-        week = self.week
-    lesson_documentation, __ = LessonDocumentation.objects.get_or_create(
-        lesson_period=self, week=week.week, year=week.year
-    )
-    return lesson_documentation
-
-
-def get_or_create_lesson_documentation_single(
-    self, week: Optional[CalendarWeek] = None
-) -> LessonDocumentation:
-    """Get or create lesson documentation object for this event/extra lesson."""
-    lesson_documentation, created = LessonDocumentation.objects.get_or_create(**{self.label_: self})
-    return lesson_documentation
-
-
-Event.method(get_or_create_lesson_documentation_single, "get_or_create_lesson_documentation")
-ExtraLesson.method(get_or_create_lesson_documentation_single, "get_or_create_lesson_documentation")
-
-
-@LessonPeriod.method
-def get_absences(self, week: Optional[CalendarWeek] = None) -> Iterator:
-    """Get all personal notes of absent persons for this lesson."""
-    if not week:
-        week = self.week
-
-    return filter(
-        lambda p: p.week == week.week and p.year == week.year and p.absent,
-        self.personal_notes.all(),
-    )
-
-
-def get_absences_simple(self, week: Optional[CalendarWeek] = None) -> Iterator:
-    """Get all personal notes of absent persons for this event/extra lesson."""
-    return filter(lambda p: p.absent, self.personal_notes.all())
-
-
-Event.method(get_absences_simple, "get_absences")
-ExtraLesson.method(get_absences_simple, "get_absences")
-
-
-@LessonPeriod.method
-def get_excused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of excused absent persons for this lesson."""
-    if not week:
-        week = self.week
-    return self.personal_notes.filter(week=week.week, year=week.year, absent=True, excused=True)
-
-
-def get_excused_absences_simple(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of excused absent persons for this event/extra lesson."""
-    return self.personal_notes.filter(absent=True, excused=True)
-
-
-Event.method(get_excused_absences_simple, "get_excused_absences")
-ExtraLesson.method(get_excused_absences_simple, "get_excused_absences")
-
-
-@LessonPeriod.method
-def get_unexcused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of unexcused absent persons for this lesson."""
-    if not week:
-        week = self.week
-    return self.personal_notes.filter(week=week.week, year=week.year, absent=True, excused=False)
-
-
-def get_unexcused_absences_simple(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of unexcused absent persons for this event/extra lesson."""
-    return self.personal_notes.filter(absent=True, excused=False)
-
-
-Event.method(get_unexcused_absences_simple, "get_unexcused_absences")
-ExtraLesson.method(get_unexcused_absences_simple, "get_unexcused_absences")
-
-
-@LessonPeriod.method
-def get_tardinesses(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of late persons for this lesson."""
-    if not week:
-        week = self.week
-    return self.personal_notes.filter(week=week.week, year=week.year, tardiness__gt=0)
-
-
-def get_tardinesses_simple(self, week: Optional[CalendarWeek] = None) -> QuerySet:
-    """Get all personal notes of late persons for this event/extra lesson."""
-    return self.personal_notes.filter(tardiness__gt=0)
-
-
-Event.method(get_tardinesses_simple, "get_tardinesses")
-ExtraLesson.method(get_tardinesses_simple, "get_tardinesses")
-
-
-@LessonPeriod.method
-def get_extra_marks(self, week: Optional[CalendarWeek] = None) -> Dict[ExtraMark, QuerySet]:
-    """Get all statistics on extra marks for this lesson."""
-    if not week:
-        week = self.week
-
-    stats = {}
-    for extra_mark in ExtraMark.objects.all():
-        qs = self.personal_notes.filter(week=week.week, year=week.year, extra_marks=extra_mark)
-        if qs:
-            stats[extra_mark] = qs
-
-    return stats
-
-
-def get_extra_marks_simple(self, week: Optional[CalendarWeek] = None) -> Dict[ExtraMark, QuerySet]:
-    """Get all statistics on extra marks for this event/extra lesson."""
-    stats = {}
-    for extra_mark in ExtraMark.objects.all():
-        qs = self.personal_notes.filter(extra_marks=extra_mark)
-        if qs:
-            stats[extra_mark] = qs
-
-    return stats
-
-
-Event.method(get_extra_marks_simple, "get_extra_marks")
-ExtraLesson.method(get_extra_marks_simple, "get_extra_marks")
-
-
-@Group.class_method
-def get_groups_with_lessons(cls: Group):
-    """Get all groups which have related lessons or child groups with related lessons."""
-    group_pks = (
-        cls.objects.for_current_school_term_or_all()
-        .annotate(lessons_count=Count("lessons"))
-        .filter(lessons_count__gt=0)
-        .values_list("pk", flat=True)
-    )
-    groups = cls.objects.filter(Q(child_groups__pk__in=group_pks) | Q(pk__in=group_pks)).distinct()
-
-    return groups
-
-
-@Person.method
-def get_owner_groups_with_lessons(self: Person):
-    """Get all groups the person is an owner of and which have related lessons.
-
-    Groups which have child groups with related lessons are also included, as well as all
-    child groups of the groups owned by the person with related lessons if the
-    inherit_privileges_from_parent_group preference is turned on.
-    """
-    if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"]:
-        return (
-            Group.get_groups_with_lessons()
-            .filter(Q(owners=self) | Q(parent_groups__owners=self))
-            .distinct()
-        )
-    return Group.get_groups_with_lessons().filter(owners=self).distinct()
-
-
-@Group.method
-def generate_person_list_with_class_register_statistics(
-    self: Group, persons: Optional[Iterable] = None
-) -> QuerySet:
-    """Get with class register statistics annotated list of all members."""
-    if persons is None:
-        persons = self.members.all()
-
-    lesson_periods = LessonPeriod.objects.filter(
-        lesson__validity__school_term=self.school_term
-    ).filter(Q(lesson__groups=self) | Q(lesson__groups__parent_groups=self))
-    extra_lessons = ExtraLesson.objects.filter(school_term=self.school_term).filter(
-        Q(groups=self) | Q(groups__parent_groups=self)
-    )
-    events = Event.objects.filter(school_term=self.school_term).filter(
-        Q(groups=self) | Q(groups__parent_groups=self)
-    )
-
-    persons = persons.select_related("primary_group", "primary_group__school_term").order_by(
-        "last_name", "first_name"
-    )
-    persons = persons.annotate(
-        filtered_personal_notes=FilteredRelation(
-            "personal_notes",
-            condition=(
-                Q(personal_notes__event__in=events)
-                | Q(personal_notes__lesson_period__in=lesson_periods)
-                | Q(personal_notes__extra_lesson__in=extra_lessons)
-            ),
-        )
-    ).annotate(
-        absences_count=Count(
-            "filtered_personal_notes",
-            filter=Q(filtered_personal_notes__absent=True)
-            & ~Q(filtered_personal_notes__excuse_type__count_as_absent=False),
-            distinct=True,
-        ),
-        excused=Count(
-            "filtered_personal_notes",
-            filter=Q(
-                filtered_personal_notes__absent=True,
-                filtered_personal_notes__excused=True,
-            )
-            & ~Q(filtered_personal_notes__excuse_type__count_as_absent=False),
-            distinct=True,
-        ),
-        excused_without_excuse_type=Count(
-            "filtered_personal_notes",
-            filter=Q(
-                filtered_personal_notes__absent=True,
-                filtered_personal_notes__excused=True,
-                filtered_personal_notes__excuse_type__isnull=True,
-            ),
-            distinct=True,
-        ),
-        unexcused=Count(
-            "filtered_personal_notes",
-            filter=Q(filtered_personal_notes__absent=True, filtered_personal_notes__excused=False),
-            distinct=True,
-        ),
-        tardiness=Sum("filtered_personal_notes__tardiness"),
-        tardiness_count=Count(
-            "filtered_personal_notes",
-            filter=Q(filtered_personal_notes__tardiness__gt=0),
-            distinct=True,
-        ),
-    )
-
-    for extra_mark in ExtraMark.objects.all():
-        persons = persons.annotate(
-            **{
-                extra_mark.count_label: Count(
-                    "filtered_personal_notes",
-                    filter=Q(filtered_personal_notes__extra_marks=extra_mark),
-                    distinct=True,
-                )
-            }
-        )
-
-    for excuse_type in ExcuseType.objects.all():
-        persons = persons.annotate(
-            **{
-                excuse_type.count_label: Count(
-                    "filtered_personal_notes",
-                    filter=Q(
-                        filtered_personal_notes__absent=True,
-                        filtered_personal_notes__excuse_type=excuse_type,
-                    ),
-                    distinct=True,
-                )
-            }
-        )
-
-    return persons
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 5303a40e57e3bf7ac6bbeba98da2f6e206be6007..fb9e57c40fa66ce75b93e11c451cb87a6b21f69c 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,12 +1,10 @@
-from datetime import date, datetime
-from typing import Optional, Union
-from urllib.parse import urlparse
+from datetime import datetime
+from typing import Optional
 
 from django.contrib.auth.models import User
 from django.core.exceptions import PermissionDenied
 from django.db import models
 from django.db.models import QuerySet
-from django.db.models.constraints import CheckConstraint
 from django.db.models.query_utils import Q
 from django.http import HttpRequest
 from django.urls import reverse
@@ -15,420 +13,27 @@ from django.utils.formats import date_format
 from django.utils.timezone import localdate, localtime, now
 from django.utils.translation import gettext_lazy as _
 
-from calendarweek import CalendarWeek
 from colorfield.fields import ColorField
 
-from aleksis.apps.alsijil.data_checks import (
-    ExcusesWithoutAbsences,
-    LessonDocumentationOnHolidaysDataCheck,
-    NoGroupsOfPersonsSetInPersonalNotesDataCheck,
-    NoPersonalNotesInCancelledLessonsDataCheck,
-    PersonalNoteOnHolidaysDataCheck,
-)
 from aleksis.apps.alsijil.managers import (
     DocumentationManager,
     GroupRoleAssignmentManager,
     GroupRoleAssignmentQuerySet,
     GroupRoleManager,
     GroupRoleQuerySet,
-    LessonDocumentationManager,
-    LessonDocumentationQuerySet,
     ParticipationStatusManager,
-    PersonalNoteManager,
-    PersonalNoteQuerySet,
 )
-from aleksis.apps.chronos.managers import GroupPropertiesMixin
-from aleksis.apps.chronos.mixins import WeekRelatedMixin
-from aleksis.apps.chronos.models import Event, ExtraLesson, LessonEvent, LessonPeriod, TimePeriod
-from aleksis.apps.chronos.util.format import format_m2m
+from aleksis.apps.chronos.models import LessonEvent
 from aleksis.apps.cursus.models import Course, Subject
 from aleksis.apps.kolego.models import Absence as KolegoAbsence
 from aleksis.apps.kolego.models import AbsenceReason
 from aleksis.core.data_checks import field_validation_data_check_factory
 from aleksis.core.mixins import ExtensibleModel, GlobalPermissionModel
-from aleksis.core.models import CalendarEvent, Group, Person, SchoolTerm
+from aleksis.core.models import CalendarEvent, Group, Person
 from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.model_helpers import ICONS
 
 
-def isidentifier(value: str) -> bool:
-    return value.isidentifier()
-
-
-class ExcuseType(ExtensibleModel):
-    """An type of excuse.
-
-    Can be used to count different types of absences separately.
-    """
-
-    short_name = models.CharField(max_length=255, unique=True, verbose_name=_("Short name"))
-    name = models.CharField(max_length=255, unique=True, verbose_name=_("Name"))
-
-    count_as_absent = models.BooleanField(
-        default=True,
-        verbose_name=_("Count as absent"),
-        help_text=_(
-            "If checked, this excuse type will be counted as a missed lesson. If not checked,"
-            "it won't show up in the absence report."
-        ),
-    )
-
-    def __str__(self):
-        return f"{self.name} ({self.short_name})"
-
-    @property
-    def count_label(self):
-        return f"excuse_type_{self.id}_count"
-
-    class Meta:
-        ordering = ["name"]
-        verbose_name = _("Excuse type")
-        verbose_name_plural = _("Excuse types")
-
-
-lesson_related_constraint_q = (
-    Q(
-        lesson_period__isnull=False,
-        event__isnull=True,
-        extra_lesson__isnull=True,
-        week__isnull=False,
-        year__isnull=False,
-    )
-    | Q(
-        lesson_period__isnull=True,
-        event__isnull=False,
-        extra_lesson__isnull=True,
-        week__isnull=True,
-        year__isnull=True,
-    )
-    | Q(
-        lesson_period__isnull=True,
-        event__isnull=True,
-        extra_lesson__isnull=False,
-        week__isnull=True,
-        year__isnull=True,
-    )
-)
-
-
-class RegisterObjectRelatedMixin(WeekRelatedMixin):
-    """Mixin with common API for lesson documentations and personal notes."""
-
-    @property
-    def register_object(
-        self: Union["LessonDocumentation", "PersonalNote"],
-    ) -> Union[LessonPeriod, Event, ExtraLesson]:
-        """Get the object related to this lesson documentation or personal note."""
-        if self.lesson_period:
-            return self.lesson_period
-        elif self.event:
-            return self.event
-        else:
-            return self.extra_lesson
-
-    @property
-    def register_object_key(self: Union["LessonDocumentation", "PersonalNote"]) -> str:
-        """Get a unique reference to the related object related."""
-        if self.week and self.year:
-            return f"{self.register_object.pk}_{self.week}_{self.year}"
-        else:
-            return self.register_object.pk
-
-    @property
-    def calendar_week(self: Union["LessonDocumentation", "PersonalNote"]) -> CalendarWeek:
-        """Get the calendar week of this lesson documentation or personal note.
-
-        .. note::
-
-            As events can be longer than one week,
-            this will return the week of the start date for events.
-        """
-        if self.lesson_period:
-            return CalendarWeek(week=self.week, year=self.year)
-        elif self.extra_lesson:
-            return self.extra_lesson.calendar_week
-        else:
-            return CalendarWeek.from_date(self.register_object.date_start)
-
-    @property
-    def school_term(self: Union["LessonDocumentation", "PersonalNote"]) -> SchoolTerm:
-        """Get the school term of the related register object."""
-        if self.lesson_period:
-            return self.lesson_period.lesson.validity.school_term
-        else:
-            return self.register_object.school_term
-
-    @property
-    def date(self: Union["LessonDocumentation", "PersonalNote"]) -> Optional[date]:
-        """Get the date of this lesson documentation or personal note.
-
-        :: warning::
-
-            As events can be longer than one day,
-            this will return `None` for events.
-        """
-        if self.lesson_period:
-            return super().date
-        elif self.extra_lesson:
-            return self.extra_lesson.date
-        return None
-
-    @property
-    def date_formatted(self: Union["LessonDocumentation", "PersonalNote"]) -> str:
-        """Get a formatted version of the date of this object.
-
-        Lesson periods, extra lessons: formatted date
-        Events: formatted date range
-        """
-        return (
-            date_format(self.date)
-            if self.date
-            else f"{date_format(self.event.date_start)}–{date_format(self.event.date_end)}"
-        )
-
-    @property
-    def period(self: Union["LessonDocumentation", "PersonalNote"]) -> TimePeriod:
-        """Get the date of this lesson documentation or personal note.
-
-        :: warning::
-
-            As events can be longer than one day,
-            this will return `None` for events.
-        """
-        if self.event:
-            return self.event.period_from
-        else:
-            return self.register_object.period
-
-    @property
-    def period_formatted(self: Union["LessonDocumentation", "PersonalNote"]) -> str:
-        """Get a formatted version of the period of this object.
-
-        Lesson periods, extra lessons: formatted period
-        Events: formatted period range
-        """
-        return (
-            f"{self.period.period}."
-            if not self.event
-            else f"{self.event.period_from.period}.–{self.event.period_to.period}."
-        )
-
-    def get_absolute_url(self: Union["LessonDocumentation", "PersonalNote"]) -> str:
-        """Get the absolute url of the detail view for the related register object."""
-        return self.register_object.get_alsijil_url(self.calendar_week)
-
-
-class PersonalNote(RegisterObjectRelatedMixin, ExtensibleModel):
-    """A personal note about a single person.
-
-    Used in the class register to note absences, excuses
-    and remarks about a student in a single lesson period.
-    """
-
-    data_checks = [
-        NoPersonalNotesInCancelledLessonsDataCheck,
-        NoGroupsOfPersonsSetInPersonalNotesDataCheck,
-        PersonalNoteOnHolidaysDataCheck,
-        ExcusesWithoutAbsences,
-    ]
-
-    objects = PersonalNoteManager.from_queryset(PersonalNoteQuerySet)()
-
-    person = models.ForeignKey("core.Person", models.CASCADE, related_name="personal_notes")
-    groups_of_person = models.ManyToManyField("core.Group", related_name="+")
-
-    week = models.IntegerField(blank=True, null=True)
-    year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True)
-
-    lesson_period = models.ForeignKey(
-        "chronos.LessonPeriod", models.CASCADE, related_name="personal_notes", blank=True, null=True
-    )
-    event = models.ForeignKey(
-        "chronos.Event", models.CASCADE, related_name="personal_notes", blank=True, null=True
-    )
-    extra_lesson = models.ForeignKey(
-        "chronos.ExtraLesson", models.CASCADE, related_name="personal_notes", blank=True, null=True
-    )
-
-    absent = models.BooleanField(default=False)
-    tardiness = models.PositiveSmallIntegerField(default=0)
-    excused = models.BooleanField(default=False)
-    excuse_type = models.ForeignKey(
-        ExcuseType,
-        on_delete=models.SET_NULL,
-        null=True,
-        blank=True,
-        verbose_name=_("Excuse type"),
-    )
-
-    remarks = models.CharField(max_length=200, blank=True)
-
-    extra_marks = models.ManyToManyField("ExtraMark", blank=True, verbose_name=_("Extra marks"))
-
-    def save(self, *args, **kwargs):
-        if self.excuse_type:
-            self.excused = True
-        if not self.absent:
-            self.excused = False
-            self.excuse_type = None
-        super().save(*args, **kwargs)
-
-    def reset_values(self):
-        """Reset all saved data to default values.
-
-        .. warning ::
-
-            This won't save the data, please execute ``save`` extra.
-        """
-        defaults = PersonalNote()
-
-        self.absent = defaults.absent
-        self.tardiness = defaults.tardiness
-        self.excused = defaults.excused
-        self.excuse_type = defaults.excuse_type
-        self.remarks = defaults.remarks
-        self.extra_marks.clear()
-
-    def __str__(self) -> str:
-        return f"{self.date_formatted}, {self.lesson_period}, {self.person}"
-
-    def get_absolute_url(self) -> str:
-        """Get the absolute url of the detail view for the related register object."""
-        return urlparse(super().get_absolute_url())._replace(fragment="personal-notes").geturl()
-
-    class Meta:
-        verbose_name = _("Personal note")
-        verbose_name_plural = _("Personal notes")
-        ordering = [
-            "year",
-            "week",
-            "lesson_period__period__weekday",
-            "lesson_period__period__period",
-            "person__last_name",
-            "person__first_name",
-        ]
-        constraints = [
-            CheckConstraint(
-                check=lesson_related_constraint_q, name="one_relation_only_personal_note"
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "lesson_period", "person"),
-                name="unique_note_per_lp",
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "event", "person"),
-                name="unique_note_per_ev",
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "extra_lesson", "person"),
-                name="unique_note_per_el",
-            ),
-        ]
-
-
-class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel):
-    """A documentation on a single lesson period.
-
-    Non-personal, includes the topic and homework of the lesson.
-    """
-
-    objects = LessonDocumentationManager.from_queryset(LessonDocumentationQuerySet)()
-
-    data_checks = [LessonDocumentationOnHolidaysDataCheck]
-
-    week = models.IntegerField(blank=True, null=True)
-    year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True)
-
-    lesson_period = models.ForeignKey(
-        "chronos.LessonPeriod", models.CASCADE, related_name="documentations", blank=True, null=True
-    )
-    event = models.ForeignKey(
-        "chronos.Event", models.CASCADE, related_name="documentations", blank=True, null=True
-    )
-    extra_lesson = models.ForeignKey(
-        "chronos.ExtraLesson", models.CASCADE, related_name="documentations", blank=True, null=True
-    )
-
-    topic = models.CharField(verbose_name=_("Lesson topic"), max_length=200, blank=True)
-    homework = models.CharField(verbose_name=_("Homework"), max_length=200, blank=True)
-    group_note = models.CharField(verbose_name=_("Group note"), max_length=200, blank=True)
-
-    def carry_over_data(self, all_periods_of_lesson: LessonPeriod):
-        """Carry over data to given periods in this lesson if data is not already set.
-
-        Both forms of carrying over data can be deactivated using site preferences
-        ``alsijil__carry_over_next_periods`` and ``alsijil__allow_carry_over_same_week``
-        respectively.
-        """
-        for period in all_periods_of_lesson:
-            lesson_documentation = period.get_or_create_lesson_documentation(
-                CalendarWeek(week=self.week, year=self.year)
-            )
-
-            changed = False
-
-            if not lesson_documentation.topic:
-                lesson_documentation.topic = self.topic
-                changed = True
-
-            if not lesson_documentation.homework:
-                lesson_documentation.homework = self.homework
-                changed = True
-
-            if not lesson_documentation.group_note:
-                lesson_documentation.group_note = self.group_note
-                changed = True
-
-            if changed:
-                lesson_documentation.save(carry_over_next_periods=False)
-
-    def __str__(self) -> str:
-        return f"{self.lesson_period}, {self.date_formatted}"
-
-    def save(self, carry_over_next_periods=True, *args, **kwargs):
-        if (
-            get_site_preferences()["alsijil__carry_over_next_periods"]
-            and (self.topic or self.homework or self.group_note)
-            and self.lesson_period
-            and carry_over_next_periods
-        ):
-            self.carry_over_data(
-                LessonPeriod.objects.filter(
-                    lesson=self.lesson_period.lesson,
-                    period__weekday=self.lesson_period.period.weekday,
-                )
-            )
-        super().save(*args, **kwargs)
-
-    class Meta:
-        verbose_name = _("Lesson documentation")
-        verbose_name_plural = _("Lesson documentations")
-        ordering = [
-            "year",
-            "week",
-            "lesson_period__period__weekday",
-            "lesson_period__period__period",
-        ]
-        constraints = [
-            CheckConstraint(
-                check=lesson_related_constraint_q,
-                name="one_relation_only_lesson_documentation",
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "lesson_period"),
-                name="unique_documentation_per_lp",
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "event"),
-                name="unique_documentation_per_ev",
-            ),
-            models.UniqueConstraint(
-                fields=("week", "year", "extra_lesson"),
-                name="unique_documentation_per_el",
-            ),
-        ]
-
-
 class ExtraMark(ExtensibleModel):
     """A model for extra marks.
 
@@ -517,7 +122,7 @@ class Documentation(CalendarEvent):
         start_datetime = CalendarEvent.value_start_datetime(self)
         end_datetime = CalendarEvent.value_end_datetime(self)
         return (
-            f"{format_m2m(self.get_groups())} {self.get_subject()}"
+            f"{','.join([str(g) for g in self.get_groups()])} {self.get_subject()}"
             + f" {start_datetime} - {end_datetime}"
         )
 
@@ -950,7 +555,7 @@ class GroupRole(ExtensibleModel):
         return reverse("edit_group_role", args=[self.id])
 
 
-class GroupRoleAssignment(GroupPropertiesMixin, ExtensibleModel):
+class GroupRoleAssignment(ExtensibleModel):
     objects = GroupRoleAssignmentManager.from_queryset(GroupRoleAssignmentQuerySet)()
 
     role = models.ForeignKey(
diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py
index 74ed22802286eaeccc4d92b6c1e255364c3503b2..b0d8fbc42c9227e32c227bf3ac10ef9c1635fba6 100644
--- a/aleksis/apps/alsijil/preferences.py
+++ b/aleksis/apps/alsijil/preferences.py
@@ -1,122 +1,19 @@
-from django.core.exceptions import ValidationError
 from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.preferences import Section
 from dynamic_preferences.types import (
     BooleanPreference,
     ChoicePreference,
-    IntegerPreference,
     ModelChoicePreference,
     ModelMultipleChoicePreference,
 )
 
 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
 
 alsijil = Section("alsijil", verbose_name=_("Class register"))
 
 
-@site_preferences_registry.register
-class BlockPersonalNotesForCancelled(BooleanPreference):
-    section = alsijil
-    name = "block_personal_notes_for_cancelled"
-    default = True
-    verbose_name = _("Block adding personal notes for cancelled lessons")
-
-
-@site_preferences_registry.register
-class ViewOwnPersonalNotes(BooleanPreference):
-    section = alsijil
-    name = "view_own_personal_notes"
-    default = True
-    verbose_name = _("Allow users to view their own personal notes")
-
-
-@site_preferences_registry.register
-class RegisterAbsenceAsPrimaryGroupOwner(BooleanPreference):
-    section = alsijil
-    name = "register_absence_as_primary_group_owner"
-    default = True
-    verbose_name = _(
-        "Allow primary group owners to register future absences for students in their groups"
-    )
-
-
-@site_preferences_registry.register
-class InheritPrivilegesFromParentGroup(BooleanPreference):
-    section = alsijil
-    name = "inherit_privileges_from_parent_group"
-    default = True
-    verbose_name = _(
-        "Grant the owner of a parent group the same privileges "
-        "as the owners of the respective child groups"
-    )
-
-
-@site_preferences_registry.register
-class EditLessonDocumentationAsOriginalTeacher(BooleanPreference):
-    section = alsijil
-    name = "edit_lesson_documentation_as_original_teacher"
-    default = True
-    verbose_name = _("Allow original teachers to edit their lessons although they are substituted")
-
-
-@site_preferences_registry.register
-class CarryOverDataToNextPeriods(BooleanPreference):
-    section = alsijil
-    name = "carry_over_next_periods"
-    default = True
-    verbose_name = _(
-        "Carry over data from first lesson period to the "
-        "following lesson periods in lessons over multiple periods"
-    )
-    help_text = _("This will carry over data only if the data in the following periods are empty.")
-
-
-@site_preferences_registry.register
-class AllowCarryOverLessonDocumentationToCurrentWeek(BooleanPreference):
-    section = alsijil
-    name = "allow_carry_over_same_week"
-    default = False
-    verbose_name = _(
-        "Allow carrying over data from any lesson period to all other lesson \
-                periods with the same lesson and in the same week"
-    )
-    help_text = _(
-        "This will carry over data only if the data in the aforementioned periods are empty."
-    )
-
-
-@site_preferences_registry.register
-class CarryOverPersonalNotesToNextPeriods(BooleanPreference):
-    section = alsijil
-    name = "carry_over_personal_notes"
-    default = True
-    verbose_name = _("Carry over personal notes to all following lesson periods on the same day.")
-
-
-@site_preferences_registry.register
-class AllowOpenPeriodsOnSameDay(BooleanPreference):
-    section = alsijil
-    name = "open_periods_same_day"
-    default = False
-    verbose_name = _(
-        "Allow teachers to open lesson periods on the "
-        "same day and not just at the beginning of the period"
-    )
-    help_text = _(
-        "Lessons in the past are not affected by this setting, you can open them whenever you want."
-    )
-
-
-@site_preferences_registry.register
-class AllowEntriesInHolidays(BooleanPreference):
-    section = alsijil
-    name = "allow_entries_in_holidays"
-    default = False
-    verbose_name = _("Allow teachers to add data for lessons in holidays")
-
-
 @site_preferences_registry.register
 class GroupOwnersCanAssignRolesToParents(BooleanPreference):
     section = alsijil
@@ -127,45 +24,6 @@ class GroupOwnersCanAssignRolesToParents(BooleanPreference):
     )
 
 
-@person_preferences_registry.register
-class ShowGroupRolesInWeekView(BooleanPreference):
-    section = alsijil
-    name = "group_roles_in_week_view"
-    default = True
-    verbose_name = _("Show assigned group roles in week view")
-    help_text = _("Only week view of groups")
-
-
-@person_preferences_registry.register
-class ShowGroupRolesInLessonView(BooleanPreference):
-    section = alsijil
-    name = "group_roles_in_lesson_view"
-    default = True
-    verbose_name = _("Show assigned group roles in lesson view")
-
-
-@person_preferences_registry.register
-class RegisterObjectsTableItemsPerPage(IntegerPreference):
-    """Preference how many items are shown per page in ``RegisterObjectTable``."""
-
-    section = alsijil
-    name = "register_objects_table_items_per_page"
-    default = 100
-    verbose_name = _("Items per page in lessons table")
-
-    def validate(self, value):
-        if value < 1:
-            raise ValidationError(_("Each page must show at least one item."))
-
-
-@person_preferences_registry.register
-class DefaultLessonDocumentationFilter(BooleanPreference):
-    section = alsijil
-    name = "default_lesson_documentation_filter"
-    default = True
-    verbose_name = _("Filter lessons by existence of their lesson documentation on default")
-
-
 @site_preferences_registry.register
 class AllowEditFutureDocumentations(ChoicePreference):
     """Time range for which documentations may be edited."""
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index 5fe872e73c65d370bf5bc70ce3671899144d5ee7..602663b519a0680ca88de3813ecbc38df64579a8 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -20,9 +20,7 @@ from .util.predicates import (
     can_view_documentation,
     can_view_participation_status,
     can_view_personal_note,
-    has_lesson_group_object_perm,
     has_person_group_object_perm,
-    has_personal_note_group_perm,
     is_course_group_owner,
     is_course_member,
     is_course_teacher,
@@ -33,143 +31,10 @@ from .util.predicates import (
     is_in_allowed_time_range_for_participation_status,
     is_lesson_event_group_owner,
     is_lesson_event_teacher,
-    is_lesson_original_teacher,
-    is_lesson_parent_group_owner,
-    is_lesson_participant,
-    is_lesson_teacher,
-    is_none,
-    is_own_personal_note,
     is_owner_of_any_group,
     is_parent_group_owner,
     is_person_group_owner,
-    is_person_primary_group_owner,
-    is_personal_note_lesson_original_teacher,
-    is_personal_note_lesson_parent_group_owner,
-    is_personal_note_lesson_teacher,
-    is_teacher,
-)
-
-# View lesson
-view_register_object_predicate = has_person & (
-    is_none  # View is opened as "Current lesson"
-    | is_lesson_teacher
-    | is_lesson_original_teacher
-    | is_lesson_participant
-    | is_lesson_parent_group_owner
-    | has_global_perm("alsijil.view_lesson")
-    | has_lesson_group_object_perm("core.view_week_class_register_group")
-)
-add_perm("alsijil.view_register_object_rule", view_register_object_predicate)
-
-# View lesson in menu
-add_perm("alsijil.view_lesson_menu_rule", has_person)
-
-# View lesson personal notes
-view_lesson_personal_notes_predicate = view_register_object_predicate & (
-    ~is_lesson_participant
-    | is_lesson_teacher
-    | is_lesson_original_teacher
-    | (
-        is_lesson_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.view_personalnote")
-    | has_lesson_group_object_perm("core.view_personalnote_group")
-)
-add_perm("alsijil.view_register_object_personalnote_rule", view_lesson_personal_notes_predicate)
-
-# Edit personal note
-edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & (
-    is_lesson_teacher
-    | (
-        is_lesson_original_teacher
-        & is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher")
-    )
-    | (
-        is_lesson_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.change_personalnote")
-    | has_lesson_group_object_perm("core.edit_personalnote_group")
 )
-add_perm("alsijil.edit_register_object_personalnote_rule", edit_lesson_personal_note_predicate)
-
-# View personal note
-view_personal_note_predicate = has_person & (
-    (is_own_personal_note & is_site_preference_set("alsijil", "view_own_personal_notes"))
-    | is_personal_note_lesson_teacher
-    | is_personal_note_lesson_original_teacher
-    | is_personal_note_lesson_parent_group_owner
-    | has_global_perm("alsijil.view_personalnote")
-    | has_personal_note_group_perm("core.view_personalnote_group")
-)
-add_perm("alsijil.view_personalnote_rule", view_personal_note_predicate)
-
-# Edit personal note
-edit_personal_note_predicate = view_personal_note_predicate & (
-    ~is_own_personal_note
-    & ~(
-        is_personal_note_lesson_original_teacher
-        | ~is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher")
-    )
-    | (
-        is_personal_note_lesson_parent_group_owner
-        | is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.view_personalnote")
-    | has_personal_note_group_perm("core.edit_personalnote_group")
-)
-add_perm("alsijil.edit_personalnote_rule", edit_personal_note_predicate)
-
-# View lesson documentation
-view_lesson_documentation_predicate = view_register_object_predicate
-add_perm("alsijil.view_lessondocumentation_rule", view_lesson_documentation_predicate)
-
-# Edit lesson documentation
-edit_lesson_documentation_predicate = view_register_object_predicate & (
-    is_lesson_teacher
-    | (
-        is_lesson_original_teacher
-        & is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher")
-    )
-    | (
-        is_lesson_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.change_lessondocumentation")
-    | has_lesson_group_object_perm("core.edit_lessondocumentation_group")
-)
-add_perm("alsijil.edit_lessondocumentation_rule", edit_lesson_documentation_predicate)
-
-# View week overview
-view_week_predicate = has_person & (
-    is_current_person
-    | is_group_member
-    | is_group_owner
-    | (
-        is_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.view_week")
-    | has_object_perm("core.view_week_class_register_group")
-)
-add_perm("alsijil.view_week_rule", view_week_predicate)
-
-# View week overview in menu
-add_perm("alsijil.view_week_menu_rule", has_person)
-
-# View week personal notes
-view_week_personal_notes_predicate = has_person & (
-    (is_current_person & is_teacher)
-    | is_group_owner
-    | (
-        is_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.view_personalnote")
-    | has_object_perm("core.view_personalnote_group")
-)
-add_perm("alsijil.view_week_personalnote_rule", view_week_personal_notes_predicate)
 
 # Register absence
 view_register_absence_predicate = has_person & (
@@ -197,86 +62,6 @@ view_full_register_predicate = has_person & (
 )
 add_perm("alsijil.view_full_register_rule", view_full_register_predicate)
 
-# View students list
-view_my_students_predicate = has_person & is_teacher
-add_perm("alsijil.view_my_students_rule", view_my_students_predicate)
-
-# View groups list
-view_my_groups_predicate = has_person & is_teacher
-add_perm("alsijil.view_my_groups_rule", view_my_groups_predicate)
-
-# View students list
-view_students_list_predicate = view_my_groups_predicate & (
-    is_group_owner
-    | (
-        is_parent_group_owner
-        & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group")
-    )
-    | has_global_perm("alsijil.view_personalnote")
-    | has_object_perm("core.view_personalnote_group")
-)
-add_perm("alsijil.view_students_list_rule", view_students_list_predicate)
-
-# View person overview
-view_person_overview_predicate = has_person & (
-    (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes"))
-    | is_person_group_owner
-)
-add_perm("alsijil.view_person_overview_rule", view_person_overview_predicate)
-
-# View person overview
-view_person_overview_menu_predicate = has_person
-add_perm("alsijil.view_person_overview_menu_rule", view_person_overview_menu_predicate)
-
-# View person overview personal notes
-view_person_overview_personal_notes_predicate = view_person_overview_predicate & (
-    (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes"))
-    | is_person_primary_group_owner
-    | has_global_perm("alsijil.view_personalnote")
-    | has_person_group_object_perm("core.view_personalnote_group")
-)
-add_perm(
-    "alsijil.view_person_overview_personalnote_rule",
-    view_person_overview_personal_notes_predicate,
-)
-
-# Edit person overview personal notes
-edit_person_overview_personal_notes_predicate = view_person_overview_predicate & (
-    ~is_current_person
-    | has_global_perm("alsijil.change_personalnote")
-    | has_person_group_object_perm("core.edit_personalnote_group")
-)
-add_perm(
-    "alsijil.edit_person_overview_personalnote_rule",
-    edit_person_overview_personal_notes_predicate,
-)
-
-# View person statistics on personal notes
-view_person_statistics_personal_notes_predicate = view_person_overview_personal_notes_predicate
-add_perm(
-    "alsijil.view_person_statistics_personalnote_rule",
-    view_person_statistics_personal_notes_predicate,
-)
-
-# View excuse type list
-view_excusetypes_predicate = has_person & has_global_perm("alsijil.view_excusetype")
-add_perm("alsijil.view_excusetypes_rule", view_excusetypes_predicate)
-
-# Add excuse type
-add_excusetype_predicate = view_excusetypes_predicate & has_global_perm("alsijil.add_excusetype")
-add_perm("alsijil.add_excusetype_rule", add_excusetype_predicate)
-
-# Edit excuse type
-edit_excusetype_predicate = view_excusetypes_predicate & has_global_perm(
-    "alsijil.change_excusetype"
-)
-add_perm("alsijil.edit_excusetype_rule", edit_excusetype_predicate)
-
-# Delete excuse type
-delete_excusetype_predicate = view_excusetypes_predicate & has_global_perm(
-    "alsijil.delete_excusetype"
-)
-add_perm("alsijil.delete_excusetype_rule", delete_excusetype_predicate)
 
 # View extra mark list
 view_extramarks_predicate = has_person & has_global_perm("alsijil.view_extramark")
@@ -327,17 +112,6 @@ view_assigned_group_roles_predicate = has_person & (
 )
 add_perm("alsijil.view_assigned_grouproles_rule", view_assigned_group_roles_predicate)
 
-view_assigned_group_roles_register_object_predicate = has_person & (
-    is_lesson_teacher
-    | is_lesson_original_teacher
-    | is_lesson_parent_group_owner
-    | has_global_perm("alsijil.assign_grouprole")
-)
-add_perm(
-    "alsijil.view_assigned_grouproles_for_register_object",
-    view_assigned_group_roles_register_object_predicate,
-)
-
 assign_group_role_person_predicate = has_person & (
     is_person_group_owner | has_global_perm("alsijil.assign_grouprole")
 )
diff --git a/aleksis/apps/alsijil/static/css/alsijil/alsijil.css b/aleksis/apps/alsijil/static/css/alsijil/alsijil.css
deleted file mode 100644
index a30fb99bb8b239ca38f7f4d627329594387741c6..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/static/css/alsijil/alsijil.css
+++ /dev/null
@@ -1,60 +0,0 @@
-table.datatable a {
-  color: inherit !important;
-}
-
-table a.tr-link {
-  display: block;
-  width: inherit;
-  height: inherit;
-}
-
-.collapsible-icon-right {
-  align-self: end;
-  flex-grow: 100;
-  text-align: right !important;
-}
-
-@media only screen and (min-width: 1201px) {
-  .hide-on-extra-large-only {
-    display: none;
-  }
-}
-
-@media only screen and (max-width: 1200px) {
-  .show-on-extra-large {
-    display: none;
-  }
-}
-
-@media only screen and (max-width: 600px) {
-  .collection .collection-item.avatar {
-    padding-left: 20px;
-  }
-  .collection .collection-item.avatar:not(.circle-clipper) > .circle {
-    position: relative;
-    margin-bottom: 10px;
-  }
-}
-
-.collapsible li .show-on-active {
-  display: none;
-}
-
-.collapsible li.active .show-on-active {
-  display: block;
-}
-
-th.chip-height {
-  height: 67px;
-  line-height: 2.2;
-}
-
-.collection-item.chip-height {
-  height: 52px;
-  line-height: 2.2;
-}
-
-li.collection-item.button-height {
-  height: 58px;
-  line-height: 2.5;
-}
diff --git a/aleksis/apps/alsijil/static/css/alsijil/lesson.css b/aleksis/apps/alsijil/static/css/alsijil/lesson.css
deleted file mode 100644
index 3ca0427a81bba0793b78d41d884a5810f0080f74..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/static/css/alsijil/lesson.css
+++ /dev/null
@@ -1,140 +0,0 @@
-.alsijil-check-box {
-  margin-right: 10px;
-}
-
-.alsijil-check-box [type="checkbox"] {
-  padding-left: 30px;
-}
-
-.alsijil-lesson-cancelled {
-  text-decoration: line-through;
-}
-
-.alsijil-tardiness-text {
-  vertical-align: super;
-}
-
-@media only screen and (max-width: 992px) {
-  .no-mobile-card {
-    border: unset;
-    padding: unset;
-    margin: unset;
-    box-shadow: unset;
-  }
-  .no-mobile-card .card-content {
-    padding: unset;
-  }
-  table.alsijil-table.horizontal-on-small {
-    display: block;
-    max-width: calc(100vw - 40px);
-  }
-  table.alsijil-table.horizontal-on-small thead {
-    display: none;
-  }
-  table.alsijil-table.horizontal-on-small tbody {
-    overflow-x: scroll;
-    display: flex;
-    column-gap: 1rem;
-    flex-wrap: nowrap;
-    align-items: stretch;
-    scroll-snap-type: x proximity;
-  }
-
-  table.alsijil-table.horizontal-on-small tr {
-    flex-basis: min(75vw, 400px);
-    flex-shrink: 0;
-    flex-grow: 1;
-    border-radius: 8px;
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    scroll-snap-align: center;
-    transition: all 0.5s;
-    margin: 0.5rem 0 1rem 0;
-    background-color: #fff !important;
-    box-shadow:
-      0 2px 2px 0 rgba(0, 0, 0, 0.14),
-      0 3px 1px -2px rgba(0, 0, 0, 0.12),
-      0 1px 5px 0 rgba(0, 0, 0, 0.2);
-    padding: 24px;
-  }
-  table.alsijil-table.horizontal-on-small tr:first-of-type {
-    margin-inline-start: 0.4rem;
-    -moz-margin-start: 0.4rem;
-    -webkit-margin-start: 0.4rem;
-  }
-
-  table.alsijil-table.horizontal-on-small tr:last-of-type {
-    margin-inline-end: 0.4rem;
-    -moz-margin-end: 0.4rem;
-    -webkit-margin-end: 0.4rem;
-  }
-  table.alsijil-table.horizontal-on-small td.center-align {
-    text-align: left;
-  }
-  table.alsijil-table.horizontal-on-small .person-name {
-    font-size: 24px;
-    font-weight: 300;
-    display: block;
-    line-height: 32px;
-    margin-bottom: 8px;
-  }
-}
-
-.alsijil-time-head,
-.alsijil-object-head {
-  display: block;
-}
-
-.alsijil-time-head {
-  font-size: 2rem;
-  line-height: 1.1;
-}
-
-.alsijil-object-head {
-  font-size: 3rem;
-}
-
-@media only screen and (max-width: 600px) {
-  .alsijil-time-head {
-    font-size: 1.5rem;
-  }
-
-  .alsijil-object-head {
-    font-size: 2.2rem;
-    line-height: 1.4;
-  }
-}
-
-.alsijil-nav {
-  line-height: 36px;
-}
-
-.alsijil-header-nav-button {
-  height: 66px;
-  padding: 0;
-}
-
-.alsijil-header-nav-button.left {
-  margin-right: 5px;
-}
-
-.alsijil-header-nav-button.right {
-  margin-left: 5px;
-}
-
-.alsijil-header-nav-button i.material-icons {
-  line-height: 60px;
-  height: 60px;
-  font-size: 40px;
-}
-
-.alsijil-nav-header {
-  width: calc(100% + 40px);
-  padding: 10px 20px;
-  margin: -10px -20px 0;
-}
-
-.tabs-icons .tab svg.iconify {
-  display: block;
-}
diff --git a/aleksis/apps/alsijil/static/css/alsijil/person.css b/aleksis/apps/alsijil/static/css/alsijil/person.css
deleted file mode 100644
index d385d7b69e031fafd4c21d037b70b8f973e99b74..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/static/css/alsijil/person.css
+++ /dev/null
@@ -1,100 +0,0 @@
-span.input-field.inline > .select-wrapper > input {
-  color: red;
-  padding: 14px 0 0 0;
-  line-height: 2px;
-  height: 36px;
-  vertical-align: middle;
-}
-
-span.input-field.inline > .select-wrapper .caret {
-  top: 12px !important;
-}
-
-@media screen and (min-width: 1400px) {
-  li.collection-item form {
-    margin: -30px 0 -30px 0;
-  }
-
-  li.collection-item#title #select_all_span {
-    margin-top: 5px;
-  }
-}
-
-.collection {
-  overflow: visible;
-  overflow-x: hidden;
-}
-
-#select_all_container {
-  display: none;
-}
-
-#select_all_box:indeterminate + span:not(.lever):before {
-  top: -4px;
-  left: -6px;
-  width: 10px;
-  height: 12px;
-  border-top: none;
-  border-left: none;
-  border-right: white 2px solid;
-  border-bottom: none;
-  transform: rotate(90deg);
-  backface-visibility: hidden;
-  transform-origin: 100% 100%;
-}
-
-#select_all_box:indeterminate + span:not(.lever):after {
-  top: 0;
-  width: 20px;
-  height: 20px;
-  border: 2px solid currentColor;
-  background-color: currentColor;
-  z-index: 0;
-}
-
-#select_all_box_text {
-  color: #9e9e9e !important;
-}
-
-td.material-icons {
-  display: table-cell;
-}
-
-.medium-high {
-  position: relative;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, 50%);
-}
-
-@media screen and (min-width: 600px) {
-  /* On medium and up devices */
-  .medium-high-right {
-    float: right;
-    transform: translate(0%, 50%);
-  }
-}
-
-@media screen and (max-width: 600px) {
-  /* Only on small devices */
-  .full-width-s {
-    width: 100%;
-  }
-
-  #heading {
-    display: block;
-  }
-  #heading + a {
-    float: none !important;
-  }
-}
-
-.overflow-x-scroll {
-  overflow-x: scroll;
-}
-
-figure.modal-content figcaption {
-  font-weight: 300;
-  font-size: 2.28rem;
-  line-height: 110%;
-}
diff --git a/aleksis/apps/alsijil/static/css/alsijil/week_view.css b/aleksis/apps/alsijil/static/css/alsijil/week_view.css
deleted file mode 100644
index a42111f55e31d3b3f518873218c76fd246106afe..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/static/css/alsijil/week_view.css
+++ /dev/null
@@ -1,122 +0,0 @@
-@media screen and (max-width: 600px) {
-  #toggle-row button[type="submit"] {
-    width: 100%;
-    margin-bottom: 1em;
-  }
-}
-
-.horizontal-scroll-container {
-  overflow-x: scroll;
-  display: flex;
-  column-gap: 1rem;
-  flex-wrap: nowrap;
-  align-items: stretch;
-  scroll-snap-type: x proximity;
-}
-
-.horizontal-scroll-container.vertical {
-  flex-wrap: wrap;
-  overflow-x: inherit;
-}
-
-.horizontal-scroll-container.vertical .horizontal-scroll-card {
-  margin-inline: 0;
-}
-
-dl {
-  margin: 0;
-  padding: 0;
-}
-
-dt {
-  font-weight: bold;
-}
-
-dd {
-  margin: 0;
-  padding: unset;
-}
-
-.horizontal-scroll-card {
-  flex-basis: min(75vw, 400px);
-  flex-shrink: 0;
-  flex-grow: 1;
-  border-radius: 8px;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  scroll-snap-align: center;
-  transition: all 0.5s;
-}
-
-.horizontal-scroll-card:first-of-type {
-  margin-inline-start: 0.4rem;
-  -moz-margin-start: 0.4rem;
-  -webkit-margin-start: 0.4rem;
-}
-
-.horizontal-scroll-card:last-of-type {
-  margin-inline-end: 0.4rem;
-  -moz-margin-end: 0.4rem;
-  -webkit-margin-end: 0.4rem;
-}
-
-.horizontal-scroll-card .card-action {
-  margin-bottom: 5px;
-}
-
-.horizontal-scroll-card .card-content .card-title {
-  display: flex;
-  justify-content: space-between;
-}
-
-.horizontal-scroll-card .card-content .card-title .subject {
-  flex-grow: 5;
-}
-
-.horizontal-scroll-card .one-line {
-  display: grid;
-  grid-auto-flow: column;
-  grid-template-rows: 1fr 1fr;
-}
-
-p.subtitle {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-end;
-}
-
-.btn-superflat ~ span {
-  line-height: 24px;
-}
-
-.btn-superflat,
-.btn-superflat:focus,
-.btn-superflat:active {
-  border: none;
-  line-height: 1;
-  height: 24px;
-  background: none;
-  font-weight: normal;
-}
-
-.btn-superflat i.material-icons {
-  vertical-align: middle;
-}
-
-.btn-superflat:hover {
-  cursor: pointer;
-}
-
-.unfold-trigger i.material-icons {
-  transition: transform 0.5s 0s ease-in-out;
-  transform: rotate(-90deg);
-}
-
-.unfold-trigger.vertical i.material-icons {
-  transform: rotate(-180deg);
-}
-
-.tabs-icons .tab svg.iconify {
-  display: block;
-}
diff --git a/aleksis/apps/alsijil/static/js/alsijil/week_view.js b/aleksis/apps/alsijil/static/js/alsijil/week_view.js
deleted file mode 100644
index 69124b9c41e656948bbced89b06b8d4edaf3b3c2..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/static/js/alsijil/week_view.js
+++ /dev/null
@@ -1,20 +0,0 @@
-$(document).ready(function () {
-  $("#id_group").change(function () {
-    $("#id_teacher").val("").formSelect();
-  });
-  $("#id_teacher").change(function () {
-    $("#id_group").val("").formSelect();
-  });
-  $("#toggle-row.pre-hidden").hide();
-});
-$("#toggle-button").click(function () {
-  $("#toggle-row").toggle();
-});
-$(".unfold-trigger").click(function (event) {
-  let target = event.target;
-  target.classList.toggle("vertical");
-  let next_container = $(target).parent().next(".horizontal-scroll-container");
-  if (next_container.length >= 1) {
-    next_container[0].classList.toggle("vertical");
-  }
-});
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index f17a214930f2d7f2e40a1662b491eab587bda0ad..acc3bcbed6f8da596bec2f40dbcdf238f5046a95 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -1,45 +1,9 @@
 from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 
 import django_tables2 as tables
 from django_tables2.utils import A
 
-from aleksis.apps.chronos.models import Event, LessonPeriod
-from aleksis.core.util.tables import SelectColumn
-
-from .models import PersonalNote
-
-
-class ExcuseTypeTable(tables.Table):
-    class Meta:
-        attrs = {"class": "highlight"}
-
-    name = tables.LinkColumn("edit_excuse_type", args=[A("id")])
-    short_name = tables.Column()
-    count_as_absent = tables.BooleanColumn(
-        verbose_name=_("Count as absent"),
-        accessor="count_as_absent",
-    )
-    edit = tables.LinkColumn(
-        "edit_excuse_type",
-        args=[A("id")],
-        text=_("Edit"),
-        attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}},
-    )
-    delete = tables.LinkColumn(
-        "delete_excuse_type",
-        args=[A("id")],
-        text=_("Delete"),
-        attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
-    )
-
-    def before_render(self, request):
-        if not request.user.has_perm("alsijil.edit_excusetype_rule"):
-            self.columns.hide("edit")
-        if not request.user.has_perm("alsijil.delete_excusetype_rule"):
-            self.columns.hide("delete")
-
 
 class GroupRoleTable(tables.Table):
     class Meta:
@@ -68,137 +32,3 @@ class GroupRoleTable(tables.Table):
             self.columns.hide("edit")
         if not request.user.has_perm("alsijil.delete_grouprole_rule"):
             self.columns.hide("delete")
-
-
-class PersonalNoteTable(tables.Table):
-    selected = SelectColumn(attrs={"input": {"name": "selected_objects"}}, accessor=A("pk"))
-    date = tables.Column(
-        verbose_name=_("Date"), accessor=A("date_formatted"), order_by=A("day_start"), linkify=True
-    )
-    period = tables.Column(
-        verbose_name=_("Period"),
-        accessor=A("period_formatted"),
-        order_by=A("order_period"),
-        linkify=True,
-    )
-    groups = tables.Column(
-        verbose_name=_("Groups"),
-        accessor=A("register_object__group_names"),
-        order_by=A("order_groups"),
-        linkify=True,
-    )
-    teachers = tables.Column(
-        verbose_name=_("Teachers"),
-        accessor=A("register_object__teacher_names"),
-        order_by=A("order_teachers"),
-        linkify=True,
-    )
-    subject = tables.Column(verbose_name=_("Subject"), accessor=A("subject"), linkify=True)
-    absent = tables.Column(verbose_name=_("Absent"))
-    tardiness = tables.Column(verbose_name=_("Tardiness"))
-    excused = tables.Column(verbose_name=_("Excuse"))
-    extra_marks = tables.Column(verbose_name=_("Extra marks"), accessor=A("extra_marks__all"))
-
-    def render_groups(self, value, record):
-        if isinstance(record.register_object, LessonPeriod):
-            return record.register_object.lesson.group_names
-        else:
-            return value
-
-    def render_subject(self, value, record):
-        if isinstance(record.register_object, Event):
-            return _("Event")
-        else:
-            return value
-
-    def render_absent(self, value):
-        return (
-            render_to_string(
-                "components/materialize-chips.html",
-                dict(content=_("Absent"), classes="red white-text"),
-            )
-            if value
-            else "–"
-        )
-
-    def render_excused(self, value, record):
-        if record.absent and value:
-            context = dict(content=_("Excused"), classes="green white-text")
-            badge = render_to_string("components/materialize-chips.html", context)
-            if record.excuse_type:
-                context = dict(content=record.excuse_type.name, classes="green white-text")
-                badge = render_to_string("components/materialize-chips.html", context)
-            return badge
-        return "–"
-
-    def render_tardiness(self, value):
-        if value:
-            content = _(f"{value}' tardiness")
-            context = dict(content=content, classes="orange white-text")
-            return render_to_string("components/materialize-chips.html", context)
-        else:
-            return "–"
-
-    def render_extra_marks(self, value):
-        if value:
-            badges = ""
-            for extra_mark in value:
-                content = extra_mark.name
-                badges += render_to_string(
-                    "components/materialize-chips.html", context=dict(content=content)
-                )
-            return mark_safe(badges)  # noqa
-        else:
-            return "–"
-
-    class Meta:
-        model = PersonalNote
-        fields = ()
-
-
-def _get_link(value, record):
-    return record["register_object"].get_alsijil_url(record.get("week"))
-
-
-class RegisterObjectTable(tables.Table):
-    """Table to show all register objects in an overview.
-
-    .. warning::
-        Works only with ``generate_list_of_all_register_objects``.
-    """
-
-    class Meta:
-        attrs = {"class": "highlight responsive-table"}
-
-    status = tables.Column(accessor="register_object")
-    date = tables.Column(order_by="date_sort", linkify=_get_link)
-    period = tables.Column(order_by="period_sort", linkify=_get_link)
-    groups = tables.Column(linkify=_get_link)
-    teachers = tables.Column(linkify=_get_link)
-    subject = tables.Column(linkify=_get_link)
-    topic = tables.Column(linkify=_get_link)
-    homework = tables.Column(linkify=_get_link)
-    group_note = tables.Column(linkify=_get_link)
-
-    def render_status(self, value, record):
-        context = {
-            "has_documentation": record.get("has_documentation", False),
-            "register_object": value,
-        }
-        if record.get("week"):
-            context["week"] = record["week"]
-        if record.get("substitution"):
-            context["substitution"] = record["substitution"]
-        return render_to_string("alsijil/partials/lesson_status.html", context)
-
-
-class RegisterObjectSelectTable(RegisterObjectTable):
-    """Table to show all register objects with multi-select support.
-
-    More information at ``RegisterObjectTable``
-    """
-
-    selected = SelectColumn()
-
-    class Meta(RegisterObjectTable.Meta):
-        sequence = ("selected", "...")
diff --git a/aleksis/apps/alsijil/tasks.py b/aleksis/apps/alsijil/tasks.py
index 7eaf8c01a5186ef08c12a5e4b19e9fc1e853d4d2..01e5ff016db1eb53d7fc78b0a82ed33f02774f04 100644
--- a/aleksis/apps/alsijil/tasks.py
+++ b/aleksis/apps/alsijil/tasks.py
@@ -1,187 +1,186 @@
-from copy import deepcopy
-from datetime import date, timedelta
-
-from django.db.models import Q
-from django.utils.translation import gettext as _
-
-from calendarweek import CalendarWeek
-from celery.result import allow_join_result
-from celery.states import SUCCESS
-
-from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
-from aleksis.core.models import Group, PDFFile
-from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task
-from aleksis.core.util.pdf import generate_pdf_from_template
-
-from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
-
-
-@recorded_task
-def generate_full_register_printout(group: int, file_object: int, recorder: ProgressRecorder):
-    """Generate a full register printout as PDF for a group."""
-    context = {}
-
-    _number_of_steps = 8
-
-    recorder.set_progress(1, _number_of_steps, _("Load data ..."))
-
-    group = Group.objects.get(pk=group)
-    file_object = PDFFile.objects.get(pk=file_object)
-
-    groups_q = (
-        Q(lesson_period__lesson__groups=group)
-        | Q(lesson_period__lesson__groups__parent_groups=group)
-        | Q(extra_lesson__groups=group)
-        | Q(extra_lesson__groups__parent_groups=group)
-        | Q(event__groups=group)
-        | Q(event__groups__parent_groups=group)
-    )
-    personal_notes = (
-        PersonalNote.objects.prefetch_related(
-            "lesson_period__substitutions", "lesson_period__lesson__teachers"
-        )
-        .not_empty()
-        .filter(groups_q)
-        .filter(groups_of_person=group)
-    )
-    documentations = LessonDocumentation.objects.not_empty().filter(groups_q)
-
-    recorder.set_progress(2, _number_of_steps, _("Sort data ..."))
-
-    sorted_documentations = {"extra_lesson": {}, "event": {}, "lesson_period": {}}
-    sorted_personal_notes = {"extra_lesson": {}, "event": {}, "lesson_period": {}, "person": {}}
-    for documentation in documentations:
-        key = documentation.register_object.label_
-        sorted_documentations[key][documentation.register_object_key] = documentation
-
-    for note in personal_notes:
-        key = note.register_object.label_
-        sorted_personal_notes[key].setdefault(note.register_object_key, [])
-        sorted_personal_notes[key][note.register_object_key].append(note)
-        sorted_personal_notes["person"].setdefault(note.person.pk, [])
-        sorted_personal_notes["person"][note.person.pk].append(note)
-
-    recorder.set_progress(3, _number_of_steps, _("Load lesson data ..."))
-
-    # Get all lesson periods for the selected group
-    lesson_periods = LessonPeriod.objects.filter_group(group).distinct()
-    events = Event.objects.filter_group(group).distinct()
-    extra_lessons = ExtraLesson.objects.filter_group(group).distinct()
-    weeks = CalendarWeek.weeks_within(group.school_term.date_start, group.school_term.date_end)
-
-    register_objects_by_day = {}
-    for extra_lesson in extra_lessons:
-        day = extra_lesson.date
-        register_objects_by_day.setdefault(day, []).append(
-            (
-                extra_lesson,
-                sorted_documentations["extra_lesson"].get(extra_lesson.pk),
-                sorted_personal_notes["extra_lesson"].get(extra_lesson.pk, []),
-                None,
-            )
-        )
-
-    for event in events:
-        day_number = (event.date_end - event.date_start).days + 1
-        for i in range(day_number):
-            day = event.date_start + timedelta(days=i)
-            event_copy = deepcopy(event)
-            event_copy.annotate_day(day)
-
-            # Skip event days if it isn't inside the timetable schema
-            if not (event_copy.raw_period_from_on_day and event_copy.raw_period_to_on_day):
-                continue
-
-            register_objects_by_day.setdefault(day, []).append(
-                (
-                    event_copy,
-                    sorted_documentations["event"].get(event.pk),
-                    sorted_personal_notes["event"].get(event.pk, []),
-                    None,
-                )
-            )
-
-    recorder.set_progress(4, _number_of_steps, _("Sort lesson data ..."))
-
-    weeks = CalendarWeek.weeks_within(
-        group.school_term.date_start,
-        group.school_term.date_end,
-    )
-
-    for lesson_period in lesson_periods:
-        for week in weeks:
-            day = week[lesson_period.period.weekday]
-
-            if (
-                lesson_period.lesson.validity.date_start
-                <= day
-                <= lesson_period.lesson.validity.date_end
-            ):
-                filtered_documentation = sorted_documentations["lesson_period"].get(
-                    f"{lesson_period.pk}_{week.week}_{week.year}"
-                )
-                filtered_personal_notes = sorted_personal_notes["lesson_period"].get(
-                    f"{lesson_period.pk}_{week.week}_{week.year}", []
-                )
-
-                substitution = lesson_period.get_substitution(week)
-
-                register_objects_by_day.setdefault(day, []).append(
-                    (lesson_period, filtered_documentation, filtered_personal_notes, substitution)
-                )
-
-    recorder.set_progress(5, _number_of_steps, _("Load statistics ..."))
-
-    persons = group.members.prefetch_related(None).select_related(None)
-    persons = group.generate_person_list_with_class_register_statistics(persons)
-
-    prefetched_persons = []
-    for person in persons:
-        person.filtered_notes = sorted_personal_notes["person"].get(person.pk, [])
-        prefetched_persons.append(person)
-
-    context["school_term"] = group.school_term
-    context["persons"] = prefetched_persons
-    context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
-    context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
-    context["extra_marks"] = ExtraMark.objects.all()
-    context["group"] = group
-    context["weeks"] = weeks
-    context["register_objects_by_day"] = register_objects_by_day
-    context["register_objects"] = list(lesson_periods) + list(events) + list(extra_lessons)
-    context["today"] = date.today()
-    context["lessons"] = (
-        group.lessons.all()
-        .select_related(None)
-        .prefetch_related(None)
-        .select_related("validity", "subject")
-        .prefetch_related("teachers", "lesson_periods")
-    )
-    context["child_groups"] = (
-        group.child_groups.all()
-        .select_related(None)
-        .prefetch_related(None)
-        .prefetch_related(
-            "lessons",
-            "lessons__validity",
-            "lessons__subject",
-            "lessons__teachers",
-            "lessons__lesson_periods",
-        )
-    )
-
-    recorder.set_progress(6, _number_of_steps, _("Generate template ..."))
-
-    file_object, result = generate_pdf_from_template(
-        "alsijil/print/full_register.html", context, file_object=file_object
-    )
-
-    recorder.set_progress(7, _number_of_steps, _("Generate PDF ..."))
-
-    with allow_join_result():
-        result.wait()
-        file_object.refresh_from_db()
-        if not result.status == SUCCESS and file_object.file:
-            raise Exception(_("PDF generation failed"))
-
-    recorder.set_progress(8, _number_of_steps)
+# from copy import deepcopy
+# from datetime import date, timedelta
+
+# from django.db.models import Q
+# from django.utils.translation import gettext as _
+
+# from calendarweek import CalendarWeek
+# from celery.result import allow_join_result
+# from celery.states import SUCCESS
+
+# from aleksis.core.models import Group, PDFFile
+# from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task
+# from aleksis.core.util.pdf import generate_pdf_from_template
+
+# from .models import ExtraMark
+
+
+# @recorded_task
+# def generate_full_register_printout(group: int, file_object: int, recorder: ProgressRecorder):
+#     """Generate a full register printout as PDF for a group."""
+#     context = {}
+
+#     _number_of_steps = 8
+
+#     recorder.set_progress(1, _number_of_steps, _("Load data ..."))
+
+#     group = Group.objects.get(pk=group)
+#     file_object = PDFFile.objects.get(pk=file_object)
+
+#     groups_q = (
+#         Q(lesson_period__lesson__groups=group)
+#         | Q(lesson_period__lesson__groups__parent_groups=group)
+#         | Q(extra_lesson__groups=group)
+#         | Q(extra_lesson__groups__parent_groups=group)
+#         | Q(event__groups=group)
+#         | Q(event__groups__parent_groups=group)
+#     )
+#     personal_notes = (
+#         PersonalNote.objects.prefetch_related(
+#             "lesson_period__substitutions", "lesson_period__lesson__teachers"
+#         )
+#         .not_empty()
+#         .filter(groups_q)
+#         .filter(groups_of_person=group)
+#     )
+#     documentations = LessonDocumentation.objects.not_empty().filter(groups_q)
+
+#     recorder.set_progress(2, _number_of_steps, _("Sort data ..."))
+
+#     sorted_documentations = {"extra_lesson": {}, "event": {}, "lesson_period": {}}
+#     sorted_personal_notes = {"extra_lesson": {}, "event": {}, "lesson_period": {}, "person": {}}
+#     for documentation in documentations:
+#         key = documentation.register_object.label_
+#         sorted_documentations[key][documentation.register_object_key] = documentation
+
+#     for note in personal_notes:
+#         key = note.register_object.label_
+#         sorted_personal_notes[key].setdefault(note.register_object_key, [])
+#         sorted_personal_notes[key][note.register_object_key].append(note)
+#         sorted_personal_notes["person"].setdefault(note.person.pk, [])
+#         sorted_personal_notes["person"][note.person.pk].append(note)
+
+#     recorder.set_progress(3, _number_of_steps, _("Load lesson data ..."))
+
+#     # Get all lesson periods for the selected group
+#     lesson_periods = LessonPeriod.objects.filter_group(group).distinct()
+#     events = Event.objects.filter_group(group).distinct()
+#     extra_lessons = ExtraLesson.objects.filter_group(group).distinct()
+#     weeks = CalendarWeek.weeks_within(group.school_term.date_start, group.school_term.date_end)
+
+#     register_objects_by_day = {}
+#     for extra_lesson in extra_lessons:
+#         day = extra_lesson.date
+#         register_objects_by_day.setdefault(day, []).append(
+#             (
+#                 extra_lesson,
+#                 sorted_documentations["extra_lesson"].get(extra_lesson.pk),
+#                 sorted_personal_notes["extra_lesson"].get(extra_lesson.pk, []),
+#                 None,
+#             )
+#         )
+
+#     for event in events:
+#         day_number = (event.date_end - event.date_start).days + 1
+#         for i in range(day_number):
+#             day = event.date_start + timedelta(days=i)
+#             event_copy = deepcopy(event)
+#             event_copy.annotate_day(day)
+
+#             # Skip event days if it isn't inside the timetable schema
+#             if not (event_copy.raw_period_from_on_day and event_copy.raw_period_to_on_day):
+#                 continue
+
+#             register_objects_by_day.setdefault(day, []).append(
+#                 (
+#                     event_copy,
+#                     sorted_documentations["event"].get(event.pk),
+#                     sorted_personal_notes["event"].get(event.pk, []),
+#                     None,
+#                 )
+#             )
+
+#     recorder.set_progress(4, _number_of_steps, _("Sort lesson data ..."))
+
+#     weeks = CalendarWeek.weeks_within(
+#         group.school_term.date_start,
+#         group.school_term.date_end,
+#     )
+
+#     for lesson_period in lesson_periods:
+#         for week in weeks:
+#             day = week[lesson_period.period.weekday]
+
+#             if (
+#                 lesson_period.lesson.validity.date_start
+#                 <= day
+#                 <= lesson_period.lesson.validity.date_end
+#             ):
+#                 filtered_documentation = sorted_documentations["lesson_period"].get(
+#                     f"{lesson_period.pk}_{week.week}_{week.year}"
+#                 )
+#                 filtered_personal_notes = sorted_personal_notes["lesson_period"].get(
+#                     f"{lesson_period.pk}_{week.week}_{week.year}", []
+#                 )
+
+#                 substitution = lesson_period.get_substitution(week)
+
+#                 register_objects_by_day.setdefault(day, []).append(
+#                     (lesson_period, filtered_documentation, filtered_personal_notes, substitution)
+#                 )
+
+#     recorder.set_progress(5, _number_of_steps, _("Load statistics ..."))
+
+#     persons = group.members.prefetch_related(None).select_related(None)
+#     persons = group.generate_person_list_with_class_register_statistics(persons)
+
+#     prefetched_persons = []
+#     for person in persons:
+#         person.filtered_notes = sorted_personal_notes["person"].get(person.pk, [])
+#         prefetched_persons.append(person)
+
+#     context["school_term"] = group.school_term
+#     context["persons"] = prefetched_persons
+#     context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
+#     context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
+#     context["extra_marks"] = ExtraMark.objects.all()
+#     context["group"] = group
+#     context["weeks"] = weeks
+#     context["register_objects_by_day"] = register_objects_by_day
+#     context["register_objects"] = list(lesson_periods) + list(events) + list(extra_lessons)
+#     context["today"] = date.today()
+#     context["lessons"] = (
+#         group.lessons.all()
+#         .select_related(None)
+#         .prefetch_related(None)
+#         .select_related("validity", "subject")
+#         .prefetch_related("teachers", "lesson_periods")
+#     )
+#     context["child_groups"] = (
+#         group.child_groups.all()
+#         .select_related(None)
+#         .prefetch_related(None)
+#         .prefetch_related(
+#             "lessons",
+#             "lessons__validity",
+#             "lessons__subject",
+#             "lessons__teachers",
+#             "lessons__lesson_periods",
+#         )
+#     )
+
+#     recorder.set_progress(6, _number_of_steps, _("Generate template ..."))
+
+#     file_object, result = generate_pdf_from_template(
+#         "alsijil/print/full_register.html", context, file_object=file_object
+#     )
+
+#     recorder.set_progress(7, _number_of_steps, _("Generate PDF ..."))
+
+#     with allow_join_result():
+#         result.wait()
+#         file_object.refresh_from_db()
+#         if not result.status == SUCCESS and file_object.file:
+#             raise Exception(_("PDF generation failed"))
+
+#     recorder.set_progress(8, _number_of_steps)
diff --git a/aleksis/apps/alsijil/templates/alsijil/absences/register.html b/aleksis/apps/alsijil/templates/alsijil/absences/register.html
deleted file mode 100644
index 590ad1e1f61d5ed09210101012aadaae9dda6f25..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/absences/register.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load material_form i18n static any_js %}
-
-{% block browser_title %}{% blocktrans %}Register absence{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Register absence{% endblocktrans %}{% endblock %}
-
-{% block extra_head %}
-  {{ form.media.css }}
-  {% include_css "select2-materialize" %}
-{% endblock %}
-
-{% block content %}
-  <form method="post">
-    {% csrf_token %}
-    {% form form=register_absence_form %}{% endform %}
-    {% include "core/partials/save_button.html" %}
-  </form>
-
-  <script>
-    $(document).ready(function () {
-      $("#id_date_start").change(function () {
-        $("#id_date_end").val($("#id_date_start").val());
-        initDatePicker("#id_date_end");
-      });
-    });
-  </script>
-
-  {% include_js "select2-materialize" %}
-  {{ form.media.js }}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/absences/register_confirm.html b/aleksis/apps/alsijil/templates/alsijil/absences/register_confirm.html
deleted file mode 100644
index 2c427ef2fa701a4e1beb36b40fc5c1de09ca8e44..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/absences/register_confirm.html
+++ /dev/null
@@ -1,82 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load material_form i18n static %}
-
-{% block browser_title %}{% blocktrans %}Confirm: Register absence{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Confirm: Register absence{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  <p class="flow-text">
-    {% blocktrans %}
-      Do you really want to register the following absence?
-    {% endblocktrans %}
-  </p>
-  <div class="card">
-    <div class="card-content">
-      <div class="card-title">
-        {{ person }}
-      </div>
-      <div class="collection">
-        <div class="collection-item">
-          <i class="material-icons iconify left" data-icon="mdi:calendar-range"></i>
-          {{ form_data.date_start }}, {{ form_data.from_period }}. – {{ form_data.date_end }}, {{ form_data.to_period }}.
-          {% if form_data.date_start != form_data.date_end %}
-            <figure class="alert warning">
-              <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
-              {% blocktrans %}
-                As the length of this absence is longer than one day,
-                please double check the correctness of your entry.
-              {% endblocktrans %}
-            </figure>
-          {% endif %}
-        </div>
-        <div class="collection-item">
-          <i class="material-icons iconify left" data-icon="mdi:format-list-bulleted"></i>
-          {% blocktrans with count=affected_lessons %} {{ count }} affected lessons {% endblocktrans %}
-          {% if affected_lessons == 0 %}
-            <div class="alert error">
-              <div>
-                <i class="material-icons iconify left" data-icon="mdi:alert-octagon-outline"></i>
-                {% blocktrans %}
-                  There are no affected lessons. Registering this absence won't have any effect.
-                {% endblocktrans %}
-              </div>
-            </div>
-          {% endif %}
-        </div>
-        <div class="collection-item">
-          <i class="material-icons iconify left" data-icon="mdi:label-outline"></i>
-          {% if form_data.absent %}
-            <span class="chip red white-text">{% trans "Absent" %}</span>
-            {% if form_data.excused and form_data.excuse_type %}
-              <span class="chip green white-text">{{ form_data.excuse_type.name }}</span>
-            {% elif form_data.excused %}
-              <span class="chip green white-text">{% trans "Excused" %}</span>
-            {% endif %}
-          {% else %}
-            {% trans "Reset status to 'not absent'" %}
-          {% endif %}
-        </div>
-        {% if form_data.remarks %}
-          <div class="collection-item">
-            <i class="material-icons iconify left" data-icon="mdi:pencil-outline"></i>
-            {{ form_data.remarks }}
-          </div>
-        {% endif %}
-      </div>
-    </div>
-  </div>
-
-  <form method="post">
-    {% csrf_token %}
-    <div class="hide">
-      {% form form=form %}{% endform %}
-    </div>
-    <input type="hidden" name="confirmed" value="1">
-    {% include "core/partials/save_button.html" %}
-    <a class="btn red waves-effect waves-light" href="{% url "register_absence" person.pk %}">
-      <i class="material-icons iconify left" data-icon="mdi:close"></i>
-      {% trans "Cancel" %}
-    </a>
-  </form>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html
deleted file mode 100644
index b5e57e0ee276ab395ebd824327c7584a063dba63..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load i18n rules static django_tables2 material_form %}
-
-{% block browser_title %}{% blocktrans %}All lessons{% endblocktrans %}{% endblock %}
-
-{% block page_title %}
-  {% blocktrans %}All lessons{% endblocktrans %}
-{% endblock %}
-
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/>
-{% endblock %}
-
-{% block content %}
-  {% include "alsijil/partials/objects_table.html" %}
-  <script src="{% static "js/multi_select.js" %}"></script>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html b/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html
deleted file mode 100644
index 43a6eeeb9349969b58c61111e6017b7d24e74f60..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html
+++ /dev/null
@@ -1,109 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load i18n static rules %}
-
-{% block browser_title %}{% blocktrans %}My groups{% endblocktrans %}{% endblock %}
-
-{% block page_title %}
-  {% blocktrans %}My groups{% endblocktrans %}
-{% endblock %}
-
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/>
-{% endblock %}
-
-{% block content %}
-  <table class="highlight responsive-table hide-on-med-and-down">
-    <thead>
-    <tr>
-      <th>{% trans "Name" %}</th>
-      <th>{% trans "Students" %}</th>
-      <th></th>
-    </tr>
-    </thead>
-    {% for group in groups %}
-      <tr>
-        <td>
-          {{ group }}
-        </td>
-        <td>{{ group.students_count }}</td>
-        <td>
-          <div class="right">
-            <a class="btn primary-color waves-effect waves-light" href="{% url "students_list" group.pk %}">
-              <i class="material-icons iconify left" data-icon="mdi:account-multiple-outline"></i>
-              {% trans "Students list" %}
-            </a>
-            <a class="btn secondary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}">
-              <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-              {% trans "Week view" %}
-            </a>
-            {% has_perm "alsijil.view_assigned_grouproles_rule" user group as can_view_assigned_group_roles %}
-            {% if can_view_assigned_group_roles %}
-              <a class="btn primary waves-effect waves-light" href="{% url 'assigned_group_roles' group.pk %}">
-                <i class="material-icons iconify left" data-icon="mdi:clipboard-account-outline"></i>
-                {% trans "Roles" %}
-              </a>
-            {% endif %}
-            <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}"
-               target="_blank">
-              <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-              {% trans "Generate printout" %}
-            </a>
-          </div>
-        </td>
-      </tr>
-    {% empty %}
-      <tr>
-        <td class="flow-text" colspan="3">
-          {% blocktrans %}No groups available.{% endblocktrans %}
-        </td>
-      </tr>
-    {% endfor %}
-  </table>
-
-  <div class="hide-on-large-only">
-    <ul class="collection">
-      {% for group in groups %}
-        <li class="collection-item">
-          <span class="title">{{ group }}</span>
-          <p>
-            {{ group.students_count }} {% trans "students" %}
-          </p>
-          <p>
-            <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}">
-              <i class="material-icons iconify left" data-icon="mdi:account-multiple-outline"></i>
-              {% trans "Students list" %}
-            </a>
-          </p>
-          <p>
-            <a class="btn secondary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}">
-              <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-              {% trans "Week view" %}
-            </a>
-          </p>
-          {% has_perm "alsijil.view_assigned_grouproles_rule" user group as can_view_assigned_group_roles %}
-          {% if can_view_assigned_group_roles %}
-            <p>
-              <a class="btn primary waves-effect waves-light" href="{% url 'assigned_group_roles' group.pk %}">
-                <i class="material-icons iconify left" data-icon="mdi:clipboard-account-outline"></i>
-                {% trans "Roles" %}
-              </a>
-            </p>
-          {% endif %}
-          <p>
-            <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}"
-               target="_blank">
-              <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-              {% trans "Generate printout" %}
-            </a>
-          </p>
-        </li>
-      {% empty %}
-          <li class="collection-item flow-text">
-            {% blocktrans %}No groups available.{% endblocktrans %}
-          </li>
-      {% endfor %}
-    </ul>
-  </div>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
deleted file mode 100644
index e4e3960c8a1325acdca09ddcff95d958657d0f62..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ /dev/null
@@ -1,125 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load week_helpers material_form_internal material_form i18n static rules time_helpers %}
-
-{% block browser_title %}{% blocktrans %}Lesson{% endblocktrans %}{% endblock %}
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/lesson.css' %}"/>
-
-  {% if with_seating_plan %}
-    <link rel="stylesheet" href="{% static "css/stoelindeling/seating_plan.css" %}">
-  {% endif %}
-{% endblock %}
-
-{% block page_title %}
-  {% include "alsijil/partials/lesson/heading.html" %}
-{% endblock %}
-
-{% block content %}
-  {% has_perm "alsijil.view_lessondocumentation_rule" user register_object as can_view_lesson_documentation %}
-  {% has_perm "alsijil.edit_lessondocumentation_rule" user register_object as can_edit_lesson_documentation %}
-  {% has_perm "alsijil.edit_register_object_personalnote_rule" user register_object as can_edit_register_object_personalnote %}
-
-  <!-- Tab Buttons -->
-  <div class="col s12 margin-bottom">
-    <ul class="tabs tabs-icons tabs-fixed-width">
-      <li class="tab col">
-        <a href="#lesson-documentation">
-          <i class="material-icons iconify" data-icon="mdi:message-bulleted"></i>
-          {% trans "Period" %}
-        </a>
-      </li>
-      {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not SITE_PREFERENCES.alsijil__block_personal_notes_for_cancelled %}
-        <li class="tab col">
-          <a href="#personal-notes">
-            <i class="material-icons iconify" data-icon="mdi:account-multiple-outline"></i>
-            {% trans "Persons" %}
-          </a>
-        </li>
-      {% endif %}
-      {% if with_seating_plan %}
-        <li class="tab col">
-          <a href="#seating-plan">
-            <i class="material-icons iconify" data-icon="mdi:seat-outline"></i>
-            {% trans "Seating plan" %}
-          </a>
-        </li>
-      {% endif %}
-      {% if prev_lesson %}
-        {% has_perm "alsijil.view_lessondocumentation_rule" user prev_lesson as can_view_prev_lesson_documentation %}
-        {% if prev_lesson.get_lesson_documentation and can_view_prev_lesson_documentation %}
-          <li class="tab col">
-            <a href="#previous-lesson">
-              <i class="material-icons iconify" data-icon="mdi:history"></i>
-              {% trans "Previous" %}
-            </a>
-          </li>
-        {% endif %}
-      {% endif %}
-      <li class="tab col">
-        <a href="#more">
-          <i class="material-icons iconify" data-icon="mdi:dots-horizontal"></i>
-          {% trans "More" %}
-        </a>
-      </li>
-    </ul>
-  </div>
-
-  <form method="post" class="row">
-    {% csrf_token %}
-
-    {% if not blocked_because_holidays %}
-      <div class="row">
-        <div class="col s12 no-padding" id="lesson-documentation">
-          {% include "alsijil/partials/lesson/tabs/documentation.html" %}
-        </div>
-
-        {% with prev_doc=prev_lesson.get_lesson_documentation %}
-          {% with absences=prev_lesson.get_absences tardinesses=prev_lesson.get_tardinesses extra_marks=prev_lesson.get_extra_marks %}
-            {% has_perm "alsijil.view_lessondocumentation_rule" user prev_lesson as can_view_prev_lesson_documentation %}
-            {% if prev_doc and can_view_prev_lesson_documentation %}
-              <div class="col s12 no-padding" id="previous-lesson">
-                {% include "alsijil/partials/lesson/tabs/previous_lesson.html" %}
-              </div>
-            {% endif %}
-          {% endwith %}
-        {% endwith %}
-
-        {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not SITE_PREFERENCES.alsijil__block_personal_notes_for_cancelled %}
-          <div class="col s12 no-padding" id="personal-notes">
-            {% include "alsijil/partials/lesson/tabs/notes.html" %}
-          </div>
-        {% endif %}
-
-        {% if with_seating_plan %}
-          <div class="col s12 no-padding" id="seating-plan">
-            {% include "alsijil/partials/lesson/tabs/seating_plan.html" %}
-          </div>
-        {% endif %}
-
-        <div class="col s12 no-padding" id="more">
-          {% include "alsijil/partials/lesson/tabs/more.html" %}
-        </div>
-      </div>
-    {% else %}
-      <div class="row no-margin">
-        <div class="container">
-          <div class="card">
-            <div class="card-content center-align">
-              <p>
-                <i class="material-icons iconify medium orange-text" data-icon="mdi:alert-outline"></i>
-              </p>
-              <p class="card-title">
-                {% blocktrans %}
-                  This lesson overlaps with holidays and can't be edited.
-                {% endblocktrans %}
-              </p>
-              <span class="badge new blue no-float no-margin">{{ holiday }}</span>
-            </div>
-          </div>
-        </div>
-      </div>
-    {% endif %}
-  </form>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
deleted file mode 100644
index 78792a793cce1b93577d7bf6ca55da771a43a7df..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
+++ /dev/null
@@ -1,185 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load rules data_helpers week_helpers i18n material_form static django_tables2 %}
-
-{% block extra_head %}
-  <link rel="stylesheet" href="{% static "css/alsijil/person.css" %}">
-  <script src="{% static "js/multi_select.js" %}" type="text/javascript"></script>
-{% endblock %}
-
-{% block browser_title %}{% blocktrans %}Class register: person{% endblocktrans %}{% endblock %}
-
-
-{% block page_title %}
-  {% has_perm "alsijil.view_my_students_rule" user as has_students %}
-  {% if has_students %}
-    <a href="{% url "my_students" %}"
-       class="btn-flat primary-color-text waves-light waves-effect">
-      <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i> {% trans "Back" %}
-    </a>
-  {% endif %}
-  <span id="heading">
-    {% blocktrans with person=person %}
-      Class register overview for {{ person }}
-    {% endblocktrans %}
-  </span>
-  {% has_perm "alsijil.register_absence_rule" user person as can_register_absence %}
-  {% if can_register_absence %}
-    <a class="btn primary-color waves-effect waves-light right" href="{% url "register_absence" person.pk %}">
-      <i class="material-icons iconify left" data-icon="mdi:message-draw"></i>
-      {% trans "Register absence" %}
-    </a>
-  {% endif %}
-{% endblock %}
-
-{% block content %}
-  <div class="row">
-
-  <!-- Tab Buttons -->
-  <div class="col s12">
-    <ul class="tabs">
-      {% if register_object_table %}
-        <li class="tab">
-          <a href="#lesson-documentations">{% trans "Lesson documentations" %}</a>
-        </li>
-      {% endif %}
-      <li class="tab">
-        <a href="#personal-notes">{% trans "Personal notes" %}</a>
-      </li>
-      {% if stats %}
-        <li class="tab"><a href="#statistics">{% trans "Statistics" %}</a></li>
-      {% endif %}
-    </ul>
-  </div>
-
-  <!-- Lesson Documentation Tab -->
-  {% if register_object_table %}
-    <div class="col s12" id="lesson-documentations">
-      {% include "alsijil/partials/objects_table.html" with table=register_object_table filter_form=filter_form %}
-    </div>
-  {% endif %}
-
-  <!-- Personal Note Tab -->
-  <div class="col s12" id="personal-notes">
-    <div class="col s12" id="overview">
-      <h2>{% trans "Relevant personal notes" %}</h2>
-      <form class="modal" id="filter-modal">
-        <figure class="modal-content">
-          <figcaption>{% trans "Filter personal notes" %}</figcaption>
-          {% form form=personal_note_filter_form %}{% endform %}
-        </figure>
-        <div class="modal-footer">
-          <button type="button" class="btn-flat secondary-color-text waves-effect waves-ripple" id="remove-filters">
-            <i class="material-icons iconify left" data-icon="mdi:close"></i>{% trans "Clear all filters" %}
-          </button>
-          <button type="button" class="modal-close btn-flat red-text waves-effect waves-ripple waves-red">
-            <i class="material-icons iconify left" data-icon="mdi:close-circle-outline"></i>{% trans "Close" %}
-          </button>
-          <button type="submit" class="modal-close btn-flat primary-color-text waves-effect waves-ripple waves-light">
-            <i class="material-icons iconify left" data-icon="mdi:filter-outline"></i>{% trans "Filter" %}
-          </button>
-        </div>
-      </form>
-      {% has_perm "alsijil.edit_person_overview_personalnote_rule" user person as can_mark_all_as_excused %}
-      <div class="row">
-        <div class="col s12 m3 l5 push-m9 push-l7">
-          <button
-              class="modal-trigger btn primary-color waves-effect waves-light
-              {% if can_mark_all_as_excused %} medium-high-right {% endif %}"
-              data-target="filter-modal"
-              type="button">
-            {% trans "Filter results" %} ({{ num_filters }})
-            <i class="material-icons iconify right" data-icon="mdi:filter-outline"></i>
-          </button>
-        </div>
-        <form action="" method="post" class="">
-          {% csrf_token %}
-          <div class="col s12 m9 l7 pull-m3 pull-l5 row">
-            {% if can_mark_all_as_excused %}
-              <div class="col s12 m9">
-                {% form form=action_form %}{% endform %}
-              </div>
-              <div class="col s12 m3">
-                <button type="submit" class="btn waves-effect waves-light medium-high full-width-s">
-                  Run <i class="material-icons iconify right" data-icon="mdi:send-outline"></i>
-                </button>
-              </div>
-            {% endif %}
-          </div>
-          <div class="col s12 overflow-x-scroll">
-            {% render_table personal_notes_table %}
-          </div>
-        </form>
-      </div>
-    </div>
-  </div>
-
-  <!-- Statistics Tab -->
-  {% if stats %}
-    <div class="col s12" id="statistics">
-      <h2>{% trans "Statistics on absences, tardiness and remarks" %}</h2>
-      <ul class="collapsible">
-        {% for school_term, stat in stats %}
-          <li {% if forloop.first %}class="active"{% endif %}>
-            <div class="collapsible-header">
-              <i class="material-icons iconify" data-icon="mdi:calendar-range"></i>{{ school_term }}</div>
-            <div class="collapsible-body">
-              <table>
-                <tr>
-                  <th colspan="3">{% trans 'Absences' %}</th>
-                  <td>{{ stat.absences_count }}</td>
-                </tr>
-                <tr>
-                  <td rowspan="{{ excuse_types.count|add:3 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
-                  <td rowspan="{{ excuse_types.count|add:3 }}" class="hide-on-med-and-up"></td>
-                  <th colspan="2">{% trans 'Excused' %}</th>
-                  <td>{{ stat.excused }}</td>
-                </tr>
-                <tr>
-                  <td rowspan="{{ excuse_types.count|add:1 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
-                  <td rowspan="{{ excuse_types.count|add:1 }}" class="hide-on-med-and-up"></td>
-                  <th colspan="2" class="truncate">{% trans 'Without Excuse Type' %}</th>
-                  <td>{{ stat.excused_no_excuse_type }}</td>
-                </tr>
-                {% for excuse_type in excuse_types %}
-                  <tr>
-                    <th>{{ excuse_type.name }}</th>
-                    <td>{{ stat|get_dict:excuse_type.count_label }}</td>
-                  </tr>
-                {% endfor %}
-                <tr>
-                  <th colspan="2">{% trans 'Unexcused' %}</th>
-                  <td>{{ stat.unexcused }}</td>
-                </tr>
-                {% for excuse_type in excuse_types_not_absent %}
-                  <tr>
-                    <th colspan="3">{{ excuse_type.name }}</th>
-                    <td>{{ stat|get_dict:excuse_type.count_label }}</td>
-                  </tr>
-                  {% endfor %}
-                <tr>
-                  <th colspan="3">{% trans 'Tardiness' %}</th>
-                  <td>{{ stat.tardiness }}'/{{ stat.tardiness_count }} &times;</td>
-                </tr>
-                {% for extra_mark in extra_marks %}
-                  <tr>
-                    <th colspan="3">{{ extra_mark.name }}</th>
-                    <td>{{ stat|get_dict:extra_mark.count_label }}</td>
-                  </tr>
-                {% endfor %}
-              </table>
-            </div>
-          </li>
-        {% endfor %}
-      </ul>
-    </div>
-  {% endif %}
-  <script type="text/javascript">
-    $("#remove-filters").click(function () {
-      $("#filter-modal").trigger("reset");
-      $("#filter-modal input, #filter-modal select").each(function () {
-        $(this).val("");
-      })
-    })
-  </script>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html b/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html
deleted file mode 100644
index 6873ddc84a1cdbad62ff43b09d1d50ece163a01a..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html
+++ /dev/null
@@ -1,64 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load i18n week_helpers data_helpers static time_helpers %}
-
-{% block browser_title %}{% blocktrans %}My students{% endblocktrans %}{% endblock %}
-
-
-{% block page_title %}
-  {% blocktrans %}My students{% endblocktrans %}
-{% endblock %}
-
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/>
-{% endblock %}
-
-
-{% block content %}
-  <ul class="collapsible">
-    {% for group, persons in groups %}
-      <li {% if forloop.first %}class="active"{% endif %}>
-        <div class="collapsible-header">
-          <div class="hundred-percent">
-            <span class="right show-on-active hide-on-small-and-down">
-              <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}">
-                <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-                {% trans "Week view" %}
-              </a>
-              <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank">
-                <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-                {% trans "Generate printout" %}
-              </a>
-            </span>
-
-            <h2>{{ group.name }}
-              <span class="chip">{{ group.school_term }}</span>
-            </h2>
-
-            <p class="show-on-active hide-on-med-and-up">
-              <a class="btn primary-color waves-effect waves-light hundred-percent"
-                 href="{% url "week_view" "group" group.pk %}">
-                <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-                {% trans "Week view" %}
-              </a>
-            </p>
-            <p class="show-on-active hide-on-med-and-up">
-              <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}"
-                 target="_blank">
-                <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-                {% trans "Generate printout" %}
-              </a>
-            </p>
-          </div>
-        </div>
-
-        <div class="collapsible-body">
-          {% include "alsijil/partials/persons_with_stats.html" with persons=persons %}
-        </div>
-      </li>
-    {% endfor %}
-  </ul>
-
-  {% include "alsijil/partials/legend.html" %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html b/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html
deleted file mode 100644
index 72bb8071f1b05f7e911bf75776a1740a9a5d5ae9..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html
+++ /dev/null
@@ -1,49 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load static time_helpers data_helpers week_helpers i18n %}
-
-{% block browser_title %}{% blocktrans with group=group %}Students list: {{ group }}{% endblocktrans %}{% endblock %}
-
-{% block page_title %}
-  <a href="{% url "my_groups" %}"
-     class="btn-flat primary-color-text waves-light waves-effect">
-    <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i> {% trans "Back" %}
-  </a>
-  {% blocktrans with group=group %}Students list: {{ group }}{% endblocktrans %}
-  <span class="right show-on-active hide-on-small-and-down">
-    <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}">
-      <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-      {% trans "Week view" %}
-    </a>
-    <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank">
-      <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-      {% trans "Generate printout" %}
-    </a>
-  </span>
-{% endblock %}
-
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/>
-{% endblock %}
-
-{% block content %}
-  <p class="show-on-active hide-on-med-and-up">
-    <a class="btn primary-color waves-effect waves-light hundred-percent"
-       href="{% url "week_view" "group" group.pk %}">
-       <i class="material-icons iconify left" data-icon="mdi:view-week-outline"></i>
-      {% trans "Week view" %}
-    </a>
-  </p>
-  <p class="show-on-active hide-on-med-and-up">
-    <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}"
-       target="_blank">
-      <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-      {% trans "Generate printout" %}
-    </a>
-  </p>
-
-  {% include "alsijil/partials/persons_with_stats.html" with persons=persons %}
-
-  {% include "alsijil/partials/legend.html" %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
deleted file mode 100644
index 081f38c5321359ca92241c21ba1f9b1fc71aa926..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ /dev/null
@@ -1,443 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load material_form i18n week_helpers static data_helpers rules time_helpers %}
-
-{% block browser_title %}{% blocktrans %}Week view{% endblocktrans %}{% endblock %}
-
-{% block extra_head %}
-  {{ block.super }}
-  <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/>
-  <link rel="stylesheet" href="{% static 'css/alsijil/week_view.css' %}"/>
-{% endblock %}
-
-{% block content %}
-  <script type="text/javascript" src="{% static "js/helper.js" %}"></script>
-  {{ week_select|json_script:"week_select" }}
-  <script type="text/javascript" src="{% static "js/chronos/week_select.js" %}"></script>
-  <div class="row">
-    <div id="toggle-row" class="col s12 m8 l10 {% if lesson_periods %}pre-hidden{% endif %}">
-      <form method="post" action="">
-        {% csrf_token %}
-        {% form form=select_form %}{% endform %}
-        <button type="submit" class="btn waves-effect waves-light primary-color">
-          <i class="material-icons iconify left" data-icon="mdi:check"></i>
-          {% blocktrans %}Select{% endblocktrans %}
-        </button>
-      </form>
-    </div>
-    <div class="col s12 m4 l2 right">
-      <button type="button" class="btn waves-effect waves-light hundred-percent" id="toggle-button">
-        <i class="material-icons iconify left" data-icon="mdi:filter-outline"></i> {% trans "Toggle filters" %}
-      </button>
-    </div>
-  </div>
-
-
-  <div class="row no-margin">
-    <h4 class="col s12 m6">{% blocktrans with el=el week=week.week %}CW {{ week }}:
-      {{ instance }}{% endblocktrans %} </h4>
-    {% include "chronos/partials/week_select.html" with wanted_week=week %}
-  </div>
-
-  {% if group %}
-    <p class="hide-on-med-and-down">
-      <a class="btn primary-color waves-effect waves-light" href="{% url "students_list" group.pk %}">
-        <i class="material-icons iconify left" data-icon="mdi:account-multiple-outline"></i>
-        {% trans "Students list" %}
-      </a>
-      <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank">
-        <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-        {% trans "Generate printout" %}
-      </a>
-    </p>
-
-    <p class="hide-on-med-and-up">
-      <a class="btn primary-color waves-effect waves-light hundred-percent"
-         href="{% url "students_list" group.pk %}">
-        <i class="material-icons iconify left" data-icon="mdi:account-multiple-outline"></i>
-        {% trans "Students list" %}
-      </a>
-    </p>
-    <p class="hide-on-med-and-up">
-      <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}"
-         target="_blank">
-        <i class="material-icons iconify left" data-icon="mdi:printer-outline"></i>
-        {% trans "Generate printout" %}
-      </a>
-    </p>
-  {% endif %}
-
-  {% if lesson_periods %}
-  <div class="col s12 margin-bottom">
-    <ul class="tabs tabs-icons tabs-fixed-width">
-      <li class="tab col">
-        <a class="active" href="#week-overview">
-          <i class="material-icons iconify" data-icon="mdi:message-bulleted"></i>
-          {% trans "Lesson documentations" %}
-        </a>
-      </li>
-      <li class="tab col">
-        <a href="#personal-notes">
-          <i class="material-icons iconify" data-icon="mdi:account-multiple-outline"></i>
-          {% trans "Persons" %}
-        </a>
-      </li>
-      {% if group_roles %}
-        <li class="tab col">
-          <a href="#group-roles">
-            <i class="material-icons iconify" data-icon="mdi:clipboard-account-outline"></i>
-            {% trans "Group roles" %}
-          </a>
-        </li>
-      {% endif %}
-    </ul>
-  </div>
-  {% endif %}
-
-  {% if lesson_periods %}
-    <div class="row">
-      <div class="col s12" id="week-overview">
-        {% for weekday, objects in regrouped_objects.items %}
-          {% with weekdays|get_dict:objects.0.weekday as advanced_weekday %}
-            {% if advanced_weekday.holiday and not SITE_PREFERENCES.alsijil__allow_entries_in_holidays %}
-              <div class="card">
-                <div class="card-content">
-                    <span class="card-title">
-                      {{ advanced_weekday.name }}, {{ advanced_weekday.date }} <span
-                      class="badge new blue no-float">{{ advanced_weekday.holiday }}</span>
-                    </span>
-                </div>
-              </div>
-            {% else %}
-              <div class="card show-on-extra-large">
-                <div class="card-content">
-                    <span class="card-title">
-                      {{ advanced_weekday.name }}, {{ advanced_weekday.date }}
-                    </span>
-                  <table class="striped datatable">
-                    <thead>
-                    <tr>
-                      <th></th>
-                      <th>{% blocktrans %}Period{% endblocktrans %}</th>
-                      {% if not group %}
-                        <th>{% blocktrans %}Groups{% endblocktrans %}</th>
-                      {% endif %}
-                      <th>{% blocktrans %}Subject{% endblocktrans %}</th>
-                      <th>{% blocktrans %}Teachers{% endblocktrans %}</th>
-                      <th>{% blocktrans %}Lesson topic{% endblocktrans %}</th>
-                      <th>{% blocktrans %}Homework{% endblocktrans %}</th>
-                      <th>{% blocktrans %}Group note{% endblocktrans %}</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    {% for register_object in objects %}
-                      {% has_perm "alsijil.view_lessondocumentation_rule" user register_object as can_view_lesson_documentation %}
-                      {% if can_view_lesson_documentation %}
-                        <tr>
-                          <td class="center-align">
-                            {% include "alsijil/partials/lesson_status.html" with register_object=register_object %}
-                          </td>
-                          <td class="tr-link">
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {% if register_object.period %}
-                                {{ register_object.period.period }}.
-                              {% else %}
-                                {{ register_object.period_from_on_day }}.–
-                                {{ register_object.period_to_on_day }}.
-                              {% endif %}
-                            </a>
-                          </td>
-                          {% if not group %}
-                            <td>
-                              <a class="tr-link"
-                                 href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                                {% if register_object.lesson %}
-                                  {{ register_object.lesson.group_names }}
-                                {% else %}
-                                  {{ register_object.group_names }}
-                                {% endif %}
-                              </a>
-                            </td>
-                          {% endif %}
-                          <td>
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {% if register_object.get_subject %}
-                                {{ register_object.get_subject.name }}
-                              {% elif register_object.subject %}
-                                {{ register_object.subject }}
-                              {% else %}
-                                {% trans "Event" %} ({{ register_object.title }})
-                              {% endif %}
-                            </a>
-                          </td>
-                          <td>
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {{ register_object.teacher_names }}
-                            </a>
-                          </td>
-                          <td>
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {% firstof register_object.get_lesson_documentation.topic "–" %}
-                            </a>
-                          </td>
-                          <td>
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {% firstof register_object.get_lesson_documentation.homework "–" %}
-                            </a>
-                          </td>
-                          <td>
-                            <a class="tr-link"
-                               href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                              {% firstof register_object.get_lesson_documentation.group_note "–" %}
-                            </a>
-                          </td>
-                        </tr>
-                      {% endif %}
-                    {% endfor %}
-                    </tbody>
-                  </table>
-                </div>
-              </div>
-              <ul class="collapsible hide-on-extra-large-only hide-on-small-only">
-                <li class="">
-                  <div class="collapsible-header flow-text">
-                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }} <i
-                    class="material-icons iconify collapsible-icon-right" data-icon="mdi:unfold-more-horizontal"></i>
-                  </div>
-                  <div class="collapsible-body">
-                    <div class="collection">
-                      {% for register_object in objects %}
-                        {% has_perm "alsijil.view_lessondocumentation_rule" user register_object as can_view_lesson_documentation %}
-                        {% if can_view_lesson_documentation %}
-                          <a class="collection-item avatar"
-                             href="{{ register_object.alsijil_url }}?back={{ back_url }}">
-                            {% include "alsijil/partials/lesson_status.html" with register_object=register_object css_class="materialize-circle" color_suffix=" " %}
-                            <table>
-                              <tr>
-                                <th>{% trans "Subject" %}</th>
-                                <td>
-                                  {% if register_object.period %}
-                                    {{ register_object.period.period }}.
-                                  {% else %}
-                                    {{ register_object.period_from_on_day }}.–
-                                    {{ register_object.period_to_on_day }}.
-                                  {% endif %}
-                                  {% if register_object.get_subject %}
-                                    {{ register_object.get_subject.name }}
-                                  {% elif register_object.subject %}
-                                    {{ register_object.subject }}
-                                  {% else %}
-                                    {% trans "Event" %}
-                                  {% endif %}
-                                </td>
-                              </tr>
-                              {% if not group %}
-                                <tr>
-                                  <th>{% trans "Groups" %}</th>
-                                  <td>
-                                    {% if register_object.lesson %}
-                                      {{ register_object.lesson.group_names }}
-                                    {% else %}
-                                      {{ register_object.group_names }}
-                                    {% endif %}
-                                  </td>
-                                </tr>
-                              {% endif %}
-                              <tr>
-                                <th>{% trans "Teachers" %}</th>
-                                <td>
-                                  {{ register_object.teacher_names }}
-                                </td>
-                              </tr>
-                              <tr>
-                                <th>{% trans "Lesson topic" %}</th>
-                                <td>{% firstof register_object.get_lesson_documentation.topic "–" %}</td>
-                              </tr>
-                              {% with register_object.get_lesson_documentation as lesson_documentation %}
-                                {% if lesson_documentation.homework %}
-                                  <tr>
-                                    <th>{% trans "Homework" %}</th>
-                                    <td>{% firstof register_object.get_lesson_documentation.homework "–" %}</td>
-                                  </tr>
-                                {% endif %}
-                                {% if lesson_documentation.group_note %}
-                                  <tr>
-                                    <th>{% trans "Group note" %}</th>
-                                    <td>{% firstof register_object.get_lesson_documentation.group_note "–" %}</td>
-                                  </tr>
-                                {% endif %}
-                              {% endwith %}
-                            </table>
-                          </a>
-                        {% endif %}
-                      {% endfor %}
-                    </div>
-                  </div>
-                </li>
-              </ul>
-              <div class="hide-on-med-and-up">
-                <h3>{{ advanced_weekday.name }}</h3>
-                <p class="subtitle">
-                  <span>{{ advanced_weekday.date }}</span>
-                  <button class="btn-superflat right waves-effect unfold-trigger">
-                    {% trans "Unfold" %}
-                    <i class="material-icons iconify" data-icon="mdi:unfold-less-horizontal"></i>
-                  </button>
-                </p>
-                <div class="horizontal-scroll-container">
-                  {% for register_object in objects %}
-                    <div class="card horizontal-scroll-card">
-                      <div class="card-content">
-                        <span class="card-title">
-                          <span class="period">
-                            {% if register_object.period %}
-                              {{ register_object.period.period }}.
-                            {% else %}
-                              {{ register_object.period_from_on_day }}.–{{ register_object.period_to_on_day }}.
-                            {% endif %}
-                          </span>
-                          <span class="subject">
-                            {% if register_object.get_subject %}
-                              {{ register_object.get_subject.name }}
-                            {% elif register_object.subject %}
-                              {{ register_object.subject }}
-                            {% else %}
-                              {% trans "Event" %}
-                            {% endif %}
-                          </span>
-                          <span class="lesson-icon">
-                            {% include "alsijil/partials/lesson_status.html" with register_object=register_object %}
-                          </span>
-                        </span>
-                        <dl>
-                          <div class="one-line">
-                            {% if not group %}
-                              <dt>{% trans "Groups" %}</dt>
-                              <dd>
-                                {% if register_object.lesson %}
-                                  {{ register_object.lesson.group_names }}
-                                {% else %}
-                                  {{ register_object.group_names }}
-                                {% endif %}
-                              </dd>
-                            {% endif %}
-
-                            <dt>{% trans "Teachers" %}</dt>
-                            <dd>
-                              {{ register_object.teacher_names }}
-                            </dd>
-                          </div>
-
-                          <dt>{% trans "Lesson topic" %}</dt>
-                          <dd>{% firstof register_object.get_lesson_documentation.topic "–" %}</dd>
-
-                          {% with register_object.get_lesson_documentation as lesson_documentation %}
-                            {% if lesson_documentation.homework %}
-                              <dt>{% trans "Homework" %}</dt>
-                              <dd>{% firstof register_object.get_lesson_documentation.homework "–" %}</dd>
-                            {% endif %}
-                            {% if lesson_documentation.group_note %}
-                              <dt>{% trans "Group note" %}</dt>
-                              <dd>{% firstof register_object.get_lesson_documentation.group_note "–" %}</dd>
-                            {% endif %}
-                          {% endwith %}
-                        </dl>
-                      </div>
-                      <div class="card-action">
-                        <a href="{{ register_object.alsijil_url }}?back={{ back_url }}"
-                           class="">
-                          {% trans "Visit lesson overview" %}
-                        </a>
-                      </div>
-                    </div>
-                  {% endfor %}
-                </div>
-              </div>
-            {% endif %}
-          {% endwith %}
-        {% endfor %}
-      </div>
-      <div class="col s12" id="personal-notes">
-        <div class="card">
-          <div class="card-content">
-              <span class="card-title">
-                {% blocktrans %}Personal notes{% endblocktrans %}
-              </span>
-            {% for person in persons %}
-              <h5 class="card-title">
-                <a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
-                {% has_perm "alsijil.register_absence_rule" user person.person as can_register_absence %}
-                {% if can_register_absence %}
-                  <a class="btn primary-color waves-effect waves-light right"
-                     href="{% url "register_absence" person.person.pk %}">
-                    <i class="material-icons iconify left" data-icon="mdi:message-draw"></i>
-                    {% trans "Register absence" %}
-                  </a>
-                {% endif %}
-              </h5>
-              {% if group_roles %}
-                <p>
-                  {% for assignment in person.group_roles %}
-                    {% include "alsijil/group_role/chip.html" with role=assignment.role small=assignment.date_range %}
-                  {% endfor %}
-                </p>
-              {% endif %}
-              <p class="card-text">
-                {% trans "Absent" %}: {{ person.person.absences_count }}
-                ({{ person.person.unexcused_count }} {% trans "unexcused" %})
-              </p>
-              <p class="card-text">
-                {% trans "Summed up tardiness" %}: {% firstof person.person.tardiness_sum|to_time|time:"H\h i\m" "–" %}
-              </p>
-              <p class="card-text">
-                {% trans "Count of tardiness" %}: {{ person.person.tardiness_count }} &times;
-              </p>
-              {% for extra_mark in extra_marks %}
-                <p class="card-text">
-                  {{ extra_mark.name }}: {{ person.person|get_dict:extra_mark.count_label }}
-                </p>
-              {% endfor %}
-              {% for note in person.personal_notes %}
-                <blockquote>
-                  {{ note.remarks }}
-                  <em class="right">
-                    <a href="{{ note.register_object.alsijil_url }}">
-                      {{ note.date_formatted }}, {{ note.register_object.get_subject.name }}
-                    </a>
-                  </em>
-                </blockquote>
-              {% endfor %}
-            {% endfor %}
-          </div>
-        </div>
-      </div>
-      {% if group_roles %}
-        <div class="col s12" id="group-roles">
-          {% include "alsijil/group_role/partials/assigned_roles.html" with roles=group_roles group=group back_url=back_url %}
-        </div>
-      {% endif %}
-    </div>
-  {% else %}
-    <div class="card">
-      <div class="card-content">
-        <span class="card-title">
-          <i class="material-icons iconify red-text left" data-icon="mdi:alert-outline"></i>
-          {% blocktrans %}No lessons available{% endblocktrans %}
-        </span>
-        <p>
-          {% blocktrans %}
-            There are no lessons for the selected group or teacher in this week.
-          {% endblocktrans %}
-        </p>
-      </div>
-    </div>
-  {% endif %}
-
-  <script src="{% static 'js/alsijil/week_view.js' %}" type="text/javascript"></script>
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html
deleted file mode 100644
index 6fc6faefb2543cecf32b02e7dcb7ea2a40f3d73b..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load material_form i18n %}
-
-{% block browser_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  {% include "alsijil/excuse_type/warning.html" %}
-
-  <form method="post">
-    {% csrf_token %}
-    {% form form=form %}{% endform %}
-    {% include "core/partials/save_button.html" %}
-  </form>
-
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html
deleted file mode 100644
index 78396ed66264cc19abdac2085d1cc89ff931bb38..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load material_form i18n %}
-
-{% block browser_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %}
-
-{% block content %}
-
-  <form method="post">
-    {% csrf_token %}
-    {% form form=form %}{% endform %}
-    {% include "core/partials/save_button.html" %}
-  </form>
-
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
deleted file mode 100644
index e6235a32f382aa9cf80ae3d8ad0ad1704e25e2e9..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-
-{% load i18n rules %}
-{% load render_table from django_tables2 %}
-
-{% block browser_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  {% include "alsijil/excuse_type/warning.html" %}
-
-  {% has_perm "alsijil.add_excusetype_rule" user as add_excusetype %}
-  {% if add_excusetype %}
-    <a class="btn green waves-effect waves-light" href="{% url 'create_excuse_type' %}">
-      <i class="material-icons iconify left"data-icon="mdi:plus"></i>
-      {% trans "Create excuse type" %}
-    </a>
-  {% endif %}
-
-  {% render_table table %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html
deleted file mode 100644
index 811b90b33381c578469552154092333b2b38624d..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% load i18n %}
-<figure class="alert warning">
-  <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
-  {% blocktrans %}
-    This function should only be used to define alternatives to the default excuse which also will be counted extra.
-    Don't use this to create a default excuse or if you don't divide between different types of excuse.
-  {% endblocktrans %}
-</figure>
diff --git a/aleksis/apps/alsijil/templates/alsijil/notifications/check.html b/aleksis/apps/alsijil/templates/alsijil/notifications/check.html
deleted file mode 100644
index d76a1a0a5abfc6fb8b7722d5a4cf16ff927f069a..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/notifications/check.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{% load i18n %}{% trans "Please check if the following class register entries are complete and correct:" %}
-{% for entry in items %}
-- {{ entry.register_object }} ({{ entry.date }})
-{% endfor %}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/heading.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/heading.html
deleted file mode 100644
index 132e97f05acd0216d59f89a12cab08b96e123cc5..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/heading.html
+++ /dev/null
@@ -1,110 +0,0 @@
-{% load i18n %}
-
-{% if next_lesson_person or prev_lesson_person or back_to_week_url %}
-  <div class="row margin-bottom alsijil-nav-header">
-    <div class="col s12 no-padding">
-      {# Back to week view #}
-      {% if back_to_week_url %}
-        <a href="{{ back_to_week_url }}"
-           class="btn secondary-color waves-light waves-effect margin-bottom {% if prev_lesson_person or next_lesson_person %}hide-on-extra-large-only{% endif %}">
-          <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i> {% trans "Week view" %}
-        </a>
-      {% endif %}
-
-      {% if prev_lesson_person or next_lesson_person %}
-        <div class="col s12 no-padding center alsijil-nav">
-          {% if back_to_week_url %}
-            <a href="{{ back_to_week_url }}"
-               class="btn-flat secondary-color-text waves-light waves-effect left hide-on-med-and-down hide-on-large-only show-on-extra-large">
-              <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i> {% trans "Week view" %}
-            </a>
-          {% endif %}
-
-          {# Previous lesson #}
-          <a class="btn-flat waves-effect waves-light left primary-color-text {% if not prev_lesson_person %}disabled{% endif %}"
-             title="{% trans "My previous lesson" %}"
-              {% if prev_lesson_person %}
-             href="{% url "lesson_period" prev_lesson_person.week.year prev_lesson_person.week.week prev_lesson_person.id %}"
-              {% endif %}
-          >
-            <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i>
-            <span class="hide-on-small-only">{% trans "My previous lesson" %}</span>
-            <span class="hide-on-med-and-up">{% trans "Previous" %}</span>
-          </a>
-          {# Next lesson #}
-          <a class="btn-flat waves-effect waves-light right primary-color-text {% if not next_lesson_person %}disabled{% endif %}"
-             title="{% trans "My next lesson" %}"
-              {% if next_lesson_person %}
-             href="{% url "lesson_period" next_lesson_person.week.year next_lesson_person.week.week next_lesson_person.id %}"
-              {% endif %}
-          >
-            <i class="material-icons iconify right" data-icon="mdi:chevron-right"></i>
-            <span class="hide-on-small-only">{% trans "My next lesson" %}</span>
-            <span class="hide-on-med-and-up">{% trans "Next" %}</span>
-          </a>
-          <span class="truncate">{{ request.user.person }}</span>
-        </div>
-      {% endif %}
-    </div>
-  </div>
-{% endif %}
-
-<h1>
-  <span class="right hide-on-small-only">
-    {% include "alsijil/partials/lesson_status.html" with register_object=register_object css_class="medium" %}
-  </span>
-
-  <a class="btn-flat waves-effect waves-light primary-color-text left alsijil-header-nav-button hide-on-med-and-up {% if not prev_lesson %}disabled{% endif %}"
-      {% if prev_lesson %}
-     href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"
-      {% endif %}
-  >
-    <i class="material-icons iconify center" data-icon="mdi:chevron-left"></i>
-  </a>
-  <a class="btn-flat waves-effect waves-light primary-color-text right alsijil-header-nav-button hide-on-med-and-up {% if not next_lesson %}disabled{% endif %}"
-      {% if next_lesson %}
-     href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"
-      {% endif %}
-  >
-    <i class="material-icons iconify center" data-icon="mdi:chevron-right"></i>
-  </a>
-
-  <span class="alsijil-time-head">
-    {% if register_object.label_ == "event" %}
-      {% if register_object.date_start == register_object.date_end %}
-        {% if register_object.period_from.period == register_object.period_to.period %}
-          {{ register_object.date_start|date:"SHORT_DATE_FORMAT" }},
-          {% blocktrans with period=register_object.period_from.period %}{{ period }}. period{% endblocktrans %}
-        {% else %}
-          {{ register_object.date_start|date:"SHORT_DATE_FORMAT" }},
-          {% blocktrans with period_from=register_object.period_from.period  period_to=register_object.period_to.period %}
-            {{ period_from }}.–{{ period_to }}.  period
-          {% endblocktrans %}
-        {% endif %}
-      {% else %}
-        {{ register_object.date_start|date:"SHORT_DATE_FORMAT" }},
-        {{ register_object.period_from.period }}.–{{ register_object.date_end|date:"SHORT_DATE_FORMAT" }},
-        {{ register_object.period_to.period }}.
-      {% endif %}
-    {% else %}
-      {{ day|date:"SHORT_DATE_FORMAT" }},
-      {% blocktrans with period=register_object.period.period %}{{ period }}. period{% endblocktrans %}
-    {% endif %}
-  </span>
-
-  <span class="alsijil-object-head">
-    {{ register_object.group_names }},
-
-    {% if register_object.label_ == "event" %}
-      {% trans "Event" %} ({{ register_object.title }}),
-    {% else %}
-      {{ register_object.get_subject.short_name }},
-    {% endif %}
-
-    {{ register_object.teacher_short_names }}
-  </span>
-</h1>
-
-<div class="hide-on-med-and-up margin-bottom">
-  {% include "alsijil/partials/lesson_status.html" with register_object=register_object chip=1 css_class="hundred-percent center" %}
-</div>
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/prev_next.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/prev_next.html
deleted file mode 100644
index a12cf71e843ada4d1caeec54a82c8ca1876cb7ec..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/prev_next.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{% load i18n %}
-
-<div class="row no-margin hide-on-small-only">
-  <div class="col s12 no-padding">
-    {% if not blocked_because_holidays and with_save %}
-      {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-        <button type="submit" class="btn waves-effect waves-light green margin-bottom">
-          <i class="material-icons iconify left" data-icon="mdi:content-save-outline"></i>
-          {% trans "Save" %}
-        </button>
-      {% endif %}
-    {% endif %}
-
-    <a class="btn waves-effect waves-light primary margin-bottom {% if not prev_lesson %}disabled{% endif %}"
-        {% if prev_lesson %}
-       href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}"
-        {% endif %}
-    >
-      <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
-      {% blocktrans with subject=register_object.get_subject.short_name %}
-        Previous {{ subject }} lesson
-      {% endblocktrans %}
-    </a>
-
-    <a class="btn right waves-effect waves-light primary margin-bottom {% if not next_lesson %}disabled{% endif %}"
-        {% if next_lesson %}
-       href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}"
-        {% endif %}
-    >
-      <i class="material-icons iconify right" data-icon="mdi:arrow-right"></i>
-      {% blocktrans with subject=register_object.get_subject.short_name %}
-        Next {{ subject }} lesson
-      {% endblocktrans %}
-    </a>
-  </div>
-</div>
-
-
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/documentation.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/documentation.html
deleted file mode 100644
index 22b396f457a13d4d04824836745fd49fdf7eac72..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/documentation.html
+++ /dev/null
@@ -1,65 +0,0 @@
-{% load i18n material_form_internal material_form %}
-
-{% include "alsijil/partials/lesson/prev_next.html" with with_save=0 %}
-
-<div class="hide-on-med-and-up margin-bottom">
-  {% if not blocked_because_holidays %}
-    {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-      {% include "core/partials/save_button.html" %}
-    {% endif %}
-  {% endif %}
-</div>
-
-<div class="card">
-  <div class="card-content">
-    <span class="card-title">
-      {% blocktrans %}Lesson documentation{% endblocktrans %}
-    </span>
-
-    {% if can_edit_lesson_documentation %}
-      {% form form=lesson_documentation_form %}{% endform %}
-    {% elif can_view_lesson_documentation %}
-      <table>
-        <tr>
-          <th>
-            {% trans "Lesson topic" %}
-          </th>
-          <td>
-            {{ lesson_documentation.topic }}
-          </td>
-        </tr>
-        <tr>
-          <th>
-            {% trans "Homework" %}
-          </th>
-          <td>
-            {{ lesson_documentation.homework }}
-          </td>
-        </tr>
-        <tr>
-          <th>
-            {% trans "Group note" %}
-          </th>
-          <td>
-            {{ lesson_documentation.group_note }}
-          </td>
-        </tr>
-      </table>
-    {% endif %}
-  </div>
-  <div class="card-action-light hide-on-small-only">
-    {% if not blocked_because_holidays %}
-      {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-        {% include "core/partials/save_button.html" %}
-      {% endif %}
-    {% endif %}
-  </div>
-</div>
-
-<div class="hide-on-med-and-up">
-  {% if not blocked_because_holidays %}
-    {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-      {% include "core/partials/save_button.html" %}
-    {% endif %}
-  {% endif %}
-</div>
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/more.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/more.html
deleted file mode 100644
index ffc7706488757871e43de4e71da9e73802273a3f..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/more.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% load i18n %}
-
-{% if group_roles %}
-  {% include "alsijil/group_role/partials/assigned_roles.html" with roles=group_roles group=register_object.get_groups.first back_url=back_url %}
-{% endif %}
-
-{% if can_view_lesson_documentation %}
-  <div class="card">
-    <div class="card-content">
-      <span class="card-title">
-        {% blocktrans %}Change history{% endblocktrans %}
-      </span>
-      {% include 'core/partials/crud_events.html' with obj=lesson_documentation %}
-    </div>
-  </div>
-{% endif %}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/notes.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/notes.html
deleted file mode 100644
index 1b013252a8f316979cdf5bdcc87ef1c7463b6e36..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/notes.html
+++ /dev/null
@@ -1,142 +0,0 @@
-{% load i18n material_form_internal material_form time_helpers %}
-
-{% include "alsijil/partials/lesson/prev_next.html" with with_save=1 %}
-
-{% if not blocked_because_holidays %}
-  {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-    <button type="submit"
-            class="btn waves-effect waves-light green margin-bottom hundred-percent hide-on-med-and-up">
-      <i class="material-icons iconify left" data-icon="mdi:content-save-outline"></i> {% trans "Save" %}
-    </button>
-  {% endif %}
-{% endif %}
-
-{% if can_edit_register_object_personalnote %}
-  {% form form=personal_note_formset.management_form %}{% endform %}
-{% endif %}
-
-<div class="card no-mobile-card">
-  <div class="card-content">
-    <span class="card-title">
-      {% blocktrans %}Personal notes{% endblocktrans %}
-    </span>
-
-    <table class="striped alsijil-table horizontal-on-small">
-      <thead>
-      <tr>
-        <th>{% blocktrans %}Person{% endblocktrans %}</th>
-        <th>{% blocktrans %}Absent{% endblocktrans %}</th>
-        <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
-        <th>{% blocktrans %}Excused{% endblocktrans %}</th>
-        <th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
-        <th>{% blocktrans %}Extra marks{% endblocktrans %}</th>
-        <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
-      </tr>
-      </thead>
-      <tbody>
-      {% for form in personal_note_formset %}
-        {% if can_edit_register_object_personalnote %}
-          <tr>
-            {{ form.id }}
-            <td class="person-name">{{ form.person_name }}{{ form.person_name.value }}
-              <p>
-                {% for assignment in form.instance.person.group_roles.all %}
-                  {% include "alsijil/group_role/chip.html" with role=assignment.role %}
-                {% endfor %}
-              </p>
-            </td>
-            <td class="center-align">
-              <label>
-                {{ form.absent }}
-                <span><span class="hide-on-large-only">{{ form.absent.label }}</span></span>
-              </label>
-            </td>
-            <td>
-              <div class="input-field">
-                {{ form.tardiness }}
-                <label for="{{ form.absent.id_for_label }}">
-                  {% trans "Tardiness (in m)" %}
-                </label>
-              </div>
-            </td>
-            <td class="center-align">
-              <label>
-                {{ form.excused }}
-                <span><span class="hide-on-large-only">{{ form.excused.label }}</span></span>
-              </label>
-            </td>
-            <td>
-              <div class="input-field">
-                {{ form.excuse_type }}
-                <label for="{{ form.excuse_type.id_for_label }}">
-                  {% trans "Excuse type" %}
-                </label>
-              </div>
-            </td>
-            <td>
-              {% for group, items in form.extra_marks|select_options %}
-                {% for choice, value, selected in items %}
-                  <label class="{% if selected %} active{% endif %} alsijil-check-box">
-                    <input type="checkbox"
-                           {% if value == None or value == '' %}disabled{% else %}value="{{ value }}"{% endif %}
-                        {% if selected %} checked="checked"{% endif %}
-                           name="{{ form.extra_marks.html_name }}">
-                    <span>{{ choice }}</span>
-                  </label>
-                {% endfor %}
-              {% endfor %}
-            </td>
-            <td>
-              <div class="input-field">
-                {{ form.remarks }}
-                <label for="{{ form.remarks.id_for_label }}">
-                  {% trans "Remarks" %}
-                </label>
-              </div>
-            </td>
-          </tr>
-        {% else %}
-          <tr>
-            <td>{{ form.person_name.value }}
-              <p>
-                {% for assignment in form.instance.person.group_roles.all %}
-                  {% include "alsijil/group_role/chip.html" with role=assignment.role %}
-                {% endfor %}
-              </p>
-            </td>
-            <td>
-              <i class="material-icons iconify center" data-icon="mdi:{{ form.absent.value|yesno:"check,close" }}"></i>
-            </td>
-            <td>
-              <i class="material-icons iconify center" data-icon="mdi:{{ form.tardiness.value|yesno:"check,close" }}"></i>
-              <span class="alsijil-tardiness-text">
-                {% if form.tardiness.value %}{{ form.tardiness.value|to_time|time:"i\m" }}{% endif %}
-              </span>
-            </td>
-            <td>
-              <i class="material-icons iconify center" data-icon="mdi:{{ form.excused.value|yesno:"check,close" }}"></i>
-              </td>
-            <td>{% firstof form.instance.excuse_type "–" %}</td>
-            <td>
-              {% for extra_mark in form.instance.extra_marks.all %}
-                {{ extra_mark }}{% if not forloop.last %},{% endif %}
-              {% empty %}
-                –
-              {% endfor %}
-            </td>
-            <td>{% firstof form.remarks.value "–" %}</td>
-          </tr>
-        {% endif %}
-      {% endfor %}
-      </tbody>
-    </table>
-  </div>
-</div>
-{% if not blocked_because_holidays %}
-  {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
-    <button type="submit"
-            class="btn waves-effect waves-light green margin-bottom hundred-percent hide-on-med-and-up">
-      <i class="material-icons iconify left" data-icon="mdi:content-save-outline"></i> {% trans "Save" %}
-    </button>
-  {% endif %}
-{% endif %}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/previous_lesson.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/previous_lesson.html
deleted file mode 100644
index 8457576b786f579032d5c02052c3ea02a940b6ce..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/previous_lesson.html
+++ /dev/null
@@ -1,62 +0,0 @@
-{% load i18n rules %}
-
-<div class="card">
-  <div class="card-content">
-    <span class="card-title">
-      {% blocktrans %}Overview: Previous lesson{% endblocktrans %} ({{ prev_doc.date_formatted }},
-      {% blocktrans with period=prev_lesson.period.period %}{{ period }}. period{% endblocktrans %})
-    </span>
-
-    <table>
-      {% if prev_doc.topic %}
-        <tr>
-          <th class="collection-item">{% trans "Lesson topic of previous lesson:" %}</th>
-          <td>{{ prev_doc.topic }}</td>
-        </tr>
-      {% endif %}
-
-      {% if prev_doc.homework %}
-        <tr>
-          <th class="collection-item">{% trans "Homework for this lesson:" %}</th>
-          <td>{{ prev_doc.homework }}</td>
-        </tr>
-      {% endif %}
-
-      {% if prev_doc.group_note %}
-        <tr>
-          <th class="collection-item">{% trans "Group notes for previous lesson:" %}</th>
-          <td>{{ prev_doc.group_note }}</td>
-        </tr>
-      {% endif %}
-
-      {% if absences %}
-        <tr>
-          <th>{% trans "Absent persons:" %}</th>
-          <td>{% include "alsijil/partials/absences.html" with notes=absences %}</td>
-        </tr>
-      {% endif %}
-
-      {% if tardinesses %}
-        <tr>
-          <th>{% trans "Late persons:" %}</th>
-          <td>{% include "alsijil/partials/tardinesses.html" with notes=tardinesses %}</td>
-        </tr>
-      {% endif %}
-
-      {% for extra_mark, notes in extra_marks.items %}
-        <tr>
-          <th>{{ extra_mark.name }}</th>
-          <td>
-            {% for note in notes %}
-              {% has_perm "alsijil.view_personalnote_rule" user note as can_view_personalnote %}
-              {% if can_view_personalnote %}
-                <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span>
-              {% endif %}
-            {% endfor %}
-          </td>
-        </tr>
-      {% endfor %}
-
-    </table>
-  </div>
-</div>
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/seating_plan.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/seating_plan.html
deleted file mode 100644
index de5e9f44f947ac49a951d30b6de58a02e844f044..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson/tabs/seating_plan.html
+++ /dev/null
@@ -1,89 +0,0 @@
-{% load i18n material_form_internal material_form time_helpers rules %}
-
-
-{% if seating_plan %}
-  <div class="card no-mobile-card">
-    <div class="card-content">
-      <div class="card-title margin-bottom">
-        {% blocktrans with group=seating_plan.group room=seating_plan.room %}Seating plan for {{ group }} in
-          {{ room }}{% endblocktrans %}
-      </div>
-      {% if seating_plan_parent %}
-        <figure class="alert primary">
-          <i class="material-icons iconify left" data-icon="information-outline"></i>
-          {% blocktrans with child_group=first_group %}
-            This seating plan is taken from the parent group of {{ child_group }}.
-            If you want, you can take it over for your group and then customize it.
-          {% endblocktrans %}
-        </figure>
-      {% endif %}
-
-      <div class="row margin-bottom no-padding">
-        <div class="col s12 no-padding">
-          {% has_perm "stoelindeling.edit_seatingplan_rule" user seating_plan as can_edit %}
-          {% has_perm "stoelindeling.copy_seatingplan_for_group_rule" user first_group as can_copy %}
-
-          {% if can_edit %}
-            <a class="btn orange waves-effect waves-light"
-               href="{% url "edit_seating_plan" seating_plan.pk %}?next={{ back_url }}#seating-plan">
-              <i class="material-icons iconify left" data-icon="mdi:pencil-outline"></i>
-              {% trans "Edit seating plan" %}
-            </a>
-          {% endif %}
-          {% if can_copy and seating_plan_parent %}
-            <a class="btn orange waves-effect waves-light"
-               href="{% url "copy_seating_plan" seating_plan.pk %}?next={{ back_url }}#seating-plan">
-              <i class="material-icons iconify left" data-icon="mdi:content-copy"></i>
-              {% trans "Copy plan and edit" %}
-            </a>
-          {% endif %}
-        </div>
-      </div>
-
-      <div class="row">
-        <div class="col s12">
-          {% include "stoelindeling/seating_plan/render.html" %}
-        </div>
-      </div>
-    </div>
-  </div>
-{% else %}
-  <div class="container">
-    <div class="card">
-      <div class="card-content">
-        <div class="card-title">
-          <i class="material-icons iconify left small orange-text" data-icon="mdi:alert-outline"></i>
-          {% trans "There is no seating plan for this lesson." %}
-        </div>
-        {% has_perm "stoelindeling.create_seatingplan_rule" user first_group as can_add %}
-        {% if can_add %}
-          <div class="row margin-bottom">
-            <div class="col s12">
-              <a class="btn waves-effect waves-light" href="{% url "create_seating_plan" %}?group={{ first_group.pk }}&subject={{ register_object.get_subject.pk }}&room={{ register_object.get_room.pk }}&next={{ back_url }}#seating-plan">
-                <i class="material-icons iconify left" data-icon="mdi:plus"></i>
-                {% blocktrans with group=first_group.name subject=register_object.get_subject.name room=register_object.get_room.name %}
-                  Create a new seating plan for {{ group }} ({{ subject }}) in {{ room }}
-                {% endblocktrans %}
-              </a>
-            </div>
-          </div>
-        {% endif %}
-        {% for parent_group in first_group.parent_groups.all %}
-          {% has_perm "stoelindeling.create_seatingplan_rule" user parent_group as can_add %}
-          {% if can_add %}
-            <div class="row">
-              <div class="col s12">
-                <a class="btn waves-effect waves-light" href="{% url "create_seating_plan" %}?group={{ parent_group.pk }}&subject={{ register_object.get_subject.pk }}&room={{ register_object.get_room.pk }}&next={{ back_url }}#seating-plan">
-                  <i class="material-icons iconify left" data-icon="mdi:plus"></i>
-                  {% blocktrans with group=parent_group.name room=register_object.get_room.name %}
-                    Create a new seating plan for {{ group }} in {{ room }}
-                  {% endblocktrans %}
-                </a>
-              </div>
-            </div>
-          {% endif %}
-        {% endfor %}
-      </div>
-    </div>
-  </div>
-{% endif %}
diff --git a/aleksis/apps/alsijil/tests/test_actions.py b/aleksis/apps/alsijil/tests/test_actions.py
deleted file mode 100644
index 8dd499ac953583637a3ade36c835aa0e5d6aae24..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/tests/test_actions.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from datetime import date, time
-
-import pytest
-
-from aleksis.apps.alsijil.actions import (
-    delete_personal_note,
-    mark_as_excuse_type_generator,
-    mark_as_excused,
-    mark_as_unexcused,
-)
-from aleksis.apps.alsijil.models import ExcuseType, PersonalNote
-from aleksis.apps.chronos.models import Event, TimePeriod
-from aleksis.core.models import Person
-
-pytestmark = pytest.mark.django_db
-
-
-def _generate_event(day: date):
-    period_from = TimePeriod.objects.create(
-        weekday=0, period=1, time_start=time(10, 00), time_end=time(11, 00)
-    )
-    period_to = TimePeriod.objects.create(
-        weekday=0, period=2, time_start=time(11, 00), time_end=time(12, 00)
-    )
-
-    event = Event.objects.create(
-        date_start=day, date_end=day, period_from=period_from, period_to=period_to
-    )
-    return event
-
-
-def _prepare_notes():
-    """Create some minimal personal notes."""
-    person, __ = Person.objects.get_or_create(first_name="Jane", last_name="Doe")
-
-    excuse_type, __ = ExcuseType.objects.get_or_create(short_name="Foo", name="Fooooooooooooo")
-    notes = [
-        PersonalNote(
-            person=person,
-            event=_generate_event(date(2021, 10, 1)),
-            absent=True,
-            remarks="This is baz.",
-        ),
-        PersonalNote(person=person, event=_generate_event(date(2021, 11, 1)), absent=True),
-        PersonalNote(
-            person=person, event=_generate_event(date(2022, 10, 1)), absent=True, excused=True
-        ),
-        PersonalNote(
-            person=person,
-            event=_generate_event(date(2021, 3, 1)),
-            absent=True,
-            excused=True,
-            excuse_type=excuse_type,
-        ),
-        PersonalNote(person=person, event=_generate_event(date(2021, 10, 4)), tardiness=10),
-        PersonalNote(
-            person=person, event=_generate_event(date(2032, 10, 11)), remarks="Good work!"
-        ),
-        PersonalNote(person=person, event=_generate_event(date(2032, 10, 11))),
-    ]
-    PersonalNote.objects.bulk_create(notes)
-    return notes
-
-
-def test_mark_as_excused_action():
-    notes = _prepare_notes()
-    assert PersonalNote.objects.filter(excused=True).count() == 2
-    mark_as_excused(None, None, PersonalNote.objects.all())
-    assert PersonalNote.objects.filter(excused=True).count() == 4
-    assert PersonalNote.objects.filter(excuse_type=None, excused=True).count() == 4
-
-
-def test_mark_as_unexcused_action():
-    notes = _prepare_notes()
-    assert PersonalNote.objects.filter(excused=True).count() == 2
-    mark_as_unexcused(None, None, PersonalNote.objects.all())
-    assert PersonalNote.objects.filter(excused=True).count() == 0
-    assert PersonalNote.objects.filter(excuse_type=None, excused=True).count() == 0
-
-
-def test_mark_as_excuse_type_generator_action():
-    excuse_type, __ = ExcuseType.objects.get_or_create(short_name="Foo", name="Fooooooooooooo")
-    notes = _prepare_notes()
-    assert PersonalNote.objects.filter(excused=True).count() == 2
-    assert PersonalNote.objects.filter(excused=True, excuse_type=excuse_type).count() == 1
-    mark_as_foo = mark_as_excuse_type_generator(excuse_type=excuse_type)
-    mark_as_foo(None, None, PersonalNote.objects.all())
-    assert PersonalNote.objects.filter(excused=True).count() == 4
-    assert PersonalNote.objects.filter(excuse_type=excuse_type, excused=True).count() == 4
-
-
-def test_delete_personal_note_action():
-    notes = _prepare_notes()
-    assert PersonalNote.objects.not_empty().count() == 6
-    delete_personal_note(None, None, PersonalNote.objects.all())
-    assert PersonalNote.objects.not_empty().count() == 0
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
index 118b70af8e1a71f9d5e56a8d251c5a319cae0e02..4c93eb1fc46e9fda08afa58a2dbb8d8dbf71e150 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -1,407 +1,4 @@
-from datetime import date
-from operator import itemgetter
-from typing import Any, Dict, Iterable, List, Optional, Sequence, Union
-
-from django.db.models.expressions import Exists, OuterRef
-from django.db.models.query import Prefetch, QuerySet
-from django.db.models.query_utils import Q
-from django.http import HttpRequest
-from django.utils.formats import date_format
-from django.utils.translation import gettext as _
-
-from calendarweek import CalendarWeek
-
-from aleksis.apps.alsijil.forms import FilterRegisterObjectForm
-from aleksis.apps.alsijil.models import LessonDocumentation
-from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod
-from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
 from aleksis.apps.kolego.models import AbsenceReasonTag
-from aleksis.core.models import Group
-from aleksis.core.util.core_helpers import get_site_preferences
-
-
-def get_register_object_by_pk(
-    request: HttpRequest,
-    model: Optional[str] = None,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    id_: Optional[int] = None,
-) -> Optional[Union[LessonPeriod, Event, ExtraLesson]]:
-    """Get register object either by given object_id or by time and current person."""
-    wanted_week = CalendarWeek(year=year, week=week)
-    if id_ and model == "lesson":
-        register_object = LessonPeriod.objects.annotate_week(wanted_week).get(pk=id_)
-    elif id_ and model == "event":
-        register_object = Event.objects.get(pk=id_)
-    elif id_ and model == "extra_lesson":
-        register_object = ExtraLesson.objects.get(pk=id_)
-    elif hasattr(request, "user") and hasattr(request.user, "person"):
-        if request.user.person.lessons_as_teacher.exists():
-            register_object = (
-                LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
-            )
-        else:
-            register_object = (
-                LessonPeriod.objects.at_time().filter_participant(request.user.person).first()
-            )
-    else:
-        register_object = None
-    return register_object
-
-
-def get_timetable_instance_by_pk(
-    request: HttpRequest,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    type_: Optional[str] = None,
-    id_: Optional[int] = None,
-):
-    """Get timetable object (teacher, room or group) by given type and id or the current person."""
-    if type_ and id_:
-        return get_el_by_pk(request, type_, id_)
-    elif hasattr(request, "user") and hasattr(request.user, "person"):
-        return request.user.person
-
-
-def annotate_documentations(
-    klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int]
-) -> QuerySet:
-    """Return an annotated queryset of all provided register objects."""
-    if isinstance(klass, LessonPeriod):
-        prefetch = Prefetch(
-            "documentations",
-            queryset=LessonDocumentation.objects.filter(
-                week=wanted_week.week, year=wanted_week.year
-            ),
-        )
-    else:
-        prefetch = Prefetch("documentations")
-    instances = klass.objects.prefetch_related(prefetch).filter(pk__in=pks)
-
-    if klass == LessonPeriod:
-        instances = instances.annotate_week(wanted_week)
-    elif klass in (LessonPeriod, ExtraLesson):
-        instances = instances.order_by("period__weekday", "period__period")
-    else:
-        instances = instances.order_by("period_from__weekday", "period_from__period")
-
-    instances = instances.annotate(
-        has_documentation=Exists(
-            LessonDocumentation.objects.filter(
-                ~Q(topic__exact=""),
-                Q(week=wanted_week.week, year=wanted_week.year) | Q(week=None, year=None),
-            ).filter(**{klass.label_: OuterRef("pk")})
-        )
-    )
-
-    return instances
-
-
-def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLesson]) -> int:
-    """Sort key for sorted/sort for sorting a list of class register objects.
-
-    This will sort the objects by the start period.
-    """
-    if hasattr(register_object, "period"):
-        return register_object.period.period
-    elif isinstance(register_object, Event):
-        return register_object.period_from_on_day
-    else:
-        return 0
-
-
-def _filter_register_objects_by_dict(
-    filter_dict: Dict[str, Any],
-    register_objects: QuerySet[Union[LessonPeriod, Event, ExtraLesson]],
-    label_: str,
-) -> QuerySet[Union[LessonPeriod, Event, ExtraLesson]]:
-    """Filter register objects by a dictionary generated through ``FilterRegisterObjectForm``."""
-    if label_ == LessonPeriod.label_:
-        register_objects = register_objects.filter(
-            lesson__validity__school_term=filter_dict.get("school_term")
-        )
-    else:
-        register_objects = register_objects.filter(school_term=filter_dict.get("school_term"))
-    register_objects = register_objects.distinct()
-
-    if (
-        filter_dict.get("date_start")
-        and filter_dict.get("date_end")
-        and label_ != LessonPeriod.label_
-    ):
-        register_objects = register_objects.within_dates(
-            filter_dict.get("date_start"), filter_dict.get("date_end")
-        )
-
-    if filter_dict.get("person"):
-        if label_ == LessonPeriod.label_:
-            register_objects = register_objects.filter(
-                Q(lesson__teachers=filter_dict.get("person"))
-                | Q(substitutions__teachers=filter_dict.get("person"))
-            )
-        else:
-            register_objects = register_objects.filter_teacher(filter_dict.get("person"))
-
-    if filter_dict.get("group"):
-        register_objects = register_objects.filter_group(filter_dict.get("group"))
-
-    if filter_dict.get("groups"):
-        register_objects = register_objects.filter_groups(filter_dict.get("groups"))
-
-    if filter_dict.get("subject"):
-        if label_ == LessonPeriod.label_:
-            register_objects = register_objects.filter(
-                Q(lesson__subject=filter_dict.get("subject"))
-                | Q(substitutions__subject=filter_dict.get("subject"))
-            )
-        elif label_ == Event.label_:
-            # As events have no subject, we exclude them at all
-            register_objects = register_objects.none()
-        else:
-            register_objects = register_objects.filter(subject=filter_dict.get("subject"))
-
-    return register_objects
-
-
-def _generate_dicts_for_lesson_periods(
-    filter_dict: Dict[str, Any],
-    lesson_periods: QuerySet[LessonPeriod],
-    documentations: Optional[Iterable[LessonDocumentation]] = None,
-    holiday_days: Optional[Sequence[date]] = None,
-) -> List[Dict[str, Any]]:
-    """Generate a list of dicts for use with ``RegisterObjectTable``."""
-    if not holiday_days:
-        holiday_days = []
-    lesson_periods = list(lesson_periods)
-    date_start = lesson_periods[0].lesson.validity.date_start
-    date_end = lesson_periods[-1].lesson.validity.date_end
-    if (
-        filter_dict["filter_date"]
-        and filter_dict.get("date_start") > date_start
-        and filter_dict.get("date_start") < date_end
-    ):
-        date_start = filter_dict.get("date_start")
-    if (
-        filter_dict["filter_date"]
-        and filter_dict.get("date_end") < date_end
-        and filter_dict.get("date_end") > date_start
-    ):
-        date_end = filter_dict.get("date_end")
-    weeks = CalendarWeek.weeks_within(date_start, date_end)
-
-    register_objects = []
-    inherit_privileges_preference = get_site_preferences()[
-        "alsijil__inherit_privileges_from_parent_group"
-    ]
-    for lesson_period in lesson_periods:
-        parent_group_owned_by_person = inherit_privileges_preference and (
-            Group.objects.filter(
-                child_groups__in=Group.objects.filter(lessons__lesson_periods=lesson_period),
-                owners=filter_dict.get("person"),
-            ).exists()
-        )
-        for week in weeks:
-            day = week[lesson_period.period.weekday]
-
-            # Skip all lesson periods in holidays
-            if day in holiday_days:
-                continue
-            # Ensure that the lesson period is in filter range and validity range
-            if (
-                lesson_period.lesson.validity.date_start
-                <= day
-                <= lesson_period.lesson.validity.date_end
-            ) and (
-                not filter_dict.get("filter_date")
-                or (filter_dict.get("date_start") <= day <= filter_dict.get("date_end"))
-            ):
-                sub = lesson_period.get_substitution()
-
-                # Skip lesson period if the person isn't a teacher,
-                # substitution teacher or, when the corresponding
-                # preference is switched on, owner of a parent group
-                # of this lesson period
-                if filter_dict.get("person") and (
-                    filter_dict.get("person") not in lesson_period.lesson.teachers.all()
-                    and not sub
-                    and not parent_group_owned_by_person
-                ):
-                    continue
-
-                teachers = lesson_period.teacher_names
-                if (
-                    filter_dict.get("subject")
-                    and filter_dict.get("subject") != lesson_period.get_subject()
-                ):
-                    continue
-
-                # Filter matching documentations and annotate if they exist
-                filtered_documentations = list(
-                    filter(
-                        lambda d: d.week == week.week
-                        and d.year == week.year
-                        and d.lesson_period_id == lesson_period.pk,
-                        documentations
-                        if documentations is not None
-                        else lesson_period.documentations.all(),
-                    )
-                )
-                has_documentation = bool(filtered_documentations)
-
-                if filter_dict.get(
-                    "has_documentation"
-                ) is not None and has_documentation != filter_dict.get("has_documentation"):
-                    continue
-
-                # Build table entry
-                entry = {
-                    "pk": f"lesson_period_{lesson_period.pk}_{week.year}_{week.week}",
-                    "week": week,
-                    "has_documentation": has_documentation,
-                    "substitution": sub,
-                    "register_object": lesson_period,
-                    "date": date_format(day),
-                    "date_sort": day,
-                    "period": f"{lesson_period.period.period}.",
-                    "period_sort": lesson_period.period.period,
-                    "groups": lesson_period.lesson.group_names,
-                    "teachers": teachers,
-                    "subject": lesson_period.get_subject().name,
-                }
-                if has_documentation:
-                    doc = filtered_documentations[0]
-                    entry["topic"] = doc.topic
-                    entry["homework"] = doc.homework
-                    entry["group_note"] = doc.group_note
-                register_objects.append(entry)
-    return register_objects
-
-
-def _generate_dicts_for_events_and_extra_lessons(
-    filter_dict: Dict[str, Any],
-    register_objects_start: Sequence[Union[Event, ExtraLesson]],
-    documentations: Optional[Iterable[LessonDocumentation]] = None,
-) -> List[Dict[str, Any]]:
-    """Generate a list of dicts for use with ``RegisterObjectTable``."""
-    register_objects = []
-    for register_object in register_objects_start:
-        filtered_documentations = list(
-            filter(
-                lambda d: getattr(d, f"{register_object.label_}_id") == register_object.pk,
-                documentations
-                if documentations is not None
-                else register_object.documentations.all(),
-            )
-        )
-        has_documentation = bool(filtered_documentations)
-
-        if filter_dict.get(
-            "has_documentation"
-        ) is not None and has_documentation != filter_dict.get("has_documentation"):
-            continue
-
-        if isinstance(register_object, ExtraLesson):
-            day = date_format(register_object.date)
-            day_sort = register_object.date
-            period = f"{register_object.period.period}."
-            period_sort = register_object.period.period
-        else:
-            register_object.annotate_day(register_object.date_end)
-            day = (
-                f"{date_format(register_object.date_start)}"
-                f"–{date_format(register_object.date_end)}"
-            )
-            day_sort = register_object.date_start
-            period = f"{register_object.period_from.period}.–{register_object.period_to.period}."
-            period_sort = register_object.period_from.period
-
-        # Build table entry
-        entry = {
-            "pk": f"{register_object.label_}_{register_object.pk}",
-            "has_documentation": has_documentation,
-            "register_object": register_object,
-            "date": day,
-            "date_sort": day_sort,
-            "period": period,
-            "period_sort": period_sort,
-            "groups": register_object.group_names,
-            "teachers": register_object.teacher_names,
-            "subject": register_object.subject.name
-            if isinstance(register_object, ExtraLesson)
-            else _("Event"),
-        }
-        if has_documentation:
-            doc = filtered_documentations[0]
-            entry["topic"] = doc.topic
-            entry["homework"] = doc.homework
-            entry["group_note"] = doc.group_note
-        register_objects.append(entry)
-
-    return register_objects
-
-
-def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[Dict[str, Any]]:
-    """Generate a list of all register objects.
-
-    This list can be filtered using ``filter_dict``. The following keys are supported:
-    - ``school_term`` (defaults to the current school term)
-    - ``date_start`` and ``date_end`` (defaults to the last thirty days)
-    - ``groups`` and/or ``groups``
-    - ``person``
-    - ``subject``
-    """
-    # Always force a value for school term, start and end date so that queries won't get too big
-    initial_filter_data = FilterRegisterObjectForm.get_initial()
-    filter_dict["school_term"] = filter_dict.get("school_term", initial_filter_data["school_term"])
-
-    # If there is not school year at all, there are definitely no data.
-    if not filter_dict["school_term"]:
-        return []
-
-    filter_dict["date_start"] = filter_dict.get("date_start", initial_filter_data["date_start"])
-    filter_dict["date_end"] = filter_dict.get("date_end", initial_filter_data["date_end"])
-    filter_dict["filter_date"] = bool(filter_dict.get("date_start")) and bool(
-        filter_dict.get("date_end")
-    )
-
-    # Get all holidays in the selected school term to sort all data in holidays out
-    holidays = Holiday.objects.within_dates(
-        filter_dict["school_term"].date_start, filter_dict["school_term"].date_end
-    )
-    holiday_days = holidays.get_all_days()
-
-    lesson_periods = _filter_register_objects_by_dict(
-        filter_dict,
-        LessonPeriod.objects.order_by("lesson__validity__date_start"),
-        LessonPeriod.label_,
-    )
-    events = _filter_register_objects_by_dict(
-        filter_dict, Event.objects.exclude_holidays(holidays), Event.label_
-    )
-    extra_lessons = _filter_register_objects_by_dict(
-        filter_dict, ExtraLesson.objects.exclude_holidays(holidays), ExtraLesson.label_
-    )
-
-    # Prefetch documentations for all register objects and substitutions for all lesson periods
-    # in order to prevent extra queries
-    documentations = LessonDocumentation.objects.not_empty().filter(
-        Q(event__in=events)
-        | Q(extra_lesson__in=extra_lessons)
-        | Q(lesson_period__in=lesson_periods)
-    )
-
-    if lesson_periods:
-        register_objects = _generate_dicts_for_lesson_periods(
-            filter_dict, lesson_periods, documentations, holiday_days
-        )
-        register_objects += _generate_dicts_for_events_and_extra_lessons(
-            filter_dict, list(events) + list(extra_lessons), documentations
-        )
-
-        # Sort table entries by date and period and configure table
-        register_objects = sorted(register_objects, key=itemgetter("date_sort", "period_sort"))
-        return register_objects
-    return []
 
 
 def get_absence_reason_tag():
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
index 5966a7459bc8a32f7e7468f20de2e7da0044441e..2a27bca7e2260a13d12199027d3501add3494b60 100644
--- a/aleksis/apps/alsijil/util/predicates.py
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -1,4 +1,4 @@
-from typing import Any, Union
+from typing import Union
 
 from django.contrib.auth.models import User
 from django.db.models import Q
@@ -6,77 +6,13 @@ from django.utils.timezone import localdate, now
 
 from rules import predicate
 
-from aleksis.apps.chronos.models import Event, ExtraLesson, LessonEvent, LessonPeriod
+from aleksis.apps.chronos.models import LessonEvent
 from aleksis.apps.cursus.models import Course
 from aleksis.core.models import Group, Person
 from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.predicates import check_object_permission
 
-from ..models import Documentation, NewPersonalNote, PersonalNote
-
-
-@predicate
-def is_none(user: User, obj: Any) -> bool:
-    """Predicate that checks if the provided object is None-like."""
-    return not bool(obj)
-
-
-@predicate
-def is_lesson_teacher(user: User, obj: Union[LessonPeriod, Event, ExtraLesson]) -> bool:
-    """Predicate for teachers of a lesson.
-
-    Checks whether the person linked to the user is a teacher in the register object.
-    If the register object is a lesson period and has a substitution linked,
-    this will **only** check if the person is one of the substitution teachers.
-    """
-    if obj:
-        return user.person in obj.get_teachers().all()
-    return False
-
-
-@predicate
-def is_lesson_original_teacher(user: User, obj: Union[LessonPeriod, Event, ExtraLesson]) -> bool:
-    """Predicate for teachers of a lesson.
-
-    Checks whether the person linked to the user is a teacher in the register object.
-    If the register object is a lesson period and has a substitution linked,
-    this will **also** check if the person is one of the substitution teachers.
-    """
-    if obj:
-        if isinstance(obj, LessonPeriod) and user.person in obj.lesson.teachers.all():
-            return True
-        return user.person in obj.get_teachers().all()
-    return False
-
-
-@predicate
-def is_lesson_participant(user: User, obj: LessonPeriod) -> bool:
-    """Predicate for participants of a lesson.
-
-    Checks whether the person linked to the user is a member in
-    the groups linked to the given LessonPeriod.
-    """
-    if hasattr(obj, "lesson") or hasattr(obj, "groups"):
-        for group in obj.get_groups().all():
-            if user.person in list(group.members.all()):
-                return True
-    return False
-
-
-@predicate
-def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool:
-    """
-    Predicate for parent group owners of a lesson.
-
-    Checks whether the person linked to the user is the owner of
-    any parent groups of any groups of the given LessonPeriods lesson.
-    """
-    if hasattr(obj, "lesson") or hasattr(obj, "groups"):
-        for group in obj.get_groups().all():
-            for parent_group in group.parent_groups.all():
-                if user.person in list(parent_group.owners.all()):
-                    return True
-    return False
+from ..models import Documentation, NewPersonalNote
 
 
 @predicate
@@ -130,19 +66,6 @@ def use_prefetched(obj, attr):
     return getattr(obj, attr).all()
 
 
-@predicate
-def is_person_primary_group_owner(user: User, obj: Person) -> bool:
-    """
-    Predicate for group owners of the person's primary group.
-
-    Checks whether the person linked to the user is
-    the owner of the primary group of the given person.
-    """
-    if obj.primary_group:
-        return user.person in use_prefetched(obj.primary_group, "owners")
-    return False
-
-
 def has_person_group_object_perm(perm: str):
     """Predicate builder for permissions on a set of member groups.
 
@@ -171,55 +94,6 @@ def is_group_member(user: User, obj: Union[Group, Person]) -> bool:
     return False
 
 
-def has_lesson_group_object_perm(perm: str):
-    """Predicate builder for permissions on lesson groups.
-
-    Checks whether a user has a permission on any group of a LessonPeriod.
-    """
-    name = f"has_lesson_group_object_perm:{perm}"
-
-    @predicate(name)
-    def fn(user: User, obj: LessonPeriod) -> bool:
-        if hasattr(obj, "lesson"):
-            groups = obj.lesson.groups.all()
-            for group in groups:
-                if check_object_permission(user, perm, group, checker_obj=obj):
-                    return True
-        return False
-
-    return fn
-
-
-def has_personal_note_group_perm(perm: str):
-    """Predicate builder for permissions on personal notes.
-
-    Checks whether a user has a permission on any group of a person of a PersonalNote.
-    """
-    name = f"has_personal_note_person_or_group_perm:{perm}"
-
-    @predicate(name)
-    def fn(user: User, obj: PersonalNote) -> bool:
-        if hasattr(obj, "person"):
-            groups = obj.person.member_of.all()
-            for group in groups:
-                if check_object_permission(user, perm, group, checker_obj=obj):
-                    return True
-        return False
-
-    return fn
-
-
-@predicate
-def is_own_personal_note(user: User, obj: PersonalNote) -> bool:
-    """Predicate for users referred to in a personal note.
-
-    Checks whether the user referred to in a PersonalNote is the active user.
-    """
-    if hasattr(obj, "person") and obj.person is user.person:
-        return True
-    return False
-
-
 @predicate
 def is_parent_group_owner(user: User, obj: Group) -> bool:
     """Predicate which checks whether the user is the owner of any parent group of the group."""
@@ -230,66 +104,6 @@ def is_parent_group_owner(user: User, obj: Group) -> bool:
     return False
 
 
-@predicate
-def is_personal_note_lesson_teacher(user: User, obj: PersonalNote) -> bool:
-    """Predicate for teachers of a register object linked to a personal note.
-
-    Checks whether the person linked to the user is a teacher
-    in the register object linked to the personal note.
-    If the register object is a lesson period and has a substitution linked,
-    this will **only** check if the person is one of the substitution teachers.
-    """
-    if hasattr(obj, "register_object"):
-        return user.person in obj.register_object.get_teachers().all()
-    return False
-
-
-@predicate
-def is_personal_note_lesson_original_teacher(user: User, obj: PersonalNote) -> bool:
-    """Predicate for teachers of a register object linked to a personal note.
-
-    Checks whether the person linked to the user is a teacher
-    in the register object linked to the personal note.
-    If the register object is a lesson period and has a substitution linked,
-    this will **also** check if the person is one of the substitution teachers.
-    """
-    if hasattr(obj, "register_object"):
-        if (
-            isinstance(obj.register_object, LessonPeriod)
-            and user.person in obj.lesson_period.lesson.teachers.all()
-        ):
-            return True
-
-        return user.person in obj.register_object.get_teachers().all()
-    return False
-
-
-@predicate
-def is_personal_note_lesson_parent_group_owner(user: User, obj: PersonalNote) -> bool:
-    """
-    Predicate for parent group owners of a lesson referred to in the lesson of a personal note.
-
-    Checks whether the person linked to the user is the owner of
-    any parent groups of any groups of the given LessonPeriod lesson of the given PersonalNote.
-    If so, also checks whether the person linked to the personal note actually is a member of this
-    parent group.
-    """
-    if hasattr(obj, "register_object"):
-        for group in obj.register_object.get_groups().all():
-            for parent_group in group.parent_groups.all():
-                if user.person in use_prefetched(
-                    parent_group, "owners"
-                ) and obj.person in use_prefetched(parent_group, "members"):
-                    return True
-    return False
-
-
-@predicate
-def is_teacher(user: User, obj: Person) -> bool:
-    """Predicate which checks if the provided object is a teacher."""
-    return user.person.is_teacher
-
-
 @predicate
 def is_group_role_assignment_group_owner(user: User, obj: Union[Group, Person]) -> bool:
     """Predicate for group owners of a group role assignment.
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 0395a8bfbc54fcab81dfea092f431045d15ae134..75f0e1b06d6dfe14380047fe8a6d1221109bcf21 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,36 +1,20 @@
-from contextlib import nullcontext
-from copy import deepcopy
-from datetime import datetime, timedelta
-from typing import Any, Dict, Optional
+from typing import Any, Dict
 
-from django.apps import apps
-from django.core.exceptions import PermissionDenied
-from django.db.models import Count, Exists, FilteredRelation, OuterRef, Prefetch, Q, Sum
-from django.db.models.expressions import Case, When
-from django.db.models.functions import Extract
-from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound
-from django.shortcuts import get_object_or_404, redirect, render
+from django.db.models import Q
+from django.http import HttpRequest, HttpResponse
+from django.shortcuts import get_object_or_404, redirect
 from django.urls import reverse, reverse_lazy
 from django.utils import timezone
 from django.utils.decorators import method_decorator
 from django.utils.http import url_has_allowed_host_and_scheme
 from django.utils.translation import gettext as _
-from django.views import View
 from django.views.decorators.cache import never_cache
 from django.views.generic import DetailView
 
-import reversion
-from calendarweek import CalendarWeek
-from django_tables2 import RequestConfig, SingleTableView
-from guardian.core import ObjectPermissionChecker
-from guardian.shortcuts import get_objects_for_user
+from django_tables2 import SingleTableView
 from reversion.views import RevisionMixin
 from rules.contrib.views import PermissionRequiredMixin, permission_required
 
-from aleksis.apps.chronos.managers import TimetableType
-from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod, TimePeriod
-from aleksis.apps.chronos.util.build import build_weekdays
-from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_date
 from aleksis.core.decorators import pwa_cache
 from aleksis.core.mixins import (
     AdvancedCreateView,
@@ -38,594 +22,23 @@ from aleksis.core.mixins import (
     AdvancedEditView,
     SuccessNextMixin,
 )
-from aleksis.core.models import Group, PDFFile, Person, SchoolTerm
+from aleksis.core.models import Group, PDFFile
 from aleksis.core.util import messages
 from aleksis.core.util.celery_progress import render_progress_page
-from aleksis.core.util.core_helpers import get_site_preferences, has_person, objectgetter_optional
-from aleksis.core.util.predicates import check_global_permission
+from aleksis.core.util.core_helpers import has_person, objectgetter_optional
 
-from .filters import PersonalNoteFilter
 from .forms import (
     AssignGroupRoleForm,
-    ExcuseTypeForm,
-    FilterRegisterObjectForm,
     GroupRoleAssignmentEditForm,
     GroupRoleForm,
-    LessonDocumentationForm,
-    PersonalNoteFormSet,
-    PersonOverviewForm,
-    RegisterAbsenceForm,
-    RegisterObjectActionForm,
-    SelectForm,
 )
-from .models import ExcuseType, ExtraMark, GroupRole, GroupRoleAssignment, PersonalNote
+from .models import GroupRole, GroupRoleAssignment
 from .tables import (
-    ExcuseTypeTable,
     GroupRoleTable,
-    PersonalNoteTable,
-    RegisterObjectSelectTable,
-    RegisterObjectTable,
 )
 from .tasks import generate_full_register_printout
-from .util.alsijil_helpers import (
-    annotate_documentations,
-    generate_list_of_all_register_objects,
-    get_register_object_by_pk,
-    get_timetable_instance_by_pk,
-    register_objects_sorter,
-)
-
-
-@pwa_cache
-@permission_required("alsijil.view_register_object_rule", fn=get_register_object_by_pk)  # FIXME
-def register_object(
-    request: HttpRequest,
-    model: Optional[str] = None,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    id_: Optional[int] = None,
-) -> HttpResponse:
-    context = {}
-
-    register_object = get_register_object_by_pk(request, model, year, week, id_)
-
-    if id_ and model == "lesson":
-        wanted_week = CalendarWeek(year=year, week=week)
-    elif id_ and model == "extra_lesson":
-        wanted_week = register_object.calendar_week
-    elif hasattr(request, "user") and hasattr(request.user, "person"):
-        wanted_week = CalendarWeek()
-    else:
-        wanted_week = None
-
-    if not all((year, week, id_)):
-        if register_object and model == "lesson":
-            return redirect(
-                "lesson_period",
-                wanted_week.year,
-                wanted_week.week,
-                register_object.pk,
-            )
-        elif not register_object:
-            raise Http404(
-                _(
-                    "You either selected an invalid lesson or "
-                    "there is currently no lesson in progress."
-                )
-            )
-
-    date_of_lesson = (
-        week_weekday_to_date(wanted_week, register_object.period.weekday)
-        if not isinstance(register_object, Event)
-        else register_object.date_start
-    )
-    start_time = (
-        register_object.period.time_start
-        if not isinstance(register_object, Event)
-        else register_object.period_from.time_start
-    )
-
-    if isinstance(register_object, Event):
-        register_object.annotate_day(date_of_lesson)
-    if isinstance(register_object, LessonPeriod) and (
-        date_of_lesson < register_object.lesson.validity.date_start
-        or date_of_lesson > register_object.lesson.validity.date_end
-    ):
-        return HttpResponseNotFound()
-
-    if (
-        datetime.combine(date_of_lesson, start_time) > datetime.now()
-        and not (
-            get_site_preferences()["alsijil__open_periods_same_day"]
-            and date_of_lesson <= datetime.now().date()
-        )
-        and not request.user.is_superuser
-    ):
-        raise PermissionDenied(
-            _("You are not allowed to create a lesson documentation for a lesson in the future.")
-        )
-
-    holiday = Holiday.on_day(date_of_lesson)
-    blocked_because_holidays = (
-        holiday is not None and not get_site_preferences()["alsijil__allow_entries_in_holidays"]
-    )
-    context["blocked_because_holidays"] = blocked_because_holidays
-    context["holiday"] = holiday
-
-    next_lesson = (
-        request.user.person.next_lesson(register_object, date_of_lesson)
-        if isinstance(register_object, LessonPeriod)
-        else None
-    )
-    prev_lesson = (
-        request.user.person.previous_lesson(register_object, date_of_lesson)
-        if isinstance(register_object, LessonPeriod)
-        else None
-    )
-    back_url = reverse(
-        "lesson_period", args=[wanted_week.year, wanted_week.week, register_object.pk]
-    )
-    context["back_url"] = back_url
-
-    context["register_object"] = register_object
-    context["week"] = wanted_week
-    context["day"] = date_of_lesson
-    context["next_lesson_person"] = next_lesson
-    context["prev_lesson_person"] = prev_lesson
-    context["prev_lesson"] = (
-        register_object.prev if isinstance(register_object, LessonPeriod) else None
-    )
-    context["next_lesson"] = (
-        register_object.next if isinstance(register_object, LessonPeriod) else None
-    )
-
-    if not blocked_because_holidays:
-        groups = register_object.get_groups().all()
-        if groups:
-            first_group = groups.first()
-            context["first_group"] = first_group
-
-        # Group roles
-        show_group_roles = request.user.person.preferences[
-            "alsijil__group_roles_in_lesson_view"
-        ] and request.user.has_perm(
-            "alsijil.view_assigned_grouproles_for_register_object_rule", register_object
-        )
-        if show_group_roles:
-            group_roles = GroupRole.objects.with_assignments(date_of_lesson, groups)
-            context["group_roles"] = group_roles
-
-        with_seating_plan = (
-            apps.is_installed("aleksis.apps.stoelindeling")
-            and groups
-            and request.user.has_perm("stoelindeling.view_seatingplan_for_group_rule", first_group)
-        )
-        context["with_seating_plan"] = with_seating_plan
-
-        if with_seating_plan:
-            seating_plan = register_object.seating_plan
-            context["seating_plan"] = register_object.seating_plan
-            if seating_plan and seating_plan.group != first_group:
-                context["seating_plan_parent"] = True
-
-        # Create or get lesson documentation object; can be empty when first opening lesson
-        lesson_documentation = register_object.get_or_create_lesson_documentation(wanted_week)
-        context["has_documentation"] = bool(lesson_documentation.topic)
-
-        lesson_documentation_form = LessonDocumentationForm(
-            request.POST or None,
-            instance=lesson_documentation,
-            prefix="lesson_documentation",
-        )
-
-        # Prefetch object permissions for all related groups of the register object
-        # because the object permissions are checked for all groups of the register object
-        # That has to be set as an attribute of the register object,
-        # so that the permission system can use the prefetched data.
-        checker = ObjectPermissionChecker(request.user)
-        checker.prefetch_perms(register_object.get_groups().all())
-        register_object.set_object_permission_checker(checker)
-
-        # Create a formset that holds all personal notes for all persons in this lesson
-        if not request.user.has_perm(
-            "alsijil.view_register_object_personalnote_rule", register_object
-        ):
-            persons = Person.objects.filter(
-                Q(pk=request.user.person.pk) | Q(member_of__in=request.user.person.owner_of.all())
-            ).distinct()
-        else:
-            persons = Person.objects.all()
-
-        persons_qs = register_object.get_personal_notes(persons, wanted_week).distinct()
-
-        # Annotate group roles
-        if show_group_roles:
-            persons_qs = persons_qs.prefetch_related(
-                Prefetch(
-                    "person__group_roles",
-                    queryset=GroupRoleAssignment.objects.on_day(date_of_lesson).for_groups(groups),
-                ),
-            )
-
-        personal_note_formset = PersonalNoteFormSet(
-            request.POST or None, queryset=persons_qs, prefix="personal_notes"
-        )
-
-        if request.method == "POST":
-            if lesson_documentation_form.is_valid() and request.user.has_perm(
-                "alsijil.edit_lessondocumentation_rule", register_object
-            ):
-                with reversion.create_revision():
-                    reversion.set_user(request.user)
-                    lesson_documentation_form.save()
-
-                messages.success(request, _("The lesson documentation has been saved."))
-
-            substitution = (
-                register_object.get_substitution()
-                if isinstance(register_object, LessonPeriod)
-                else None
-            )
-            if (
-                not getattr(substitution, "cancelled", False)
-                or not get_site_preferences()["alsijil__block_personal_notes_for_cancelled"]
-            ):
-                if personal_note_formset.is_valid() and request.user.has_perm(
-                    "alsijil.edit_register_object_personalnote_rule", register_object
-                ):
-                    with reversion.create_revision():
-                        reversion.set_user(request.user)
-                        instances = personal_note_formset.save()
-
-                    if (not isinstance(register_object, Event)) and get_site_preferences()[
-                        "alsijil__carry_over_personal_notes"
-                    ]:
-                        # Iterate over personal notes
-                        # and carry changed absences to following lessons
-                        with reversion.create_revision():
-                            reversion.set_user(request.user)
-                            for instance in instances:
-                                instance.person.mark_absent(
-                                    wanted_week[register_object.period.weekday],
-                                    register_object.period.period + 1,
-                                    instance.absent,
-                                    instance.excused,
-                                    instance.excuse_type,
-                                )
-
-                messages.success(request, _("The personal notes have been saved."))
-
-                # Regenerate form here to ensure that programmatically
-                # changed data will be shown correctly
-                personal_note_formset = PersonalNoteFormSet(
-                    None, queryset=persons_qs, prefix="personal_notes"
-                )
-
-        back_url = request.GET.get("back", "")
-        back_url_is_safe = url_has_allowed_host_and_scheme(
-            url=back_url,
-            allowed_hosts={request.get_host()},
-            require_https=request.is_secure(),
-        )
-        if back_url_is_safe:
-            context["back_to_week_url"] = back_url
-        elif register_object.get_groups().all():
-            context["back_to_week_url"] = reverse(
-                "week_view_by_week",
-                args=[
-                    lesson_documentation.calendar_week.year,
-                    lesson_documentation.calendar_week.week,
-                    "group",
-                    register_object.get_groups().all()[0].pk,
-                ],
-            )
-        context["lesson_documentation"] = lesson_documentation
-        context["lesson_documentation_form"] = lesson_documentation_form
-        context["personal_note_formset"] = personal_note_formset
-
-    return render(request, "alsijil/class_register/lesson.html", context)
-
-
-@pwa_cache
-@permission_required("alsijil.view_week_rule", fn=get_timetable_instance_by_pk)
-def week_view(
-    request: HttpRequest,
-    year: Optional[int] = None,
-    week: Optional[int] = None,
-    type_: Optional[str] = None,
-    id_: Optional[int] = None,
-) -> HttpResponse:
-    context = {}
-
-    wanted_week = CalendarWeek(year=year, week=week) if year and week else CalendarWeek()
 
-    instance = get_timetable_instance_by_pk(request, year, week, type_, id_)
 
-    lesson_periods = LessonPeriod.objects.in_week(wanted_week).prefetch_related(
-        "lesson__groups__members",
-        "lesson__groups__parent_groups",
-        "lesson__groups__parent_groups__owners",
-    )
-    events = Event.objects.in_week(wanted_week)
-    extra_lessons = ExtraLesson.objects.in_week(wanted_week)
-
-    query_exists = True
-    if type_ and id_:
-        if isinstance(instance, HttpResponseNotFound):
-            return HttpResponseNotFound()
-
-        type_ = TimetableType.from_string(type_)
-
-        lesson_periods = lesson_periods.filter_from_type(type_, instance)
-        events = events.filter_from_type(type_, instance)
-        extra_lessons = extra_lessons.filter_from_type(type_, instance)
-
-    elif hasattr(request, "user") and hasattr(request.user, "person"):
-        if request.user.person.lessons_as_teacher.exists():
-            inherit_privileges_preference = get_site_preferences()[
-                "alsijil__inherit_privileges_from_parent_group"
-            ]
-            lesson_periods = (
-                lesson_periods.filter_teacher(request.user.person).union(
-                    lesson_periods.filter_groups(request.user.person.owner_of.all())
-                )
-                if inherit_privileges_preference
-                else lesson_periods.filter_teacher(request.user.person)
-            )
-            events = (
-                events.filter_teacher(request.user.person).union(
-                    events.filter_groups(request.user.person.owner_of.all())
-                )
-                if inherit_privileges_preference
-                else events.filter_teacher(request.user.person)
-            )
-            extra_lessons = (
-                extra_lessons.filter_teacher(request.user.person).union(
-                    extra_lessons.filter_groups(request.user.person.owner_of.all())
-                )
-                if inherit_privileges_preference
-                else extra_lessons.filter_teacher(request.user.person)
-            )
-
-            type_ = TimetableType.TEACHER
-        else:
-            lesson_periods = lesson_periods.filter_participant(request.user.person)
-            events = events.filter_participant(request.user.person)
-            extra_lessons = extra_lessons.filter_participant(request.user.person)
-
-    else:
-        query_exists = False
-        lesson_periods = None
-        events = None
-        extra_lessons = None
-
-    # Add a form to filter the view
-    if type_:
-        initial = {type_.value: instance}
-        back_url = reverse(
-            "week_view_by_week", args=[wanted_week.year, wanted_week.week, type_.value, instance.pk]
-        )
-    else:
-        initial = {}
-        back_url = reverse("week_view_by_week", args=[wanted_week.year, wanted_week.week])
-    context["back_url"] = back_url
-    select_form = SelectForm(request, request.POST or None, initial=initial)
-
-    if request.method == "POST" and select_form.is_valid():
-        if "type_" not in select_form.cleaned_data:
-            return redirect("week_view_by_week", wanted_week.year, wanted_week.week)
-        else:
-            return redirect(
-                "week_view_by_week",
-                wanted_week.year,
-                wanted_week.week,
-                select_form.cleaned_data["type_"].value,
-                select_form.cleaned_data["instance"].pk,
-            )
-
-    group = instance if type_ == TimetableType.GROUP else None
-
-    # Group roles
-    show_group_roles = (
-        group
-        and request.user.person.preferences["alsijil__group_roles_in_week_view"]
-        and request.user.has_perm("alsijil.view_assigned_grouproles_rule", group)
-    )
-    if show_group_roles:
-        group_roles = GroupRole.objects.with_assignments(wanted_week, [group])
-        context["group_roles"] = group_roles
-        group_roles_persons = GroupRoleAssignment.objects.in_week(wanted_week).for_group(group)
-
-    extra_marks = ExtraMark.objects.all()
-
-    if query_exists:
-        lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
-        lesson_periods = annotate_documentations(LessonPeriod, wanted_week, lesson_periods_pk)
-
-        events_pk = [event.pk for event in events]
-        events = annotate_documentations(Event, wanted_week, events_pk)
-
-        extra_lessons_pk = list(extra_lessons.values_list("pk", flat=True))
-        extra_lessons = annotate_documentations(ExtraLesson, wanted_week, extra_lessons_pk)
-        groups = Group.objects.filter(
-            Q(lessons__lesson_periods__in=lesson_periods_pk)
-            | Q(events__in=events_pk)
-            | Q(extra_lessons__in=extra_lessons_pk)
-        )
-    else:
-        lesson_periods_pk = []
-        events_pk = []
-        extra_lessons_pk = []
-
-    if lesson_periods_pk or events_pk or extra_lessons_pk:
-        # Aggregate all personal notes for this group and week
-        persons_qs = Person.objects.all()
-
-        if not request.user.has_perm("alsijil.view_week_personalnote_rule", instance):
-            persons_qs = persons_qs.filter(pk=request.user.person.pk)
-        elif group:
-            persons_qs = (
-                persons_qs.filter(member_of=group)
-                .filter(member_of__in=request.user.person.owner_of.all())
-                .distinct()
-            )
-        else:
-            persons_qs = (
-                persons_qs.filter(member_of__in=groups)
-                .filter(member_of__in=request.user.person.owner_of.all())
-                .distinct()
-            )
-
-        # Prefetch object permissions for persons and groups the persons are members of
-        # because the object permissions are checked for both persons and groups
-        checker = ObjectPermissionChecker(request.user)
-        checker.prefetch_perms(persons_qs.prefetch_related(None))
-        checker.prefetch_perms(groups)
-
-        prefetched_personal_notes = list(
-            PersonalNote.objects.filter(  #
-                Q(event__in=events_pk)
-                | Q(
-                    week=wanted_week.week,
-                    year=wanted_week.year,
-                    lesson_period__in=lesson_periods_pk,
-                )
-                | Q(extra_lesson__in=extra_lessons_pk)
-            ).filter(~Q(remarks=""))
-        )
-        persons_qs = (
-            persons_qs.select_related("primary_group")
-            .prefetch_related(
-                Prefetch(
-                    "primary_group__owners",
-                    queryset=Person.objects.filter(pk=request.user.person.pk),
-                    to_attr="owners_prefetched",
-                ),
-                Prefetch("member_of", queryset=groups, to_attr="member_of_prefetched"),
-            )
-            .annotate(
-                filtered_personal_notes=FilteredRelation(
-                    "personal_notes",
-                    condition=(
-                        Q(personal_notes__event__in=events_pk)
-                        | Q(
-                            personal_notes__week=wanted_week.week,
-                            personal_notes__year=wanted_week.year,
-                            personal_notes__lesson_period__in=lesson_periods_pk,
-                        )
-                        | Q(personal_notes__extra_lesson__in=extra_lessons_pk)
-                    ),
-                )
-            )
-        )
-
-        persons_qs = persons_qs.annotate(
-            absences_count=Count(
-                "filtered_personal_notes",
-                filter=Q(filtered_personal_notes__absent=True),
-            ),
-            unexcused_count=Count(
-                "filtered_personal_notes",
-                filter=Q(
-                    filtered_personal_notes__absent=True, filtered_personal_notes__excused=False
-                ),
-            ),
-            tardiness_sum=Sum("filtered_personal_notes__tardiness"),
-            tardiness_count=Count(
-                "filtered_personal_notes",
-                filter=Q(filtered_personal_notes__tardiness__gt=0),
-            ),
-        )
-
-        for extra_mark in extra_marks:
-            persons_qs = persons_qs.annotate(
-                **{
-                    extra_mark.count_label: Count(
-                        "filtered_personal_notes",
-                        filter=Q(filtered_personal_notes__extra_marks=extra_mark),
-                    )
-                }
-            )
-
-        persons = []
-        for person in persons_qs:
-            personal_notes = []
-            for note in filter(lambda note: note.person_id == person.pk, prefetched_personal_notes):
-                if note.lesson_period:
-                    note.lesson_period.annotate_week(wanted_week)
-                personal_notes.append(note)
-            person.set_object_permission_checker(checker)
-            person_dict = {"person": person, "personal_notes": personal_notes}
-            if show_group_roles:
-                person_dict["group_roles"] = filter(
-                    lambda role: role.person_id == person.pk, group_roles_persons
-                )
-            persons.append(person_dict)
-    else:
-        persons = None
-
-    context["extra_marks"] = extra_marks
-    context["week"] = wanted_week
-    context["weeks"] = get_weeks_for_year(year=wanted_week.year)
-
-    context["lesson_periods"] = lesson_periods
-    context["events"] = events
-    context["extra_lessons"] = extra_lessons
-
-    context["persons"] = persons
-    context["group"] = group
-    context["select_form"] = select_form
-    context["instance"] = instance
-    context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week)
-
-    regrouped_objects = {}
-
-    for register_object in list(lesson_periods) + list(extra_lessons):
-        register_object.weekday = register_object.period.weekday
-        regrouped_objects.setdefault(register_object.period.weekday, [])
-        regrouped_objects[register_object.period.weekday].append(register_object)
-
-    for event in events:
-        weekday_from = event.get_start_weekday(wanted_week)
-        weekday_to = event.get_end_weekday(wanted_week)
-
-        for weekday in range(weekday_from, weekday_to + 1):
-            # Make a copy in order to keep the annotation only on this weekday
-            event_copy = deepcopy(event)
-            event_copy.annotate_day(wanted_week[weekday])
-            event_copy.weekday = weekday
-
-            regrouped_objects.setdefault(weekday, [])
-            regrouped_objects[weekday].append(event_copy)
-
-    # Sort register objects
-    for weekday in regrouped_objects:
-        to_sort = regrouped_objects[weekday]
-        regrouped_objects[weekday] = sorted(to_sort, key=register_objects_sorter)
-    context["regrouped_objects"] = regrouped_objects
-
-    week_prev = wanted_week - 1
-    week_next = wanted_week + 1
-    args_prev = [week_prev.year, week_prev.week]
-    args_next = [week_next.year, week_next.week]
-    args_dest = []
-    if type_ and id_:
-        args_prev += [type_.value, id_]
-        args_next += [type_.value, id_]
-        args_dest += [type_.value, id_]
-
-    context["week_select"] = {
-        "year": wanted_week.year,
-        "dest": reverse("week_view_placeholders", args=args_dest),
-    }
-
-    context["url_prev"] = reverse("week_view_by_week", args=args_prev)
-    context["url_next"] = reverse("week_view_by_week", args=args_next)
-
-    return render(request, "alsijil/class_register/week_view.html", context)
-
-
-@pwa_cache
 @permission_required(
     "alsijil.view_full_register_rule", fn=objectgetter_optional(Group, None, False)
 )
@@ -665,436 +78,6 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     )
 
 
-@pwa_cache
-@permission_required("alsijil.view_my_students_rule")
-def my_students(request: HttpRequest) -> HttpResponse:
-    context = {}
-    relevant_groups = (
-        request.user.person.get_owner_groups_with_lessons()
-        .annotate(has_parents=Exists(Group.objects.filter(child_groups=OuterRef("pk"))))
-        .filter(members__isnull=False)
-        .order_by("has_parents", "name")
-        .prefetch_related("members")
-        .distinct()
-    )
-
-    # Prefetch object permissions for persons and groups the persons are members of
-    # because the object permissions are checked for both persons and groups
-    all_persons = Person.objects.filter(member_of__in=relevant_groups)
-    checker = ObjectPermissionChecker(request.user)
-    checker.prefetch_perms(relevant_groups)
-    checker.prefetch_perms(all_persons)
-
-    new_groups = []
-    for group in relevant_groups:
-        persons = group.generate_person_list_with_class_register_statistics(
-            group.members.prefetch_related(
-                "primary_group__owners",
-                Prefetch("member_of", queryset=relevant_groups, to_attr="member_of_prefetched"),
-            )
-        ).distinct()
-        persons_for_group = []
-        for person in persons:
-            person.set_object_permission_checker(checker)
-            persons_for_group.append(person)
-        new_groups.append((group, persons_for_group))
-
-    context["groups"] = new_groups
-    context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
-    context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
-    context["extra_marks"] = ExtraMark.objects.all()
-    return render(request, "alsijil/class_register/persons.html", context)
-
-
-@pwa_cache
-@permission_required(
-    "alsijil.view_my_groups_rule",
-)
-def my_groups(request: HttpRequest) -> HttpResponse:
-    context = {}
-    context["groups"] = request.user.person.get_owner_groups_with_lessons().annotate(
-        students_count=Count("members", distinct=True)
-    )
-    return render(request, "alsijil/class_register/groups.html", context)
-
-
-@method_decorator(pwa_cache, "dispatch")
-class StudentsList(PermissionRequiredMixin, DetailView):
-    model = Group
-    template_name = "alsijil/class_register/students_list.html"
-    permission_required = "alsijil.view_students_list_rule"
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context["group"] = self.object
-        context["persons"] = (
-            self.object.generate_person_list_with_class_register_statistics()
-            .filter(member_of__in=self.request.user.person.owner_of.all())
-            .distinct()
-        )
-        context["extra_marks"] = ExtraMark.objects.all()
-        context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
-        context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
-        return context
-
-
-@pwa_cache
-@permission_required(
-    "alsijil.view_person_overview_rule",
-    fn=objectgetter_optional(
-        Person.objects.prefetch_related("member_of__owners"), "request.user.person", True
-    ),
-)
-def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
-    context = {}
-    person = objectgetter_optional(
-        Person.objects.prefetch_related("member_of__owners"),
-        default="request.user.person",
-        default_eval=True,
-    )(request, id_)
-    context["person"] = person
-
-    person_personal_notes = (
-        person.personal_notes.all()
-        .prefetch_related(
-            "lesson_period__lesson__groups",
-            "lesson_period__lesson__teachers",
-            "lesson_period__substitutions",
-        )
-        .annotate_date_range()
-    )
-
-    # Prefetch object permissions for groups the person is a member of
-    # because the object permissions are checked for all groups the person is a member of
-    # That has to be set as an attribute of the register object,
-    # so that the permission system can use the prefetched data.
-    checker = ObjectPermissionChecker(request.user)
-    checker.prefetch_perms(Group.objects.filter(members=person))
-    person.set_object_permission_checker(checker)
-
-    if request.user.has_perm("alsijil.view_person_overview_personalnote_rule", person):
-        allowed_personal_notes = person_personal_notes.all()
-    else:
-        allowed_personal_notes = person_personal_notes.filter(
-            Q(lesson_period__lesson__groups__owners=request.user.person)
-            | Q(extra_lesson__groups__owners=request.user.person)
-            | Q(event__groups__owners=request.user.person)
-        )
-
-    unexcused_absences = allowed_personal_notes.filter(absent=True, excused=False)
-    context["unexcused_absences"] = unexcused_absences
-
-    personal_notes = (
-        allowed_personal_notes.not_empty()
-        .filter(Q(absent=True) | Q(tardiness__gt=0) | ~Q(remarks="") | Q(extra_marks__isnull=False))
-        .annotate(
-            school_term_start=Case(
-                When(event__isnull=False, then="event__school_term__date_start"),
-                When(extra_lesson__isnull=False, then="extra_lesson__school_term__date_start"),
-                When(
-                    lesson_period__isnull=False,
-                    then="lesson_period__lesson__validity__school_term__date_start",
-                ),
-            ),
-            order_year=Case(
-                When(event__isnull=False, then=Extract("event__date_start", "year")),
-                When(extra_lesson__isnull=False, then="extra_lesson__year"),
-                When(lesson_period__isnull=False, then="year"),
-            ),
-            order_week=Case(
-                When(event__isnull=False, then=Extract("event__date_start", "week")),
-                When(extra_lesson__isnull=False, then="extra_lesson__week"),
-                When(lesson_period__isnull=False, then="week"),
-            ),
-            order_weekday=Case(
-                When(event__isnull=False, then="event__period_from__weekday"),
-                When(extra_lesson__isnull=False, then="extra_lesson__period__weekday"),
-                When(lesson_period__isnull=False, then="lesson_period__period__weekday"),
-            ),
-            order_period=Case(
-                When(event__isnull=False, then="event__period_from__period"),
-                When(extra_lesson__isnull=False, then="extra_lesson__period__period"),
-                When(lesson_period__isnull=False, then="lesson_period__period__period"),
-            ),
-            order_groups=Case(
-                When(event__isnull=False, then="event__groups"),
-                When(extra_lesson__isnull=False, then="extra_lesson__groups"),
-                When(lesson_period__isnull=False, then="lesson_period__lesson__groups"),
-            ),
-            order_teachers=Case(
-                When(event__isnull=False, then="event__teachers"),
-                When(extra_lesson__isnull=False, then="extra_lesson__teachers"),
-                When(lesson_period__isnull=False, then="lesson_period__lesson__teachers"),
-            ),
-        )
-        .order_by(
-            "-school_term_start",
-            "-order_year",
-            "-order_week",
-            "-order_weekday",
-            "order_period",
-        )
-        .annotate_date_range()
-        .annotate_subject()
-    )
-
-    personal_note_filter_object = PersonalNoteFilter(request.GET, queryset=personal_notes)
-    filtered_personal_notes = personal_note_filter_object.qs
-    context["personal_note_filter_form"] = personal_note_filter_object.form
-
-    used_filters = list(personal_note_filter_object.data.values())
-    context["num_filters"] = (
-        len(used_filters) - used_filters.count("") - used_filters.count("unknown")
-    )
-
-    personal_notes_list = []
-    for note in personal_notes:
-        note.set_object_permission_checker(checker)
-        personal_notes_list.append(note)
-    context["personal_notes"] = personal_notes_list
-    context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
-    context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
-
-    form = PersonOverviewForm(request, request.POST or None, queryset=allowed_personal_notes)
-    if (
-        request.method == "POST"
-        and request.user.has_perm("alsijil.edit_person_overview_personalnote_rule", person)
-        and form.is_valid()
-    ):
-        with reversion.create_revision():
-            reversion.set_user(request.user)
-            form.execute()
-        person.refresh_from_db()
-    context["action_form"] = form
-
-    table = PersonalNoteTable(filtered_personal_notes)
-    RequestConfig(request, paginate={"per_page": 20}).configure(table)
-    context["personal_notes_table"] = table
-
-    extra_marks = ExtraMark.objects.all()
-    excuse_types = ExcuseType.objects.all()
-    if request.user.has_perm("alsijil.view_person_statistics_personalnote_rule", person):
-        school_terms = SchoolTerm.objects.all().order_by("-date_start")
-        stats = []
-        for school_term in school_terms:
-            stat = {}
-            personal_notes = PersonalNote.objects.filter(
-                person=person,
-            ).filter(
-                Q(lesson_period__lesson__validity__school_term=school_term)
-                | Q(extra_lesson__school_term=school_term)
-                | Q(event__school_term=school_term)
-            )
-
-            if not personal_notes.exists():
-                continue
-
-            stat.update(
-                personal_notes.filter(absent=True)
-                .exclude(excuse_type__count_as_absent=False)
-                .aggregate(absences_count=Count("absent"))
-            )
-            stat.update(
-                personal_notes.filter(absent=True, excused=True)
-                .exclude(excuse_type__count_as_absent=False)
-                .aggregate(excused=Count("absent"))
-            )
-            stat.update(
-                personal_notes.filter(absent=True, excused=True, excuse_type__isnull=True)
-                .exclude(excuse_type__count_as_absent=False)
-                .aggregate(excused_no_excuse_type=Count("absent"))
-            )
-            stat.update(
-                personal_notes.filter(absent=True, excused=False).aggregate(
-                    unexcused=Count("absent")
-                )
-            )
-            stat.update(personal_notes.aggregate(tardiness=Sum("tardiness")))
-            stat.update(
-                personal_notes.filter(~Q(tardiness=0)).aggregate(tardiness_count=Count("tardiness"))
-            )
-
-            for extra_mark in extra_marks:
-                stat.update(
-                    personal_notes.filter(extra_marks=extra_mark).aggregate(
-                        **{extra_mark.count_label: Count("pk")}
-                    )
-                )
-
-            for excuse_type in excuse_types:
-                stat.update(
-                    personal_notes.filter(absent=True, excuse_type=excuse_type).aggregate(
-                        **{excuse_type.count_label: Count("absent")}
-                    )
-                )
-
-            stats.append((school_term, stat))
-        context["stats"] = stats
-
-    context["extra_marks"] = extra_marks
-
-    # Build filter with own form and logic as django-filter can't work with different models
-    if request.user.person.preferences["alsijil__default_lesson_documentation_filter"]:
-        default_documentation = False
-    else:
-        default_documentation = None
-
-    filter_form = FilterRegisterObjectForm(
-        request, request.GET or None, for_person=True, default_documentation=default_documentation
-    )
-    filter_dict = (
-        filter_form.cleaned_data
-        if filter_form.is_valid()
-        else {"has_documentation": default_documentation}
-    )
-    filter_dict["person"] = person
-    context["filter_form"] = filter_form
-
-    if person.is_teacher:
-        register_objects = generate_list_of_all_register_objects(filter_dict)
-        table = RegisterObjectTable(register_objects)
-        items_per_page = request.user.person.preferences[
-            "alsijil__register_objects_table_items_per_page"
-        ]
-        RequestConfig(request, paginate={"per_page": items_per_page}).configure(table)
-        context["register_object_table"] = table
-    return render(request, "alsijil/class_register/person.html", context)
-
-
-@never_cache
-@permission_required("alsijil.register_absence_rule", fn=objectgetter_optional(Person))
-def register_absence(request: HttpRequest, id_: int = None) -> HttpResponse:
-    context = {}
-
-    person = get_object_or_404(Person, pk=id_) if id_ else None
-
-    register_absence_form = RegisterAbsenceForm(
-        request, request.POST or None, initial={"person": person}
-    )
-
-    if (
-        request.method == "POST"
-        and register_absence_form.is_valid()
-        and request.user.has_perm("alsijil.register_absence_rule", person)
-    ):
-        confirmed = request.POST.get("confirmed", "0") == "1"
-
-        # Get data from form
-        person = register_absence_form.cleaned_data["person"]
-        start_date = register_absence_form.cleaned_data["date_start"]
-        end_date = register_absence_form.cleaned_data["date_end"]
-        from_period = register_absence_form.cleaned_data["from_period"]
-        to_period = register_absence_form.cleaned_data["to_period"]
-        absent = register_absence_form.cleaned_data["absent"]
-        excused = register_absence_form.cleaned_data["excused"]
-        excuse_type = register_absence_form.cleaned_data["excuse_type"]
-        remarks = register_absence_form.cleaned_data["remarks"]
-
-        # Mark person as absent
-        affected_count = 0
-        delta = end_date - start_date
-        for i in range(delta.days + 1):
-            from_period_on_day = from_period if i == 0 else TimePeriod.period_min
-            to_period_on_day = to_period if i == delta.days else TimePeriod.period_max
-            day = start_date + timedelta(days=i)
-
-            # Skip holidays if activated
-            if not get_site_preferences()["alsijil__allow_entries_in_holidays"]:
-                holiday = Holiday.on_day(day)
-                if holiday:
-                    continue
-
-            with reversion.create_revision() if confirmed else nullcontext():
-                affected_count += person.mark_absent(
-                    day,
-                    from_period_on_day,
-                    absent,
-                    excused,
-                    excuse_type,
-                    remarks,
-                    to_period_on_day,
-                    dry_run=not confirmed,
-                )
-
-        if not confirmed:
-            # Show confirmation page
-            context = {}
-            context["affected_lessons"] = affected_count
-            context["person"] = person
-            context["form_data"] = register_absence_form.cleaned_data
-            context["form"] = register_absence_form
-            return render(request, "alsijil/absences/register_confirm.html", context)
-        else:
-            messages.success(request, _("The absence has been saved."))
-            return redirect("overview_person", person.pk)
-
-    context["person"] = person
-    context["register_absence_form"] = register_absence_form
-
-    return render(request, "alsijil/absences/register.html", context)
-
-
-@method_decorator(never_cache, name="dispatch")
-class DeletePersonalNoteView(PermissionRequiredMixin, DetailView):
-    model = PersonalNote
-    template_name = "core/pages/delete.html"
-    permission_required = "alsijil.edit_personalnote_rule"
-
-    def post(self, request, *args, **kwargs):
-        note = self.get_object()
-        with reversion.create_revision():
-            reversion.set_user(request.user)
-            note.reset_values()
-            note.save()
-        messages.success(request, _("The personal note has been deleted."))
-        return redirect("overview_person", note.person.pk)
-
-
-@method_decorator(pwa_cache, "dispatch")
-class ExcuseTypeListView(PermissionRequiredMixin, SingleTableView):
-    """Table of all excuse types."""
-
-    model = ExcuseType
-    table_class = ExcuseTypeTable
-    permission_required = "alsijil.view_excusetypes_rule"
-    template_name = "alsijil/excuse_type/list.html"
-
-
-@method_decorator(never_cache, name="dispatch")
-class ExcuseTypeCreateView(PermissionRequiredMixin, AdvancedCreateView):
-    """Create view for excuse types."""
-
-    model = ExcuseType
-    form_class = ExcuseTypeForm
-    permission_required = "alsijil.add_excusetype_rule"
-    template_name = "alsijil/excuse_type/create.html"
-    success_url = reverse_lazy("excuse_types")
-    success_message = _("The excuse type has been created.")
-
-
-@method_decorator(never_cache, name="dispatch")
-class ExcuseTypeEditView(PermissionRequiredMixin, AdvancedEditView):
-    """Edit view for excuse types."""
-
-    model = ExcuseType
-    form_class = ExcuseTypeForm
-    permission_required = "alsijil.edit_excusetype_rule"
-    template_name = "alsijil/excuse_type/edit.html"
-    success_url = reverse_lazy("excuse_types")
-    success_message = _("The excuse type has been saved.")
-
-
-@method_decorator(never_cache, "dispatch")
-class ExcuseTypeDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView):
-    """Delete view for excuse types."""
-
-    model = ExcuseType
-    permission_required = "alsijil.delete_excusetype_rule"
-    template_name = "core/pages/delete.html"
-    success_url = reverse_lazy("excuse_types")
-    success_message = _("The excuse type has been deleted.")
-
-
 @method_decorator(pwa_cache, "dispatch")
 class GroupRoleListView(PermissionRequiredMixin, SingleTableView):
     """Table of all group roles."""
@@ -1261,52 +244,3 @@ class GroupRoleAssignmentDeleteView(
     def get_success_url(self) -> str:
         pk = self.object.groups.first().pk
         return reverse("assigned_group_roles", args=[pk])
-
-
-@method_decorator(pwa_cache, "dispatch")
-class AllRegisterObjectsView(PermissionRequiredMixin, View):
-    """Provide overview of all register objects for coordinators."""
-
-    permission_required = "alsijil.view_register_objects_list_rule"
-
-    def get_context_data(self, request):
-        context = {}
-        # Filter selectable groups by permissions
-        groups = Group.objects.all()
-        if not check_global_permission(request.user, "alsijil.view_full_register"):
-            allowed_groups = get_objects_for_user(
-                self.request.user, "core.view_full_register_group", Group
-            ).values_list("pk", flat=True)
-            groups = groups.filter(Q(parent_groups__in=allowed_groups) | Q(pk__in=allowed_groups))
-
-        # Build filter with own form and logic as django-filter can't work with different models
-        filter_form = FilterRegisterObjectForm(
-            request, request.GET or None, for_person=False, groups=groups
-        )
-        filter_dict = filter_form.cleaned_data if filter_form.is_valid() else {}
-        filter_dict["groups"] = groups
-        context["filter_form"] = filter_form
-
-        register_objects = generate_list_of_all_register_objects(filter_dict)
-
-        self.action_form = RegisterObjectActionForm(request, register_objects, request.POST or None)
-        context["action_form"] = self.action_form
-
-        if register_objects:
-            self.table = RegisterObjectSelectTable(register_objects)
-            items_per_page = request.user.person.preferences[
-                "alsijil__register_objects_table_items_per_page"
-            ]
-            RequestConfig(request, paginate={"per_page": items_per_page}).configure(self.table)
-            context["table"] = self.table
-        return context
-
-    def get(self, request: HttpRequest) -> HttpResponse:
-        context = self.get_context_data(request)
-        return render(request, "alsijil/class_register/all_objects.html", context)
-
-    def post(self, request: HttpRequest):
-        context = self.get_context_data(request)
-        if self.action_form.is_valid():
-            self.action_form.execute()
-        return render(request, "alsijil/class_register/all_objects.html", context)