diff --git a/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py b/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py
new file mode 100644
index 0000000000000000000000000000000000000000..34611bf386a55de933219217c7fabb0709f2f14e
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0010_events_extra_lessons.py
@@ -0,0 +1,91 @@
+# Generated by Django 3.1.5 on 2021-01-10 15:48
+
+import aleksis.apps.chronos.util.date
+import django.contrib.sites.managers
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0004_substitution_extra_lesson_year'),
+        ('alsijil', '0009_group_roles'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='lessondocumentation',
+            options={'ordering': ['year', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period'], 'verbose_name': 'Lesson documentation', 'verbose_name_plural': 'Lesson documentations'},
+        ),
+        migrations.AlterModelOptions(
+            name='personalnote',
+            options={'ordering': ['year', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period', 'person__last_name', 'person__first_name'], 'verbose_name': 'Personal note', 'verbose_name_plural': 'Personal notes'},
+        ),
+        migrations.AddField(
+            model_name='lessondocumentation',
+            name='event',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documentations', to='chronos.event'),
+        ),
+        migrations.AddField(
+            model_name='lessondocumentation',
+            name='extra_lesson',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documentations', to='chronos.extralesson'),
+        ),
+        migrations.AddField(
+            model_name='personalnote',
+            name='event',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='chronos.event'),
+        ),
+        migrations.AddField(
+            model_name='personalnote',
+            name='extra_lesson',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='chronos.extralesson'),
+        ),
+        migrations.AlterField(
+            model_name='lessondocumentation',
+            name='lesson_period',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documentations', to='chronos.lessonperiod'),
+        ),
+        migrations.AlterField(
+            model_name='lessondocumentation',
+            name='week',
+            field=models.IntegerField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='lessondocumentation',
+            name='year',
+            field=models.IntegerField(blank=True, default=aleksis.apps.chronos.util.date.get_current_year, null=True, verbose_name='Year'),
+        ),
+        migrations.AlterField(
+            model_name='personalnote',
+            name='lesson_period',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='chronos.lessonperiod'),
+        ),
+        migrations.AlterField(
+            model_name='personalnote',
+            name='week',
+            field=models.IntegerField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='personalnote',
+            name='year',
+            field=models.IntegerField(blank=True, default=aleksis.apps.chronos.util.date.get_current_year, null=True, verbose_name='Year'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='lessondocumentation',
+            unique_together=set(),
+        ),
+        migrations.AlterUniqueTogether(
+            name='personalnote',
+            unique_together=set(),
+        ),
+        migrations.AddConstraint(
+            model_name='lessondocumentation',
+            constraint=models.CheckConstraint(check=models.Q(models.Q(('event__isnull', True), ('extra_lesson__isnull', True), ('lesson_period__isnull', False), ('week__isnull', False), ('year__isnull', False)), models.Q(('event__isnull', False), ('extra_lesson__isnull', True), ('lesson_period__isnull', True), ('week__isnull', True), ('year__isnull', True)), models.Q(('event__isnull', True), ('extra_lesson__isnull', False), ('lesson_period__isnull', True), ('week__isnull', True), ('year__isnull', True)), _connector='OR'), name='one_relation_only_lesson_documentation'),
+        ),
+        migrations.AddConstraint(
+            model_name='personalnote',
+            constraint=models.CheckConstraint(check=models.Q(models.Q(('event__isnull', True), ('extra_lesson__isnull', True), ('lesson_period__isnull', False), ('week__isnull', False), ('year__isnull', False)), models.Q(('event__isnull', False), ('extra_lesson__isnull', True), ('lesson_period__isnull', True), ('week__isnull', True), ('year__isnull', True)), models.Q(('event__isnull', True), ('extra_lesson__isnull', False), ('lesson_period__isnull', True), ('week__isnull', True), ('year__isnull', True)), _connector='OR'), name='one_relation_only_personal_note'),
+        ),
+    ]
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index fdd05fd4bf3a867c98914b17ace921c65ffe052b..d7ebda3a49deadf724d18476d3c4dd9ab55abee5 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -3,16 +3,43 @@ from typing import Dict, Iterable, Iterator, Optional, Union
 
 from django.db.models import Exists, OuterRef, Q, QuerySet
 from django.db.models.aggregates import Count, Sum
+from django.db.models.expressions import Subquery
+from django.urls import reverse
 from django.utils.translation import gettext as _
 
 from calendarweek import CalendarWeek
 
-from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.apps.alsijil.managers import PersonalNoteQuerySet
+from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.core.models import Group, Person
 
 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,
@@ -27,8 +54,8 @@ def mark_absent(
 ):
     """Mark a person absent for all lessons in a day, optionally starting with a selected period number.
 
-    This function creates `PersonalNote` objects for every `LessonPeriod` the person
-    participates in on the selected day and marks them as absent/excused.
+    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
@@ -49,14 +76,28 @@ def mark_absent(
         .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 lesson_period in lesson_periods:
-            sub = lesson_period.get_substitution()
+        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
 
@@ -65,10 +106,8 @@ def mark_absent(
                 .prefetch_related(None)
                 .update_or_create(
                     person=self,
-                    lesson_period=lesson_period,
-                    week=wanted_week.week,
-                    year=wanted_week.year,
                     defaults={"absent": absent, "excused": excused, "excuse_type": excuse_type,},
+                    **q_attrs,
                 )
             )
             personal_note.groups_of_person.set(self.member_of.all())
@@ -80,14 +119,18 @@ def mark_absent(
                     personal_note.remarks = remarks
                 personal_note.save()
 
-    return lesson_periods.count()
+    return lesson_periods.count() + extra_lessons.count()
 
 
-@LessonPeriod.method
-def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
-    """Get all personal notes for that lesson in a specified week.
+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.
 
-    Returns all linked `PersonalNote` objects, filtered by the given weeek,
+    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.
@@ -97,37 +140,30 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
         - 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(
-                week=wanted_week.week,
-                year=wanted_week.year,
-                lesson_period=self,
-                person__pk=OuterRef("pk"),
-            )
-        )
+        no_personal_notes=~Exists(PersonalNote.objects.filter(person__pk=OuterRef("pk"), **q_attrs))
     ).filter(
-        member_of__in=Group.objects.filter(pk__in=self.lesson.groups.all()),
+        member_of__in=Group.objects.filter(pk__in=self.get_groups().all()),
         is_active=True,
         no_personal_notes=True,
     )
 
     # Create all missing personal notes
-    new_personal_notes = [
-        PersonalNote(
-            person=person, lesson_period=self, week=wanted_week.week, year=wanted_week.year,
-        )
-        for person in missing_persons
-    ]
+    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(
-            lesson_period=self, week=wanted_week.week, year=wanted_week.year, person__in=persons,
-        )
+        PersonalNote.objects.filter(**q_attrs, person__in=persons)
         .select_related(None)
         .prefetch_related(None)
         .select_related("person", "excuse_type")
@@ -135,6 +171,10 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
     )
 
 
