diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index e75ab0d438be36c9399b1ad38c1ece05bb7b77dc..6be0957fb83b1321137b56762e165d0cea3059ba 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -3,6 +3,7 @@ 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.urls import reverse
 from django.utils.translation import gettext as _
 
 import reversion
@@ -16,6 +17,18 @@ from aleksis.core.models import Group, Person
 from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 
 
+def alsijil_url(self):
+    if isinstance(self, LessonPeriod):
+        return reverse("lesson_period", args=[self.week.year, self.week.week, self.pk])
+    else:
+        return reverse(self.label_, args=[self.pk])
+
+
+LessonPeriod.property_(alsijil_url)
+Event.property_(alsijil_url)
+ExtraLesson.property_(alsijil_url)
+
+
 @Person.method
 def mark_absent(
     self,
@@ -52,17 +65,33 @@ 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):
+            sub = (
+                register_object.get_substitution()
+                if isinstance(register_object, LessonPeriod)
+                else None
+            )
             if sub and sub.cancelled:
                 continue
 
+            q_attrs = (
+                dict(week=wanted_week.week, year=wanted_week.year, lesson_period=register_object)
+                if isinstance(register_object, LessonPeriod)
+                else dict(extra_lesson=register_object)
+            )
+
             with reversion.create_revision():
                 set_user(get_request().user)
                 personal_note, created = (
@@ -70,14 +99,12 @@ 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())
@@ -89,11 +116,10 @@ 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):
+def get_personal_notes(self, persons: QuerySet, wanted_week: Optional[CalendarWeek] = None):
     """Get all personal notes for that lesson in a specified week.
 
     Returns all linked `PersonalNote` objects, filtered by the given weeek,
@@ -106,37 +132,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")
@@ -144,6 +163,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(
@@ -214,9 +237,7 @@ def get_or_create_lesson_documentation_simple(
     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(
-        **{"event" if isinstance(self, Event) else "extra_lesson": self}
-    )
+    lesson_documentation, created = LessonDocumentation.objects.get_or_create({self.label_: self})
     return lesson_documentation
 
 
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 38526f78b80b24935b73659325e44aac1185e692..71f8d2b3ea1a976ffe7fbed10b2cb88d444a51ee 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,7 +1,9 @@
+from datetime import date
+from typing import Optional, Union
+
 from django.db import models
 from django.db.models.constraints import CheckConstraint
 from django.db.models.query_utils import Q
-from django.urls import reverse
 from django.utils.formats import date_format
 from django.utils.translation import gettext_lazy as _
 
@@ -16,8 +18,7 @@ from aleksis.apps.alsijil.data_checks import (
 )
 from aleksis.apps.alsijil.managers import PersonalNoteManager
 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.util.core_helpers import get_site_preferences
 
@@ -73,7 +74,46 @@ lesson_related_constraint_q = (
 )
 
 
-class PersonalNote(ExtensibleModel, WeekRelatedMixin):
+class RegisterObjectRelatedMixin(WeekRelatedMixin):
+    @property
+    def register_object(self) -> Union[LessonPeriod, Event, ExtraLesson]:
+        if self.lesson_period:
+            return self.lesson_period
+        elif self.event:
+            return self.event
+        else:
+            return self.extra_lesson
+
+    @property
+    def calendar_week(self) -> CalendarWeek:
+        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 date(self) -> Optional[date]:
+        if self.lesson_period:
+            return super().date
+        elif self.extra_lesson:
+            return self.extra_lesson.date
+        return None
+
+    @property
+    def date_formatted(self) -> str:
+        return (
+            date_format(self.date)
+            if self.date
+            else f"{self.event.date_start}–{self.event.date_end}"
+        )
+
+    def get_absolute_url(self) -> str:
+        return self.register_object.alsijil_url
+
+
+class PersonalNote(RegisterObjectRelatedMixin, ExtensibleModel):
     """A personal note about a single person.
 
     Used in the class register to note absences, excuses
@@ -93,9 +133,7 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
     groups_of_person = models.ManyToManyField("core.Group", related_name="+")
 
     week = models.IntegerField(blank=True, null=True)
-    year = models.IntegerField(
-        verbose_name=_("Year"), default=get_current_year, blank=True, null=True
-    )
+    year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True)
 
     lesson_period = models.ForeignKey(
         "chronos.LessonPeriod", models.CASCADE, related_name="personal_notes", blank=True, null=True
@@ -144,16 +182,11 @@ 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:
+        return super().get_absolute_url() + "#personal-notes"
 
     class Meta:
         verbose_name = _("Personal note")
@@ -173,7 +206,7 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
         ]
 
 
-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.
@@ -182,9 +215,7 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
     data_checks = [LessonDocumentationOnHolidaysDataCheck]
 
     week = models.IntegerField(blank=True, null=True)
-    year = models.IntegerField(
-        verbose_name=_("Year"), default=get_current_year, blank=True, null=True
-    )
+    year = models.IntegerField(verbose_name=_("Year"), blank=True, null=True)
 
     lesson_period = models.ForeignKey(
         "chronos.LessonPeriod", models.CASCADE, related_name="documentations", blank=True, null=True
@@ -233,16 +264,13 @@ class LessonDocumentation(ExtensibleModel, WeekRelatedMixin):
                 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],
-        )
+        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)
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index f553c2773ef6e96aab87b585d01c389399cecd50..36f6a93e01f6505918902e9370dd75f36906ad49 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -27,7 +27,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
@@ -35,19 +35,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 & (
@@ -55,7 +55,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 & (
@@ -76,11 +76,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 23c96614dba54513c8c8dc4d033fd10cdc2665d6..68c8fcccdabc20c7269a41fc29492a16567076b5 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>
@@ -158,7 +167,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 %}
@@ -229,14 +238,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 %}
 
@@ -254,7 +263,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 }}</td>
@@ -354,25 +363,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/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
index 4c7d73da476595d6ae4f23824bbc7e0916e080e8..22b936effe6b28f51c3a1086882084d3725ff3a8 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -115,7 +115,7 @@
                           </td>
                           <td class="tr-link">
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                               href="{{ register_object.alsijil_url }}">
                               {% if register_object.period %}
                                 {{ register_object.period.period }}.
                               {% else %}
@@ -126,7 +126,7 @@
                           {% if not group %}
                             <td>
                               <a class="tr-link"
-                                 href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                                 href="{{ register_object.alsijil_url }}">
                                 {% if register_object.lesson %}
                                   {{ register_object.lesson.group_names }}
                                 {% else %}
@@ -137,41 +137,37 @@
                           {% endif %}
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                               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" %}
+                                {% trans "Event" %} ({{ register_object.title }})
                               {% endif %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
-                              {% if register_object.get_teacher_names %}
-                                {{ register_object.get_teacher_names }}
-                              {% else %}
+                               href="{{ register_object.alsijil_url }}">
                                 {{ register_object.teacher_names }}
-                              {% endif %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                               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 register_object.id %}">
+                               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 register_object.id %}">
+                               href="{{ register_object.alsijil_url }}">
                               {% firstof register_object.get_lesson_documentation.group_note "–" %}
                             </a>
                           </td>
@@ -194,7 +190,7 @@
                         {% 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 register_object.id %}">
+                             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>
@@ -229,11 +225,7 @@
                               <tr>
                                 <th>{% trans "Teachers" %}</th>
                                 <td>
-                                  {% if register_object.get_teacher_names %}
-                                    {{ register_object.get_teacher_names }}
-                                  {% else %}
                                     {{ register_object.teacher_names }}
-                                  {% endif %}
                                 </td>
                               </tr>
                               <tr>
@@ -268,7 +260,7 @@
                                   {% elif register_object.subject %}
                                     {{ register_object.subject }}
                                   {% else %}
-                                    {% trans "Event" %}
+                                    {% trans "Event" %} ({{ register_object.title }})
                                   {% endif %}
                                 </li>
                                 {% if not group %}
@@ -281,11 +273,7 @@
                                   </li>
                                 {% endif %}
                                 <li class="collection-item">