+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
 Group.add_permission(
@@ -175,6 +215,19 @@ def get_lesson_documentation(
         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
@@ -182,12 +235,24 @@ def get_or_create_lesson_documentation(
     """Get or create lesson documentation object for this lesson."""
     if not week:
         week = self.week
-    lesson_documentation, created = LessonDocumentation.objects.get_or_create(
+    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."""
@@ -200,6 +265,15 @@ def get_absences(self, week: Optional[CalendarWeek] = None) -> Iterator:
     )
 
 
+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."""
@@ -208,6 +282,15 @@ def get_excused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet:
     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."""
@@ -216,6 +299,15 @@ def get_unexcused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySe
     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."""
@@ -224,6 +316,15 @@ def get_tardinesses(self, week: Optional[CalendarWeek] = None) -> QuerySet:
     return self.personal_notes.filter(week=week.week, year=week.year, late__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(late__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."""
@@ -239,6 +340,21 @@ def get_extra_marks(self, week: Optional[CalendarWeek] = None) -> Dict[ExtraMark
     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."""
@@ -270,62 +386,59 @@ def generate_person_list_with_class_register_statistics(
     if persons is None:
         persons = self.members.all()
 
-    persons = persons.filter(
-        personal_notes__groups_of_person=self,
-        personal_notes__lesson_period__lesson__validity__school_term=self.school_term,
-    ).distinct()
+    # Build reusable Q objects for filtering by school term and by groups
+    # Necessary for the following annotations
+    school_term_q = (
+        Q(personal_notes__lesson_period__lesson__validity__school_term=self.school_term)
+        | Q(personal_notes__extra_lesson__school_term=self.school_term)
+        | Q(personal_notes__event__school_term=self.school_term)
+    )
+    groups_q = (
+        Q(personal_notes__lesson_period__lesson__groups=self)
+        | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
+        | Q(personal_notes__extra_lesson__groups=self)
+        | Q(personal_notes__extra_lesson__groups__parent_groups=self)
+        | Q(personal_notes__event__groups=self)
+        | Q(personal_notes__event__groups__parent_groups=self)
+    )
+
+    persons = persons.filter(personal_notes__groups_of_person=self).filter(school_term_q).distinct()
+
     persons = persons.annotate(
         absences_count=Count(
-            "personal_notes__absent",
-            filter=Q(
-                personal_notes__absent=True,
-                personal_notes__lesson_period__lesson__validity__school_term=self.school_term,
-            )
-            & (
-                Q(personal_notes__lesson_period__lesson__groups=self)
-                | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-            ),
+            "personal_notes",
+            filter=Q(personal_notes__absent=True) & school_term_q & groups_q,
+            distinct=True,
         ),
         excused=Count(
-            "personal_notes__absent",
+            "personal_notes",
             filter=Q(
                 personal_notes__absent=True,
                 personal_notes__excused=True,
                 personal_notes__excuse_type__isnull=True,
-                personal_notes__lesson_period__lesson__validity__school_term=self.school_term,
             )
-            & (
-                Q(personal_notes__lesson_period__lesson__groups=self)
-                | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-            ),
+            & school_term_q
+            & groups_q,
+            distinct=True,
         ),
         unexcused=Count(
-            "personal_notes__absent",
-            filter=Q(
-                personal_notes__absent=True,
-                personal_notes__excused=False,
-                personal_notes__lesson_period__lesson__validity__school_term=self.school_term,
-            )
-            & (
-                Q(personal_notes__lesson_period__lesson__groups=self)
-                | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-            ),
+            "personal_notes",
+            filter=Q(personal_notes__absent=True, personal_notes__excused=False)
+            & school_term_q
+            & groups_q,
+            distinct=True,
         ),
-        tardiness=Sum(
-            "personal_notes__late",
-            filter=(
-                Q(personal_notes__lesson_period__lesson__groups=self)
-                | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-            ),
+        tardiness=Subquery(
+            Person.objects.filter(school_term_q & groups_q)
+            .filter(pk=OuterRef("pk"),)
+            .distinct()
+            .annotate(tardiness=Sum("personal_notes__late"))
+            .values("tardiness")
         ),
         tardiness_count=Count(
             "personal_notes",
-            filter=~Q(personal_notes__late=0)
-            & Q(personal_notes__lesson_period__lesson__validity__school_term=self.school_term,)
-            & (
-                Q(personal_notes__lesson_period__lesson__groups=self)
-                | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-            ),
+            filter=~Q(personal_notes__late=0) & school_term_q & groups_q,
+            distinct=True,
         ),
     )
 
@@ -334,14 +447,7 @@ def generate_person_list_with_class_register_statistics(
             **{
                 extra_mark.count_label: Count(
                     "personal_notes",
-                    filter=Q(
-                        personal_notes__extra_marks=extra_mark,
-                        personal_notes__lesson_period__lesson__validity__school_term=self.school_term,  # noqa
-                    )
-                    & (
-                        Q(personal_notes__lesson_period__lesson__groups=self)
-                        | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-                    ),
+                    filter=Q(personal_notes__extra_marks=extra_mark) & school_term_q & groups_q,
                 )
             }
         )
@@ -351,15 +457,9 @@ def generate_person_list_with_class_register_statistics(
             **{
                 excuse_type.count_label: Count(
                     "personal_notes__absent",
-                    filter=Q(
-                        personal_notes__absent=True,
-                        personal_notes__excuse_type=excuse_type,
-                        personal_notes__lesson_period__lesson__validity__school_term=self.school_term,  # noqa
-                    )
-                    & (
-                        Q(personal_notes__lesson_period__lesson__groups=self)
-                        | Q(personal_notes__lesson_period__lesson__groups__parent_groups=self)
-                    ),
+                    filter=Q(personal_notes__absent=True, personal_notes__excuse_type=excuse_type,)
+                    & school_term_q
+                    & groups_q,
                 )
             }
         )
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index a0447a17423e487a68dbf57a80cc9ca15b0a54cc..163ef2b8636b25b9cbb3ec7f7d4e5f4d0e9926eb 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,5 +1,10 @@
+from datetime import date
+from typing import Optional, Union
+from urllib.parse import urlparse
+
 from django.db import models
-from django.urls import reverse
+from django.db.models.constraints import CheckConstraint
+from django.db.models.query_utils import Q
 from django.utils.formats import date_format
 from django.utils.translation import gettext_lazy as _
 
@@ -25,9 +30,9 @@ from aleksis.apps.alsijil.managers import (
 )
 from aleksis.apps.chronos.managers import GroupPropertiesMixin
 from aleksis.apps.chronos.mixins import WeekRelatedMixin
-from aleksis.apps.chronos.models import LessonPeriod
-from aleksis.apps.chronos.util.date import get_current_year
+from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.core.mixins import ExtensibleModel
+from aleksis.core.models import SchoolTerm
 from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.model_helpers import ICONS
 
@@ -58,7 +63,104 @@ class ExcuseType(ExtensibleModel):
         verbose_name_plural = _("Excuse types")
 
 
-class PersonalNote(ExtensibleModel, WeekRelatedMixin):
+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 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)}"
+        )
+
+    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
@@ -77,11 +179,17 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
     person = models.ForeignKey("core.Person", models.CASCADE, related_name="personal_notes")
     groups_of_person = models.ManyToManyField("core.Group", related_name="+")
 
-    week = models.IntegerField()
-    year = models.IntegerField(verbose_name=_("Year"), default=get_current_year)
+    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"
+        "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)
@@ -119,21 +227,16 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
         self.remarks = defaults.remarks
         self.extra_marks.clear()
 
-    def __str__(self):
-        return f"{date_format(self.date)}, {self.lesson_period}, {self.person}"
+    def __str__(self) -> str:
+        return f"{self.date_formatted}, {self.lesson_period}, {self.person}"
 
-    def get_absolute_url(self):
-        return (
-            reverse(
-                "lesson_by_week_and_period", args=[self.year, self.week, self.lesson_period.pk],
-            )
-            + "#personal-notes"
-        )
+    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")
-        unique_together = [["lesson_period", "week", "person"]]
         ordering = [
             "year",
             "week",
@@ -142,9 +245,14 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
             "person__last_name",
             "person__first_name",
         ]
+        constraints = [
+            CheckConstraint(
+                check=lesson_related_constraint_q, name="one_relation_only_personal_note"
+            )
+        ]
 
 
-class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
+class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel):
     """A documentation on a single lesson period.
 
     Non-personal, includes the topic and homework of the lesson.
@@ -154,11 +262,17 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
 
     data_checks = [LessonDocumentationOnHolidaysDataCheck]
 
-    week = models.IntegerField()
-    year = models.IntegerField(verbose_name=_("Year"), default=get_current_year)
+    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"
+        "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)
@@ -197,17 +311,14 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
             if changed:
                 lesson_documentation.save()
 
-    def __str__(self):
-        return f"{self.lesson_period}, {date_format(self.date)}"
-
-    def get_absolute_url(self):
-        return reverse(
-            "lesson_by_week_and_period", args=[self.year, self.week, self.lesson_period.pk],
-        )
+    def __str__(self) -> str:
+        return f"{self.lesson_period}, {self.date_formatted}"
 
     def save(self, *args, **kwargs):
-        if get_site_preferences()["alsijil__carry_over"] and (
-            self.topic or self.homework or self.group_note
+        if (
+            get_site_preferences()["alsijil__carry_over"]
+            and (self.topic or self.homework or self.group_note)
+            and self.lesson_period
         ):
             self._carry_over_data()
         super().save(*args, **kwargs)
@@ -215,13 +326,17 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
     class Meta:
         verbose_name = _("Lesson documentation")
         verbose_name_plural = _("Lesson documentations")
-        unique_together = [["lesson_period", "week"]]
         ordering = [
             "year",
             "week",
             "lesson_period__period__weekday",
             "lesson_period__period__period",
         ]
+        constraints = [
+            CheckConstraint(
+                check=lesson_related_constraint_q, name="one_relation_only_lesson_documentation",
+            )
+        ]
 
 
 class ExtraMark(ExtensibleModel):
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index 6f2a8d614aab4ba0f8a5bb1d83fcfd7b6c0fd4f5..f36db7aa5a3b532d2b143c109c7daf089391c96a 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -29,7 +29,7 @@ from .util.predicates import (
 )
 
 # View lesson
-view_lesson_predicate = has_person & (
+view_register_object_predicate = has_person & (
     is_none  # View is opened as "Current lesson"
     | is_lesson_teacher
     | is_lesson_participant
@@ -37,19 +37,19 @@ view_lesson_predicate = has_person & (
     | has_global_perm("alsijil.view_lesson")
     | has_lesson_group_object_perm("core.view_week_class_register_group")
 )
-add_perm("alsijil.view_lesson", view_lesson_predicate)
+add_perm("alsijil.view_register_object", view_register_object_predicate)
 
 # View lesson in menu
 add_perm("alsijil.view_lesson_menu", has_person)
 
 # View lesson personal notes
-view_lesson_personal_notes_predicate = view_lesson_predicate & (
+view_lesson_personal_notes_predicate = view_register_object_predicate & (
     ~is_lesson_participant
     | is_lesson_teacher
     | has_global_perm("alsijil.view_personalnote")
     | has_lesson_group_object_perm("core.view_personalnote_group")
 )
-add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate)
+add_perm("alsijil.view_register_object_personalnote", view_lesson_personal_notes_predicate)
 
 # Edit personal note
 edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & (
@@ -57,7 +57,7 @@ edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & (
     | has_global_perm("alsijil.change_personalnote")
     | has_lesson_group_object_perm("core.edit_personalnote_group")
 )
-add_perm("alsijil.edit_lesson_personalnote", edit_lesson_personal_note_predicate)
+add_perm("alsijil.edit_register_object_personalnote", edit_lesson_personal_note_predicate)
 
 # View personal note
 view_personal_note_predicate = has_person & (
@@ -78,11 +78,11 @@ edit_personal_note_predicate = view_personal_note_predicate & (
 add_perm("alsijil.edit_personalnote", edit_personal_note_predicate)
 
 # View lesson documentation
-view_lesson_documentation_predicate = view_lesson_predicate
+view_lesson_documentation_predicate = view_register_object_predicate
 add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate)
 
 # Edit lesson documentation
-edit_lesson_documentation_predicate = view_lesson_predicate & (
+edit_lesson_documentation_predicate = view_register_object_predicate & (
     is_lesson_teacher
     | has_global_perm("alsijil.change_lessondocumentation")
     | has_lesson_group_object_perm("core.edit_lessondocumentation_group")
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
index f04e06fc317d58d202501ff09ff38a76f1c85e1a..f21d7686acb763c6f63942dcab91517020b7e0e2 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -10,21 +10,21 @@
 {% endblock %}
 
 {% block content %}
-  {% if next_lesson_person or prev_lesson_person %}
+  {% if next_lesson_person or prev_lesson_person or lesson_documentation %}
     <div class="row no-margin">
       <div class="col s12 no-padding">
         {# Back to week view #}
-        {% with lesson_period.get_lesson_documentation as lesson_doc %}
-          <a href="{% url "week_view_by_week" lesson_doc.year lesson_doc.week "group" lesson_period.lesson.groups.all.0.pk %}"
+        {% if lesson_documentation %}
+          <a href="{% url "week_view_by_week" lesson_documentation.calendar_week.year lesson_documentation.calendar_week.week "group" register_object.get_groups.all.0.pk %}"
              class="btn primary-color waves-light waves-effect alsijil-top-button">
             <i class="material-icons left">chevron_left</i> {% trans "Back to week view" %}
           </a>
-        {% endwith %}
+        {% endif %}
 
         {# Next lesson #}
         {% if prev_lesson_person %}
           <a class="btn primary waves-effect waves-light alsijil-top-button"
-             href="{% url "lesson_by_week_and_period" prev_lesson_person.week.year prev_lesson_person.week.week prev_lesson_person.id %}">
+             href="{% url "lesson_period" prev_lesson_person.week.year prev_lesson_person.week.week prev_lesson_person.id %}">
             <i class="material-icons left">arrow_back</i>
             {% trans "My previous lesson" %}
           </a>
@@ -33,7 +33,7 @@
         {# Previous lesson #}
         {% if next_lesson_person %}
           <a class="btn primary right waves-effect waves-light alsijil-top-button"
-             href="{% url "lesson_by_week_and_period" next_lesson_person.week.year next_lesson_person.week.week next_lesson_person.id %}">
+             href="{% url "lesson_period" next_lesson_person.week.year next_lesson_person.week.week next_lesson_person.id %}">
             <i class="material-icons right">arrow_forward</i>
             {% trans "My next lesson" %}
           </a>
@@ -43,51 +43,60 @@
   {% endif %}
 
   <h4>
-    {{ day }}, {% blocktrans with period=lesson_period.period.period %}{{ period }}. period{% endblocktrans %} –
+    {% if register_object.label_ == "event" %}
+      {{ register_object.date_start }} {{ register_object.period_from.period }}.–{{ register_object.date_end }}
+      {{ register_object.period_to.period }}.,
+    {% else %}
+      {{ day }}, {% blocktrans with period=register_object.period.period %}{{ period }}. period{% endblocktrans %} –
+    {% endif %}
 
-    {% for group in lesson_period.get_groups.all %}
-      <span>{{ group.name }}</span>,
-    {% endfor %}
+    {{ register_object.group_names }},
 
-    {{ lesson_period.get_subject.name }},
+    {% if register_object.label_ == "event" %}
+      {% trans "Event" %} ({{ register_object.title }})
+    {% else %}
+      {{ register_object.get_subject.name }}
+    {% endif %},
 
-    {% for teacher in lesson_period.get_teachers.all %}
-      {{ teacher.short_name }}
-    {% endfor %}
+    {{ register_object.teacher_short_names }}
 
     <span class="right">
-    {% include "alsijil/partials/lesson_status_icon.html" with period=lesson_period css_class="medium" %}
-  </span>
+      {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object css_class="medium" %}
+    </span>
   </h4>
   <br/>
 
-  {% has_perm "alsijil.view_lessondocumentation" user lesson_period as can_view_lesson_documentation %}
-  {% has_perm "alsijil.edit_lessondocumentation" user lesson_period as can_edit_lesson_documentation %}
-  {% has_perm "alsijil.edit_lesson_personalnote" user lesson_period as can_edit_lesson_personalnote %}
+  {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %}
+  {% has_perm "alsijil.edit_lessondocumentation" user register_object as can_edit_lesson_documentation %}
+  {% has_perm "alsijil.edit_register_object_personalnote" user register_object as can_edit_register_object_personalnote %}
 
   <form method="post" class="row">
     <p>
       {% if not blocked_because_holidays %}
-        {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
+        {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
           {% include "core/partials/save_button.html" %}
         {% endif %}
       {% endif %}
 
-      <a class="btn waves-effect waves-light primary"
-         href="{% url "lesson_by_week_and_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}">
-        <i class="material-icons left">arrow_back</i>
-        {% blocktrans with subject=lesson_period.get_subject.name %}
-          Previous {{ subject }} lesson
-        {% endblocktrans %}
-      </a>
-
-      <a class="btn right waves-effect waves-light primary"
-         href="{% url "lesson_by_week_and_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}">
-        <i class="material-icons right">arrow_forward</i>
-        {% blocktrans with subject=lesson_period.get_subject.name %}
-          Next {{ subject }} lesson
-        {% endblocktrans %}
-      </a>
+      {% if prev_lesson %}
+        <a class="btn waves-effect waves-light primary"
+           href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}">
+          <i class="material-icons left">arrow_back</i>
+          {% blocktrans with subject=register_object.get_subject.name %}
+            Previous {{ subject }} lesson
+          {% endblocktrans %}
+        </a>
+      {% endif %}
+
+      {% if next_lesson %}
+        <a class="btn right waves-effect waves-light primary"
+           href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}">
+          <i class="material-icons right">arrow_forward</i>
+          {% blocktrans with subject=register_object.get_subject.name %}
+            Next {{ subject }} lesson
+          {% endblocktrans %}
+        </a>
+      {% endif %}
     </p>
 
     {% csrf_token %}
@@ -100,13 +109,13 @@
             <li class="tab">
               <a href="#lesson-documentation">{% trans "Lesson documentation" %}</a>
             </li>
-            {% if not lesson_period.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %}
+            {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %}
               <li class="tab">
                 <a href="#personal-notes">{% trans "Personal notes" %}</a>
               </li>
             {% endif %}
-            {% has_perm "alsijil.view_lessondocumentation" user lesson_period.prev as can_view_prev_lesson_documentation %}
-            {% if lesson_period.prev.get_lesson_documentation and can_view_prev_lesson_documentation %}
+            {% has_perm "alsijil.view_lessondocumentation" user register_object.prev as can_view_prev_lesson_documentation %}
+            {% if register_object.prev.get_lesson_documentation and can_view_prev_lesson_documentation %}
               <li class="tab">
                 <a href="#previous-lesson">{% trans "Previous lesson" %}</a>
               </li>
@@ -163,7 +172,7 @@
           </div>
         </div>
 
-        {% with prev_lesson=lesson_period.prev prev_doc=prev_lesson.get_lesson_documentation %}
+        {% with prev_lesson=register_object.prev 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" user prev_lesson as can_view_prev_lesson_documentation %}
             {% if prev_doc and can_view_prev_lesson_documentation %}
@@ -234,14 +243,14 @@
           {% endwith %}
         {% endwith %}
 
-        {% if not lesson_period.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %}
+        {% if register_object.label_ != "lesson_period" or not register_object.get_substitution.cancelled or not request.site.preferences.alsijil__block_personal_notes_for_cancelled %}
           <div class="col s12" id="personal-notes">
             <div class="card">
               <div class="card-content">
                 <span class="card-title">
                   {% blocktrans %}Personal notes{% endblocktrans %}
                 </span>
-                {% if can_edit_lesson_personalnote %}
+                {% if can_edit_register_object_personalnote %}
                   {% form form=personal_note_formset.management_form %}{% endform %}
                 {% endif %}
 
@@ -259,7 +268,7 @@
                   </thead>
                   <tbody>
                   {% for form in personal_note_formset %}
-                    {% if can_edit_lesson_personalnote %}
+                    {% if can_edit_register_object_personalnote %}
                       <tr>
                         {{ form.id }}
                         <td>{{ form.person_name }}{{ form.person_name.value }}
@@ -357,7 +366,7 @@
 
         {% if group_roles %}
           <div class="col s12" id="group-roles">
-            {% include "alsijil/group_role/partials/assigned_roles.html" with roles=group_roles group=lesson_period.lesson.groups.first back_url=back_url %}
+            {% include "alsijil/group_role/partials/assigned_roles.html" with roles=group_roles group=register_object.get_groups.first back_url=back_url %}
           </div>
         {% endif %}
 
@@ -377,25 +386,29 @@
 
 
       <p>
-        {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
+        {% if can_edit_lesson_documentation or can_edit_register_object_personalnote %}
           {% include "core/partials/save_button.html" %}
         {% endif %}
 
-        <a class="btn primary waves-effect waves-light"
-           href="{% url "lesson_by_week_and_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}">
-          <i class="material-icons left">arrow_back</i>
-          {% blocktrans with subject=lesson_period.get_subject.name %}
-            Previous {{ subject }} lesson
-          {% endblocktrans %}
-        </a>
+        {% if prev_lesson %}
+          <a class="btn primary waves-effect waves-light"
+             href="{% url "lesson_period" prev_lesson.week.year prev_lesson.week.week prev_lesson.id %}">
+            <i class="material-icons left">arrow_back</i>
+            {% blocktrans with subject=register_object.get_subject.name %}
+              Previous {{ subject }} lesson
+            {% endblocktrans %}
+          </a>
+        {% endif %}
 
-        <a class="btn primary right waves-effect waves-light"
-           href="{% url "lesson_by_week_and_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}">
-          <i class="material-icons right">arrow_forward</i>
-          {% blocktrans with subject=lesson_period.get_subject.name %}
-            Next {{ subject }} lesson
-          {% endblocktrans %}
-        </a>
+        {% if next_lesson %}
+          <a class="btn primary right waves-effect waves-light"
+             href="{% url "lesson_period" next_lesson.week.year next_lesson.week.week next_lesson.id %}">
+            <i class="material-icons right">arrow_forward</i>
+            {% blocktrans with subject=register_object.get_subject.name %}
+              Next {{ subject }} lesson
+            {% endblocktrans %}
+          </a>
+        {% endif %}
       </p>
 
     {% else %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
index 259b7b3471c1508f08b29188e5e4a578204db8f2..7254e0e24be0f979f9fa1f01a05a0b4c8f10e532 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
@@ -37,7 +37,6 @@
 
     <ul class="collection">
       {% for note in unexcused_absences %}
-        {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
         <li class="collection-item">
           {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
           {% if can_edit_personal_note %}
@@ -54,7 +53,7 @@
           {% endif %}
           <i class="material-icons left red-text">warning</i>
           <p class="no-margin">
-            <a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">{{ note_date }}, {{ note.lesson_period }}</a>
+            <a href="{{ note.get_absolute_url }}">{{ note.date }}, {{ note.lesson_period }}</a>
           </p>
           {% if note.remarks %}
             <p class="no-margin"><em>{{ note.remarks }}</em></p>
@@ -130,38 +129,44 @@
         <div>
           <ul>
             {% for note in personal_notes %}
-              {% ifchanged note.lesson_period.lesson.validity.school_term %}</ul></div></li>
+              {% ifchanged note.school_term %}</ul></div></li>
                 <li {% if forloop.first %}class="active"{% endif %}>
                 <div class="collapsible-header"><i
-                        class="material-icons">date_range</i>{{ note.lesson_period.lesson.validity.school_term }}</div>
+                    class="material-icons">date_range</i>{{ note.school_term }}</div>
                 <div class="collapsible-body">
                 <ul class="collection">
               {% endifchanged %}
 
               {% ifchanged note.week %}
                 <li class="collection-item">
-                  <strong>{% blocktrans with week=note.week %}Week {{ week }}{% endblocktrans %}</strong>
+                  <strong>{% blocktrans with week=note.calendar_week.week %}Week {{ week }}{% endblocktrans %}</strong>
                 </li>
               {% endifchanged %}
-              {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
-              {% ifchanged note_date %}
+              {% ifchanged note.date %}
                 <li class="collection-item">
-                  {% if can_mark_all_as_excused %}
+                  {% if can_mark_all_as_excused and note.date %}
                     <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
                       {% csrf_token %}
                       {% trans "Mark all as" %}
-                      <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
+                      <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
                       {% include "alsijil/partials/mark_as_buttons.html" %}
                     </form>
                   {% endif %}
                   <i class="material-icons left">schedule</i>
-                  {{ note_date }}
 
-                  {% if can_mark_all_as_excused %}
+                  {% if note.date %}
+                    {{ note.date }}
+                  {% else %}
+                    {{ note.register_object.date_start }}
+                    {{ note.register_object.period_from.period }}.–{{ note.register_object.date_end }}
+                    {{ note.register_object.period_to.period }}.
+                  {% endif %}
+
+                  {% if can_mark_all_as_excused and note.date %}
                     <form action="" method="post" class="hide-on-med-and-up">
                       {% csrf_token %}
                       {% trans "Mark all as" %}
-                      <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
+                      <input type="hidden" value="{{ note.date|date:"Y-m-d" }}" name="date">
                       {% include "alsijil/partials/mark_as_buttons.html" %}
                     </form>
                   {% endif %}
@@ -171,14 +176,20 @@
               <li class="collection-item">
                 <div class="row no-margin">
                   <div class="col s2 m1">
-                    {{ note.lesson_period.period.period }}.
+                    {% if note.register_object.period %}
+                      {{ note.register_object.period.period }}.
+                    {% endif %}
                   </div>
 
                   <div class="col s10 m4">
                     <i class="material-icons left">event_note</i>
-                    <a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">
-                      {{ note.lesson_period.get_subject.name }}<br/>
-                      {{ note.lesson_period.get_teacher_names }}
+                    <a href="{{ note.get_absolute_url }}">
+                      {% if note.register_object.get_subject %}
+                        {{ note.register_object.get_subject.name }}
+                      {% else %}
+                        {% trans "Event" %} ({{ note.register_object.title }})
+                      {% endif %}<br/>
+                      {{ note.register_object.teacher_names }}
                     </a>
                   </div>
 
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
index 24780eba16318c05d925d57aed87637caa9c2a85..92f92a2b242e497b60cf3a79fde1542810b650e2 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -78,17 +78,14 @@
         </ul>
       </div>
       <div class="col s12" id="week-overview">
-        {% regroup lesson_periods by period.get_weekday_display as periods_by_day %}
-        {% for weekday, periods in periods_by_day %}
-          {% with weekdays|get_dict:periods.0.period.weekday as advanced_weekday %}
-            {% weekday_to_date week periods.0.period.weekday as current_date %}
-
+        {% for weekday, objects in regrouped_objects.items %}
+          {% with weekdays|get_dict:forloop.counter0 as advanced_weekday %}
             {% if advanced_weekday.holiday and not request.site.preferences.alsijil__allow_entries_in_holidays %}
               <div class="card">
                 <div class="card-content">
-                  {% weekday_to_date week periods.0.period.weekday as current_date %}
                   <span class="card-title">
-                    {{ weekday }}, {{ current_date }} <span class="badge new blue no-float">{{ advanced_weekday.holiday }}</span>
+                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }} <span
+                      class="badge new blue no-float">{{ advanced_weekday.holiday }}</span>
                   </span>
                 </div>
               </div>
@@ -96,7 +93,7 @@
               <div class="card show-on-extra-large">
                 <div class="card-content">
                   <span class="card-title">
-                    {{ weekday }}, {{ current_date }}
+                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }}
                   </span>
                   <table class="striped datatable">
                     <thead>
@@ -114,55 +111,69 @@
                     </tr>
                     </thead>
                     <tbody>
-                    {% for period in periods %}
-                      {% has_perm "alsijil.view_lessondocumentation" user period as can_view_lesson_documentation %}
+                    {% for register_object in objects %}
+                      {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %}
                       {% if can_view_lesson_documentation %}
                         <tr>
                           <td class="center-align">
-                            {% include "alsijil/partials/lesson_status_icon.html" with period=period %}
+                            {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object %}
                           </td>
                           <td class="tr-link">
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.period.period }}.
+                               href="{{ register_object.alsijil_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="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                                {{ period.lesson.group_names }}
+                                 href="{{ register_object.alsijil_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="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.get_subject.name }}
+                               href="{{ register_object.alsijil_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="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.get_teacher_names }}
+                               href="{{ register_object.alsijil_url }}">
+                                {{ register_object.teacher_names }}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.topic "–" %}
+                               href="{{ register_object.alsijil_url }}">
+                              {% firstof register_object.get_lesson_documentation.topic "–" %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.homework "–" %}
+                               href="{{ register_object.alsijil_url }}">
+                              {% firstof register_object.get_lesson_documentation.homework "–" %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.group_note "–" %}
+                               href="{{ register_object.alsijil_url }}">
+                              {% firstof register_object.get_lesson_documentation.group_note "–" %}
                             </a>
                           </td>
                         </tr>
@@ -174,48 +185,69 @@
               </div>
               <ul class="collapsible hide-on-extra-large-only">
                 <li class="">
-                  {% weekday_to_date week periods.0.period.weekday as current_date %}
                   <div class="collapsible-header flow-text">
-                    {{ weekday }}, {{ current_date }} <i class="material-icons collapsible-icon-right">expand_more</i>
+                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }} <i
+                      class="material-icons collapsible-icon-right">expand_more</i>
                   </div>
                   <div class="collapsible-body">
                     <div class="collection">
-                      {% for period in periods %}
-                        {% has_perm "alsijil.view_lessondocumentation" user period as can_view_lesson_documentation %}
+                      {% for register_object in objects %}
+                        {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %}
                         {% if can_view_lesson_documentation %}
                           <a class="collection-item avatar"
-                             href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                            {% include "alsijil/partials/lesson_status_icon.html" with period=period css_class="materialize-circle" color_suffix=" " %}
+                             href="{{ register_object.alsijil_url }}">
+                            {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object css_class="materialize-circle" color_suffix=" " %}
                             <table class="hide-on-med-and-down">
                               <tr>
                                 <th>{% trans "Subject" %}</th>
-                                <td>{{ period.period.period }}. {{ period.get_subject.name }}</td>
+                                <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 "Group" %}</th>
-                                  <td>{{ period.lesson.group_names }}</td>
+                                  <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>{{ period.lesson.teacher_names }}</td>
+                                <td>
+                                    {{ register_object.teacher_names }}
+                                </td>
                               </tr>
                               <tr>
                                 <th>{% trans "Lesson topic" %}</th>
-                                <td>{% firstof period.get_lesson_documentation.topic "–" %}</td>
+                                <td>{% firstof register_object.get_lesson_documentation.topic "–" %}</td>
                               </tr>
                               {% with period.get_lesson_documentation as lesson_documentation %}
                                 {% if lesson_documentation.homework %}
                                   <tr>
                                     <th>{% trans "Homework" %}</th>
-                                    <td>{% firstof period.get_lesson_documentation.homework "–" %}</td>
+                                    <td>{% firstof register_object.get_lesson_documentation.homework "–" %}</td>
                                   </tr>
                                 {% endif %}
                                 {% if lesson_documentation.group_note %}
                                   <tr>
                                     <th>{% trans "Group note" %}</th>
-                                    <td>{% firstof period.get_lesson_documentation.group_note "–" %}</td>
+                                    <td>{% firstof register_object.get_lesson_documentation.group_note "–" %}</td>
                                   </tr>
                                 {% endif %}
                               {% endwith %}
@@ -223,32 +255,45 @@
                             <div class="hide-on-large-only">
                               <ul class="collection">
                                 <li class="collection-item">
-                                  {{ period.period.period }}. {{ period.get_subject.name }}
+                                  {% 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" %} ({{ register_object.title }})
+                                  {% endif %}
                                 </li>
                                 {% if not group %}
                                   <li class="collection-item">
-
-                                    {{ period.lesson.group_names }}
-
+                                    {% if register_object.lesson %}
+                                      {{ register_object.lesson.group_names }}
+                                    {% else %}
+                                      {{ register_object.group_names }}
+                                    {% endif %}
                                   </li>
                                 {% endif %}
                                 <li class="collection-item">
-                                  {{ period.lesson.teacher_names }}
+                                    {{ register_object.teacher_names }}
                                 </li>
                                 <li class="collection-item">
-                                  {{ period.get_lesson_documentation.topic }}
+                                  {{ register_object.get_lesson_documentation.topic }}
                                 </li>
                                 {% with period.get_lesson_documentation as lesson_documentation %}
                                   {% if lesson_documentation.homework %}
                                     <li class="collection-item">
                                       <strong>{% trans "Homework" %}</strong>
-                                      {% firstof period.get_lesson_documentation.homework "–" %}
+                                      {% firstof register_object.get_lesson_documentation.homework "–" %}
                                     </li>
                                   {% endif %}
                                   {% if lesson_documentation.group_note %}
                                     <li class="collection-item">
                                       <strong>{% trans "Group note" %}</strong>
-                                      {% firstof period.get_lesson_documentation.group_note "–" %}
+                                      {% firstof register_object.get_lesson_documentation.group_note "–" %}
                                     </li>
                                   {% endif %}
                                 {% endwith %}
@@ -312,10 +357,10 @@
                 {% if note.remarks %}
                   <blockquote>
                     {{ note.remarks }}
-                    {% weekday_to_date week note.lesson_period.period.weekday as note_date %}
+                    {% weekday_to_date week note.register_object.period.weekday as note_date %}
                     <em class="right">
-                      <a href="{% url 'lesson_by_week_and_period' week.year week.week note.lesson_period.id %}">
-                        {{ note_date }}, {{ note.lesson_period.get_subject.name }}
+                      <a href="{{ note.register_object.alsijil_url }}">
+                        {{ note.date }}, {{ note.register_object.get_subject.name }}
                       </a>
                     </em>
                   </blockquote>
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
index 4ad4aa8251059cbe5453f24ca5e00cd6a4490bd2..ceff8c1a17ea0452b1f013d759dbee7edfd6c630 100644
--- a/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
@@ -2,19 +2,30 @@
 
 {% now_datetime as now_dt %}
 
-{% if period.has_documentation %}
+{% if register_object.has_documentation %}
   <i class="material-icons green{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Data complete" %}" title="{% trans "Data complete" %}">check_circle</i>
+{% elif not register_object.period %}
+  {% period_to_time_start week register_object.raw_period_from_on_day as time_start %}
+  {% period_to_time_end week register_object.raw_period_to_on_day as time_end %}
+
+  {% if now_dt > time_end %}
+    <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i>
+  {% elif now_dt > time_start and now_dt < time_end %}
+    <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i>
+  {% else %}
+    <i class="material-icons purple{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Event" %}" title="{% trans "Event" %}">event</i>
+  {% endif %}
 {% else %}
-  {% period_to_time_start week period.period as time_start %}
-  {% period_to_time_end week period.period as time_end %}
+  {% period_to_time_start week register_object.period as time_start %}
+  {% period_to_time_end week register_object.period as time_end %}
 
-  {% if period.get_substitution.cancelled %}
+  {% if register_object.get_substitution.cancelled %}
     <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Lesson cancelled" %}" title="{% trans "Lesson cancelled" %}">cancel</i>
   {% elif now_dt > time_end %}
     <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i>
   {% elif now_dt > time_start and now_dt < time_end %}
     <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i>
-  {% elif period.get_substitution %}
+  {% elif register_object.get_substitution %}
     <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Substitution" %}" title="{% trans "Substitution" %}">update</i>
   {% endif %}
 {% endif %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
index c95a168f7dc6bbe82f6a8823b0a074abf836b756..ca2d801d60cf9657be5378c38abf5903304a2ed7 100644
--- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
+++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
@@ -318,12 +318,24 @@
       <tbody>
       {% for note in person.filtered_notes %}
         {% if note.absent or note.late or note.remarks or note.extra_marks.all %}
-            {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
           <tr>
-            <td>{{ note_date }}</td>
-            <td>{{ note.lesson_period.period.period }}</td>
-            <td>{{ note.lesson_period.get_subject.short_name }} </td>
-            <td>{{ note.lesson_period.get_teachers.first.short_name }}</td>
+            {% if note.date %}
+              <td>{{ note.date }}</td>
+              <td>{{ note.register_object.period.period }}</td>
+            {% else %}
+              <td colspan="2">
+                {{ note.register_object.date_start }} {{ note.register_object.period_from.period }}.–{{ note.register_object.date_end }}
+                {{ note.register_object.period_to.period }}.
+              </td>
+            {% endif %}
+            <td>
+              {% if note.register_object.label_ != "event" %}
+                {{ note.register_object.get_subject.short_name }}
+              {% else %}
+                {% trans "Event" %}
+              {% endif %}
+            </td>
+            <td>{{ note.register_object.teacher_short_names }}</td>
             <td>
               {% if note.absent %}
                 {% trans 'Yes' %}
@@ -374,8 +386,8 @@
       </thead>
       <tbody>
       {% for day in week %}
-        {% with periods_by_day|get_dict:day as periods %}
-          {% for period, documentations, notes, substitution in periods %}
+        {% with register_objects_by_day|get_dict:day as register_objects %}
+          {% for register_object, documentations, notes, substitution in register_objects %}
             <tr class="
                     {% if substitution %}
                       {% if substitution.cancelled %}
@@ -389,18 +401,28 @@
                     {% endif %}
                   ">
               {% if forloop.first %}
-                <th rowspan="{{ periods|length }}" class="lessons-day-head">{{ day }}</th>
+                <th rowspan="{{ register_objects|length }}" class="lessons-day-head">{{ day }}</th>
               {% endif %}
-              <td class="lesson-pe">{{ period.period.period }}</td>
+              <td class="lesson-pe">
+                {% if register_object.label_ != "event" %}
+                  {{ register_object.period.period }}
+                {% else %}
+                  {{ register_object.period_from_on_day }}.–{{ register_object.period_to_on_day }}.
+                {% endif %}
+              </td>
               <td class="lesson-subj">
-                {% if substitution %}
+                {% if register_object.label_ == "event" %}
+                  <strong>{% trans "Event" %}</strong>
+                  {% elif substitution %}
                   {% include "chronos/partials/subs/subject.html" with type="substitution" el=substitution %}
                 {% else %}
-                  {% include "chronos/partials/subject.html" with subject=period.lesson.subject %}
+                  {% include "chronos/partials/subject.html" with subject=register_object.get_subject %}
                 {% endif %}
               </td>
               <td class="lesson-topic">
-                {% if substitution.cancelled %}
+                {% if register_object.label_ == "event" %}
+                  {{ register_object.title }}: {{ documentations.0.topic }}
+                {% elif substitution.cancelled %}
                   {% trans 'Lesson cancelled' %}
                 {% else %}
                   {{ documentations.0.topic }}
@@ -451,7 +473,7 @@
               </td>
               <td class="lesson-te">
                 {% if documentations.0.topic %}
-                  {{ substitution.teachers.first.short_name|default:period.lesson.teachers.first.short_name }}
+                  {{ substitution.teachers.first.short_name|default:register_object.get_teachers.first.short_name }}
                 {% endif %}
               </td>
             </tr>
diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py
index 864c862ae36490c5a7817c7e7ddfce17042930c6..16ba0d0b9c5c4c976c26222a8542d3e96a93b1aa 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -3,12 +3,20 @@ from django.urls import path
 from . import views
 
 urlpatterns = [
-    path("lesson", views.lesson, name="lesson"),
+    path("lesson", views.register_object, {"model": "lesson"}, name="lesson_period"),
     path(
-        "lesson/<int:year>/<int:week>/<int:period_id>",
-        views.lesson,
-        name="lesson_by_week_and_period",
+        "lesson/<int:year>/<int:week>/<int:id_>",
+        views.register_object,
+        {"model": "lesson"},
+        name="lesson_period",
     ),
+    path(
+        "extra_lesson/<int:id_>/",
+        views.register_object,
+        {"model": "extra_lesson"},
+        name="extra_lesson",
+    ),
+    path("event/<int:id_>/", views.register_object, {"model": "event"}, name="event",),
     path("week/", views.week_view, name="week_view"),
     path("week/<int:year>/<int:week>/", views.week_view, name="week_view_by_week"),
     path("week/year/cw/", views.week_view, name="week_view_placeholders"),
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
index 432afb18f2edf21b118539992342cb674bd8d0ae..9aedcb24ea2c8dd2c529d273b701b082281786ea 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -1,35 +1,44 @@
-from typing import Optional
+from typing import List, Optional, 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 calendarweek import CalendarWeek
 
-from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.apps.alsijil.models import LessonDocumentation
+from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
 
 
-def get_lesson_period_by_pk(
+def get_register_object_by_pk(
     request: HttpRequest,
+    model: Optional[str] = None,
     year: Optional[int] = None,
     week: Optional[int] = None,
-    period_id: Optional[int] = None,
-):
-    """Get LessonPeriod object either by given object_id or by time and current person."""
+    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 period_id:
-        lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(pk=period_id)
+    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():
-            lesson_period = (
+            register_object = (
                 LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
             )
         else:
-            lesson_period = (
+            register_object = (
                 LessonPeriod.objects.at_time().filter_participant(request.user.person).first()
             )
     else:
-        lesson_period = None
-    return lesson_period
+        register_object = None
+    return register_object
 
 
 def get_timetable_instance_by_pk(
@@ -44,3 +53,49 @@ def get_timetable_instance_by_pk(
         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=""), week=wanted_week.week, year=wanted_week.year,
+            ).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
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
index e039a3b4232a67db3069cbbe05c79538fd22c027..27bdc4b9798c304b0ad20981bc918449f800db2b 100644
--- a/aleksis/apps/alsijil/util/predicates.py
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -5,7 +5,7 @@ from django.contrib.auth.models import Permission, User
 from guardian.models import UserObjectPermission
 from rules import predicate
 
-from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.core.models import Group, Person
 from aleksis.core.util.core_helpers import get_content_type_by_perm
 
@@ -19,17 +19,17 @@ def is_none(user: User, obj: Any) -> bool:
 
 
 @predicate
-def is_lesson_teacher(user: User, obj: LessonPeriod) -> bool:
+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 lesson or the substitution linked to the given LessonPeriod.
     """
     if obj:
-        sub = obj.get_substitution()
+        sub = obj.get_substitution() if isinstance(obj, LessonPeriod) else None
         if sub and sub in user.person.lesson_substitutions.all():
             return True
-        return user.person in obj.lesson.teachers.all()
+        return user.person in obj.get_teachers().all()
     return False
 
 
@@ -40,8 +40,8 @@ def is_lesson_participant(user: User, obj: LessonPeriod) -> bool:
     Checks whether the person linked to the user is a member in
     the groups linked to the given LessonPeriod.
     """
-    if hasattr(obj, "lesson"):
-        for group in obj.lesson.groups.all():
+    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
@@ -55,8 +55,8 @@ def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool:
     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"):
-        for group in obj.lesson.groups.all():
+    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
@@ -211,15 +211,15 @@ def is_personal_note_lesson_teacher(user: User, obj: PersonalNote) -> bool:
     Checks whether the person linked to the user is a teacher
     in the lesson or the substitution linked to the LessonPeriod of the given PersonalNote.
     """
-    if hasattr(obj, "lesson_period"):
-        if hasattr(obj.lesson_period, "lesson"):
+    if hasattr(obj, "register_object"):
+        if getattr(obj, "lesson_period", None):
             sub = obj.lesson_period.get_substitution()
             if sub and user.person in Person.objects.filter(
                 lesson_substitutions=obj.lesson_period.get_substitution()
             ):
                 return True
 
-            return user.person in obj.lesson_period.lesson.teachers.all()
+        return user.person in obj.register_object.get_teachers().all()
 
         return False
     return False
@@ -233,12 +233,11 @@ def is_personal_note_lesson_parent_group_owner(user: User, obj: PersonalNote) ->
     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 hasattr(obj, "lesson_period"):
-        if hasattr(obj.lesson_period, "lesson"):
-            for group in obj.lesson_period.lesson.groups.all():
-                for parent_group in group.parent_groups.all():
-                    if user.person in list(parent_group.owners.all()):
-                        return True
+    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 list(parent_group.owners.all()):
+                    return True
     return False
 
 
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 91199a3d6eb9a49d8cfd4cf477103f83ecfc2f7a..b2378f891f579b328c9c51114a7d212adba5d1ec 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,9 +1,12 @@
 from contextlib import nullcontext
+from copy import deepcopy
 from datetime import date, datetime, timedelta
 from typing import Any, Dict, Optional
 
 from django.core.exceptions import PermissionDenied
 from django.db.models import Count, Exists, OuterRef, Prefetch, Q, Subquery, 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.urls import reverse, reverse_lazy
@@ -20,7 +23,7 @@ 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 Holiday, LessonPeriod, TimePeriod
+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.mixins import (
@@ -53,33 +56,41 @@ from .models import (
     PersonalNote,
 )
 from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable
-from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk
+from .util.alsijil_helpers import (
+    annotate_documentations,
+    get_register_object_by_pk,
+    get_timetable_instance_by_pk,
+    register_objects_sorter,
+)
 
 
-@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk)
-def lesson(
+@permission_required("alsijil.view_register_object", fn=get_register_object_by_pk)  # FIXME
+def register_object(
     request: HttpRequest,
+    model: Optional[str] = None,
     year: Optional[int] = None,
     week: Optional[int] = None,
-    period_id: Optional[int] = None,
+    id_: Optional[int] = None,
 ) -> HttpResponse:
     context = {}
 
-    lesson_period = get_lesson_period_by_pk(request, year, week, period_id)
+    register_object = get_register_object_by_pk(request, model, year, week, id_)
 
-    if period_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, period_id)):
-        if lesson_period:
+    if not all((year, week, id_)):
+        if register_object and model == "lesson":
             return redirect(
-                "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk,
+                "lesson_period", wanted_week.year, wanted_week.week, register_object.pk,
             )
-        else:
+        elif not register_object:
             raise Http404(
                 _(
                     "You either selected an invalid lesson or "
@@ -87,22 +98,30 @@ def lesson(
                 )
             )
 
-    date_of_lesson = week_weekday_to_date(wanted_week, lesson_period.period.weekday)
+    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 (
-        date_of_lesson < lesson_period.lesson.validity.date_start
-        or date_of_lesson > lesson_period.lesson.validity.date_end
+    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(
-            wanted_week[lesson_period.period.weekday], lesson_period.period.time_start,
-        )
-        > datetime.now()
+        datetime.combine(date_of_lesson, start_time) > datetime.now()
         and not (
             get_site_preferences()["alsijil__open_periods_same_day"]
-            and wanted_week[lesson_period.period.weekday] <= datetime.now().date()
+            and date_of_lesson <= datetime.now().date()
         )
         and not request.user.is_superuser
     ):
@@ -117,45 +136,56 @@ def lesson(
     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_by_week_and_period", args=[wanted_week.year, wanted_week.week, lesson_period.pk]
+        "lesson_period", args=[wanted_week.year, wanted_week.week, register_object.pk]
     )
     context["back_url"] = back_url
 
-    next_lesson = request.user.person.next_lesson(lesson_period, date_of_lesson)
-    prev_lesson = request.user.person.previous_lesson(lesson_period, date_of_lesson)
-
-    context["lesson_period"] = lesson_period
+    context["register_object"] = register_object
     context["week"] = wanted_week
-    context["day"] = wanted_week[lesson_period.period.weekday]
+    context["day"] = date_of_lesson
     context["next_lesson_person"] = next_lesson
     context["prev_lesson_person"] = prev_lesson
-    context["prev_lesson"] = lesson_period.prev
-    context["next_lesson"] = lesson_period.next
+    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:
         # Group roles
         show_group_roles = request.user.person.preferences[
             "alsijil__group_roles_in_lesson_view"
-        ] and request.user.has_perm("alsijil.view_assigned_grouproles", lesson_period)
+        ] and request.user.has_perm("alsijil.view_assigned_grouproles", register_object)
         if show_group_roles:
-            groups = lesson_period.lesson.groups.all()
+            groups = register_object.get_groups().all()
             group_roles = GroupRole.objects.with_assignments(date_of_lesson, groups)
             context["group_roles"] = group_roles
 
         # Create or get lesson documentation object; can be empty when first opening lesson
-        lesson_documentation = lesson_period.get_or_create_lesson_documentation(wanted_week)
+        lesson_documentation = register_object.get_or_create_lesson_documentation(wanted_week)
         lesson_documentation_form = LessonDocumentationForm(
             request.POST or None, instance=lesson_documentation, prefix="lesson_documentation",
         )
 
         # Create a formset that holds all personal notes for all persons in this lesson
-        if not request.user.has_perm("alsijil.view_lesson_personalnote", lesson_period):
+        if not request.user.has_perm("alsijil.view_register_object_personalnote", register_object):
             persons = Person.objects.filter(pk=request.user.person.pk)
         else:
             persons = Person.objects.all()
 
-        persons_qs = lesson_period.get_personal_notes(persons, wanted_week)
+        persons_qs = register_object.get_personal_notes(persons, wanted_week)
 
         # Annotate group roles
         if show_group_roles:
@@ -172,7 +202,7 @@ def lesson(
 
         if request.method == "POST":
             if lesson_documentation_form.is_valid() and request.user.has_perm(
-                "alsijil.edit_lessondocumentation", lesson_period
+                "alsijil.edit_lessondocumentation", register_object
             ):
                 with reversion.create_revision():
                     reversion.set_user(request.user)
@@ -180,19 +210,25 @@ def lesson(
 
                 messages.success(request, _("The lesson documentation has been saved."))
 
-            substitution = lesson_period.get_substitution()
+            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_lesson_personalnote", lesson_period
+                    "alsijil.edit_register_object_personalnote", register_object
                 ):
                     with reversion.create_revision():
                         reversion.set_user(request.user)
                         instances = personal_note_formset.save()
 
-                    if get_site_preferences()["alsijil__carry_over_personal_notes"]:
+                    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():
@@ -243,8 +279,10 @@ def week_view(
         "lesson__groups__parent_groups",
         "lesson__groups__parent_groups__owners",
     )
+    events = Event.objects.in_week(wanted_week)
+    extra_lessons = ExtraLesson.objects.in_week(wanted_week)
 
-    lesson_periods_query_exists = True
+    query_exists = True
     if type_ and id_:
         if isinstance(instance, HttpResponseNotFound):
             return HttpResponseNotFound()
@@ -252,15 +290,26 @@ def week_view(
         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():
             lesson_periods = lesson_periods.filter_teacher(request.user.person)
+            events = events.filter_teacher(request.user.person)
+            extra_lessons = 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:
-        lesson_periods_query_exists = False
+        query_exists = False
         lesson_periods = None
+        events = None
+        extra_lessons = None
 
     # Add a form to filter the view
     if type_:
@@ -304,35 +353,21 @@ def week_view(
 
     extra_marks = ExtraMark.objects.all()
 
-    if lesson_periods_query_exists:
+    if query_exists:
         lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
-        lesson_periods = (
-            LessonPeriod.objects.prefetch_related(
-                Prefetch(
-                    "documentations",
-                    queryset=LessonDocumentation.objects.filter(
-                        week=wanted_week.week, year=wanted_week.year
-                    ),
-                )
-            )
-            .filter(pk__in=lesson_periods_pk)
-            .annotate_week(wanted_week)
-            .annotate(
-                has_documentation=Exists(
-                    LessonDocumentation.objects.filter(
-                        ~Q(topic__exact=""),
-                        lesson_period=OuterRef("pk"),
-                        week=wanted_week.week,
-                        year=wanted_week.year,
-                    )
-                )
-            )
-            .order_by("period__weekday", "period__period")
-        )
+        lesson_periods = annotate_documentations(LessonPeriod, wanted_week, lesson_periods_pk)
+
+        events_pk = list(events.values_list("pk", flat=True))
+        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)
     else:
         lesson_periods_pk = []
+        events_pk = []
+        extra_lessons_pk = []
 
-    if lesson_periods_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.filter(is_active=True)
 
@@ -341,15 +376,49 @@ def week_view(
         elif group:
             persons_qs = persons_qs.filter(member_of=group)
         else:
-            persons_qs = persons_qs.filter(member_of__lessons__lesson_periods__in=lesson_periods_pk)
+            persons_qs = persons_qs.filter(
+                Q(member_of__lessons__lesson_periods__in=lesson_periods_pk)
+                | Q(member_of__events__in=events_pk)
+                | Q(member_of__extra_lessons__in=extra_lessons_pk)
+            )
+
+        personal_notes_q = (
+            Q(
+                personal_notes__week=wanted_week.week,
+                personal_notes__year=wanted_week.year,
+                personal_notes__lesson_period__in=lesson_periods_pk,
+            )
+            | Q(
+                personal_notes__event__date_start__lte=wanted_week[6],
+                personal_notes__event__date_end__gte=wanted_week[0],
+                personal_notes__event__in=events_pk,
+            )
+            | Q(
+                personal_notes__extra_lesson__week=wanted_week.week,
+                personal_notes__extra_lesson__year=wanted_week.year,
+                personal_notes__extra_lesson__in=extra_lessons_pk,
+            )
+        )
 
         persons_qs = persons_qs.distinct().prefetch_related(
             Prefetch(
                 "personal_notes",
                 queryset=PersonalNote.objects.filter(
-                    week=wanted_week.week,
-                    year=wanted_week.year,
-                    lesson_period__in=lesson_periods_pk,
+                    Q(
+                        week=wanted_week.week,
+                        year=wanted_week.year,
+                        lesson_period__in=lesson_periods_pk,
+                    )
+                    | Q(
+                        event__date_start__lte=wanted_week[6],
+                        event__date_end__gte=wanted_week[0],
+                        event__in=events_pk,
+                    )
+                    | Q(
+                        extra_lesson__week=wanted_week.week,
+                        extra_lesson__year=wanted_week.year,
+                        extra_lesson__in=extra_lessons_pk,
+                    )
                 ),
             ),
             "member_of__owners",
@@ -363,48 +432,28 @@ def week_view(
                     queryset=GroupRoleAssignment.objects.in_week(wanted_week).for_group(group),
                 ),
             )
-
         persons_qs = persons_qs.annotate(
             absences_count=Count(
                 "personal_notes",
-                filter=Q(
-                    personal_notes__lesson_period__in=lesson_periods_pk,
-                    personal_notes__week=wanted_week.week,
-                    personal_notes__year=wanted_week.year,
-                    personal_notes__absent=True,
-                ),
+                filter=personal_notes_q & Q(personal_notes__absent=True,),
                 distinct=True,
             ),
             unexcused_count=Count(
                 "personal_notes",
-                filter=Q(
-                    personal_notes__lesson_period__in=lesson_periods_pk,
-                    personal_notes__week=wanted_week.week,
-                    personal_notes__year=wanted_week.year,
-                    personal_notes__absent=True,
-                    personal_notes__excused=False,
-                ),
+                filter=personal_notes_q
+                & Q(personal_notes__absent=True, personal_notes__excused=False,),
                 distinct=True,
             ),
             tardiness_sum=Subquery(
-                Person.objects.filter(
-                    pk=OuterRef("pk"),
-                    personal_notes__lesson_period__in=lesson_periods_pk,
-                    personal_notes__week=wanted_week.week,
-                    personal_notes__year=wanted_week.year,
-                )
+                Person.objects.filter(personal_notes_q)
+                .filter(pk=OuterRef("pk"),)
                 .distinct()
                 .annotate(tardiness_sum=Sum("personal_notes__late"))
                 .values("tardiness_sum")
             ),
             tardiness_count=Count(
                 "personal_notes",
-                filter=Q(
-                    personal_notes__lesson_period__in=lesson_periods_pk,
-                    personal_notes__week=wanted_week.week,
-                    personal_notes__year=wanted_week.year,
-                )
-                & ~Q(personal_notes__late=0),
+                filter=personal_notes_q & ~Q(personal_notes__late=0),
                 distinct=True,
             ),
         )
@@ -414,12 +463,7 @@ def week_view(
                 **{
                     extra_mark.count_label: Count(
                         "personal_notes",
-                        filter=Q(
-                            personal_notes__lesson_period__in=lesson_periods_pk,
-                            personal_notes__week=wanted_week.week,
-                            personal_notes__year=wanted_week.year,
-                            personal_notes__extra_marks=extra_mark,
-                        ),
+                        filter=personal_notes_q & Q(personal_notes__extra_marks=extra_mark,),
                         distinct=True,
                     )
                 }
@@ -427,20 +471,53 @@ def week_view(
 
         persons = []
         for person in persons_qs:
-            persons.append({"person": person, "personal_notes": list(person.personal_notes.all())})
+            personal_notes = []
+            for note in person.personal_notes.all():
+                if note.lesson_period:
+                    note.lesson_period.annotate_week(wanted_week)
+                personal_notes.append(note)
+            persons.append({"person": person, "personal_notes": personal_notes})
     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):
+        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])
+
+            regrouped_objects.setdefault(weekday, [])
+            regrouped_objects[weekday].append(event_copy)
+
+    # Sort register objects
+    for weekday in regrouped_objects.keys():
+        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]
@@ -467,33 +544,61 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     context = {}
 
     group = get_object_or_404(Group, pk=id_)
-
+    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.select_related("lesson_period")
         .prefetch_related(
             "lesson_period__substitutions", "lesson_period__lesson__teachers", "groups_of_person"
         )
         .not_empty()
-        .filter(
-            Q(lesson_period__lesson__groups=group)
-            | Q(lesson_period__lesson__groups__parent_groups=group)
-        )
+        .filter(groups_q)
     )
     documentations = (
-        LessonDocumentation.objects.select_related("lesson_period")
-        .not_empty()
-        .filter(
-            Q(lesson_period__lesson__groups=group)
-            | Q(lesson_period__lesson__groups__parent_groups=group)
-        )
+        LessonDocumentation.objects.select_related("lesson_period").not_empty().filter(groups_q)
     )
 
     # 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,
+                list(filter(lambda d: d.extra_lesson == extra_lesson, documentations)),
+                list(filter(lambda d: d.extra_lesson == extra_lesson, personal_notes)),
+                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)
+            register_objects_by_day.setdefault(day, []).append(
+                (
+                    event_copy,
+                    list(filter(lambda d: d.event == event, documentations)),
+                    list(filter(lambda d: d.event == event, personal_notes)),
+                    None,
+                )
+            )
 
     weeks = CalendarWeek.weeks_within(group.school_term.date_start, group.school_term.date_end,)
 
-    periods_by_day = {}
     for lesson_period in lesson_periods:
         for week in weeks:
             day = week[lesson_period.period.weekday]
@@ -521,7 +626,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
                 )
                 substitution = lesson_period.get_substitution(week)
 
-                periods_by_day.setdefault(day, []).append(
+                register_objects_by_day.setdefault(day, []).append(
                     (lesson_period, filtered_documentations, filtered_personal_notes, substitution)
                 )
 
@@ -539,8 +644,8 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     context["extra_marks"] = ExtraMark.objects.all()
     context["group"] = group
     context["weeks"] = weeks
-    context["periods_by_day"] = periods_by_day
-    context["lesson_periods"] = lesson_periods
+    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()
@@ -639,13 +744,17 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
                         ):
                             raise PermissionDenied()
 
-                        notes = person.personal_notes.filter(
-                            week=date.isocalendar()[1],
-                            lesson_period__period__weekday=date.weekday(),
-                            lesson_period__lesson__validity__date_start__lte=date,
-                            lesson_period__lesson__validity__date_end__gte=date,
-                            absent=True,
-                            excused=False,
+                        notes = person.personal_notes.filter(absent=True, excused=False,).filter(
+                            Q(
+                                week=date.isocalendar()[1],
+                                lesson_period__period__weekday=date.weekday(),
+                                lesson_period__lesson__validity__date_start__lte=date,
+                                lesson_period__lesson__validity__date_end__gte=date,
+                            )
+                            | Q(
+                                extra_lesson__week=date.isocalendar()[1],
+                                extra_lesson__period__weekday=date.weekday(),
+                            )
                         )
                         for note in notes:
                             note.excused = True
@@ -685,17 +794,50 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
         allowed_personal_notes = person_personal_notes.all()
     else:
         allowed_personal_notes = person_personal_notes.filter(
-            lesson_period__lesson__groups__owners=request.user.person
+            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().order_by(
-        "-lesson_period__lesson__validity__date_start",
-        "-week",
-        "lesson_period__period__weekday",
-        "lesson_period__period__period",
+    personal_notes = (
+        allowed_personal_notes.not_empty()
+        .filter(Q(absent=True) | Q(late__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_by(
+            "-school_term_start", "-order_year", "-order_week", "-order_weekday", "order_period",
+        )
     )
     context["personal_notes"] = personal_notes
     context["excuse_types"] = ExcuseType.objects.all()
@@ -707,8 +849,10 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
         stats = []
         for school_term in school_terms:
             stat = {}
-            personal_notes = PersonalNote.objects.filter(
-                person=person, lesson_period__lesson__validity__school_term=school_term
+            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():