-                                  {% if register_object.get_teacher_names %}
-                                    {{ register_object.get_teacher_names }}
-                                  {% else %}
                                     {{ register_object.teacher_names }}
-                                  {% endif %}
                                 </li>
                                 <li class="collection-item">
                                   {{ register_object.get_lesson_documentation.topic }}
@@ -359,10 +347,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/urls.py b/aleksis/apps/alsijil/urls.py
index 673154f496b081b194b95cdde9aeef8464f05519..ca5da439368beb869b38035b49261b84480ae47e 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 24e04320e5a691e9a371c871d423f53453f7e613..1ad17814238fcd0f76ea726245194954bcd103c4 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -12,28 +12,33 @@ 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(
@@ -53,14 +58,18 @@ def get_timetable_instance_by_pk(
 def annotate_documentations(
     klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int]
 ) -> QuerySet:
-    instances = klass.objects.prefetch_related(
-        Prefetch(
+
+    if isinstance(klass, LessonPeriod):
+        prefetch = Prefetch(
             "documentations",
             queryset=LessonDocumentation.objects.filter(
                 week=wanted_week.week, year=wanted_week.year
             ),
         )
-    ).filter(pk__in=pks)
+    else:
+        prefetch = Prefetch("documentations")
+    instances = klass.objects.prefetch_related(prefetch).filter(pk__in=pks)
+
     if klass == LessonPeriod:
         instances = instances.annotate_week(wanted_week)
     if klass in (LessonPeriod, ExtraLesson):
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
index 95c7825c2a1eceeb2aed222260816ec9575ad0a8..a831cf0fd4db684857c38ca9c1669c5a177c0755 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
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index f5c5f18a0288cec7dcc54ebf44644bf85b46c0e9..5076124f759a051cbbc9b312d261866eb0312e2b 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -39,36 +39,37 @@ from .models import ExcuseType, ExtraMark, PersonalNote
 from .tables import ExcuseTypeTable, ExtraMarkTable
 from .util.alsijil_helpers import (
     annotate_documentations,
-    get_lesson_period_by_pk,
+    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:
-            return redirect(
-                "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk,
-            )
-        else:
+    if not all((year, week, id_)):
+        if register_object and model == "lesson":
+            return redirect("lesson", wanted_week.year, wanted_week.week, register_object.pk,)
+        elif not register_object:
             raise Http404(
                 _(
                     "You either selected an invalid lesson or "
@@ -76,22 +77,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
     ):
@@ -106,39 +115,51 @@ def lesson(
     context["blocked_because_holidays"] = blocked_because_holidays
     context["holiday"] = holiday
 
-    next_lesson = request.user.person.next_lesson(lesson_period, date_of_lesson)
-    prev_lesson = request.user.person.previous_lesson(lesson_period, date_of_lesson)
+    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
+    )
 
-    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:
 
         # 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)
         personal_note_formset = PersonalNoteFormSet(
             request.POST or None, queryset=persons_qs, prefix="personal_notes"
         )
 
         if request.method == "POST":
             if lesson_documentation_form.is_valid() and request.user.has_perm(
-                "alsijil.edit_lessondocumentation", lesson_period
+                "alsijil.edit_lessondocumentation", register_object
             ):
                 with reversion.create_revision():
                     reversion.set_user(request.user)
@@ -146,27 +167,33 @@ 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()
 
-                    # Iterate over personal notes and carry changed absences to following lessons
-                    for instance in instances:
-                        instance.person.mark_absent(
-                            wanted_week[lesson_period.period.weekday],
-                            lesson_period.period.period + 1,
-                            instance.absent,
-                            instance.excused,
-                            instance.excuse_type,
-                        )
+                    if not isinstance(register_object, Event):
+                        # Iterate over personal notes
+                        # and carry changed absences to following lessons
+                        for instance in instances:
+                            instance.person.mark_absent(
+                                wanted_week[register_object.period.weekday],
+                                register_object.period.period + 1,
+                                instance.absent,
+                                instance.excused,
+                                instance.excuse_type,
+                            )
 
                 messages.success(request, _("The personal notes have been saved."))
 
@@ -376,7 +403,12 @@ 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