diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index dde0bc501f3a3aa4726561034625c52742859ac5..f27596eef71d1c934296e6643b7af280558320c1 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -11,7 +11,13 @@ from material import Layout, Row
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.core.models import Group, Person
 
-from .models import ExtraMark, LessonDocumentation, PersonalNote, PersonalNoteFilter
+from .models import (
+    ExcuseType,
+    ExtraMark,
+    LessonDocumentation,
+    PersonalNote,
+    PersonalNoteFilter,
+)
 
 
 class LessonDocumentationForm(forms.ModelForm):
@@ -28,7 +34,7 @@ class LessonDocumentationForm(forms.ModelForm):
 class PersonalNoteForm(forms.ModelForm):
     class Meta:
         model = PersonalNote
-        fields = ["absent", "late", "excused", "extra_marks", "remarks"]
+        fields = ["absent", "late", "excused", "excuse_type", "extra_marks", "remarks"]
 
     person_name = forms.CharField(disabled=True)
 
@@ -47,10 +53,7 @@ class SelectForm(forms.Form):
     layout = Layout(Row("group", "teacher"))
 
     group = forms.ModelChoiceField(
-        queryset=None,
-        label=_("Group"),
-        required=False,
-        widget=Select2Widget,
+        queryset=None, label=_("Group"), required=False, widget=Select2Widget,
     )
     teacher = forms.ModelChoiceField(
         queryset=Person.objects.annotate(
@@ -81,8 +84,10 @@ class SelectForm(forms.Form):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.fields["group"].queryset = Group.objects.for_current_school_term_or_all().annotate(lessons_count=Count("lessons")).filter(
-            lessons_count__gt=0
+        self.fields["group"].queryset = (
+            Group.objects.for_current_school_term_or_all()
+            .annotate(lessons_count=Count("lessons"))
+            .filter(lessons_count__gt=0)
         )
 
 
@@ -124,3 +129,11 @@ class ExtraMarkForm(forms.ModelForm):
     class Meta:
         model = ExtraMark
         fields = ["short_name", "name"]
+
+
+class ExcuseTypeForm(forms.ModelForm):
+    layout = Layout("short_name", "name")
+
+    class Meta:
+        model = ExcuseType
+        fields = ["short_name", "name"]
diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py
index e18a62352ebe850649baec923d4857734b6b89c9..081c1ec2abca720d1f8e4eda519ceae4a931d80b 100644
--- a/aleksis/apps/alsijil/menus.py
+++ b/aleksis/apps/alsijil/menus.py
@@ -36,6 +36,12 @@ MENUS = {
                     "icon": "filter_list",
                     "validators": ["menu_generator.validators.is_superuser"],
                 },
+                {
+                    "name": _("Excuse types"),
+                    "url": "excuse_types",
+                    "icon": "label",
+                    "validators": ["menu_generator.validators.is_superuser"],
+                },
                 {
                     "name": _("Extra marks"),
                     "url": "extra_marks",
diff --git a/aleksis/apps/alsijil/migrations/0002_excuse_type.py b/aleksis/apps/alsijil/migrations/0002_excuse_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e4df12f7219969a1059abcd0757be1999a4fb6a
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0002_excuse_type.py
@@ -0,0 +1,73 @@
+# Generated by Django 3.0.8 on 2020-07-10 10:46
+
+import django.contrib.postgres.fields.jsonb
+import django.contrib.sites.managers
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("sites", "0002_alter_domain_unique"),
+        ("alsijil", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="ExcuseType",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "extended_data",
+                    django.contrib.postgres.fields.jsonb.JSONField(
+                        default=dict, editable=False
+                    ),
+                ),
+                (
+                    "short_name",
+                    models.CharField(
+                        max_length=255, unique=True, verbose_name="Short name"
+                    ),
+                ),
+                (
+                    "name",
+                    models.CharField(max_length=255, unique=True, verbose_name="Name"),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Excuse type",
+                "verbose_name_plural": "Excuse types",
+                "ordering": ["name"],
+            },
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
+        ),
+        migrations.AddField(
+            model_name="personalnote",
+            name="excuse_type",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="alsijil.ExcuseType",
+                verbose_name="Excuse type",
+            ),
+        ),
+    ]
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index d0129090a5faf99547695eb4568748d996d5274f..ecba9f773f54ba1343fbf86ab2cd2fc7003a760b 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -8,7 +8,7 @@ from calendarweek import CalendarWeek
 from aleksis.apps.chronos.models import LessonPeriod
 from aleksis.core.models import Group, Person
 
-from .models import ExtraMark, LessonDocumentation, PersonalNote
+from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 
 
 @Person.method
@@ -18,6 +18,7 @@ def mark_absent(
     from_period: int = 0,
     absent: bool = True,
     excused: bool = False,
+    excuse_type: Optional[ExcuseType] = None,
     remarks: str = "",
 ):
     """Mark a person absent for all lessons in a day, optionally starting with a selected period number.
@@ -44,7 +45,7 @@ def mark_absent(
             person=self,
             lesson_period=lesson_period,
             week=wanted_week.week,
-            defaults={"absent": absent, "excused": excused},
+            defaults={"absent": absent, "excused": excused, "excuse_type": excuse_type},
         )
 
         if remarks:
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 1f0b1a7191395dc306cfdfbec06cda52bf26d16f..76b3b8a98c4ed20a97d89acf906d66568afc6aeb 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -8,6 +8,30 @@ def isidentifier(value: str) -> bool:
     return value.isidentifier()
 
 
+class ExcuseType(ExtensibleModel):
+    """An type of excuse.
+
+    Can be used to count different types of absences separately.
+    """
+
+    short_name = models.CharField(
+        max_length=255, unique=True, verbose_name=_("Short name")
+    )
+    name = models.CharField(max_length=255, unique=True, verbose_name=_("Name"))
+
+    def __str__(self):
+        return f"{self.name} ({self.short_name})"
+
+    @property
+    def count_label(self):
+        return f"{self.short_name}_count"
+
+    class Meta:
+        ordering = ["name"]
+        verbose_name = _("Excuse type")
+        verbose_name_plural = _("Excuse types")
+
+
 class PersonalNote(ExtensibleModel):
     """A personal note about a single person.
 
@@ -27,6 +51,13 @@ class PersonalNote(ExtensibleModel):
     absent = models.BooleanField(default=False)
     late = models.IntegerField(default=0)
     excused = models.BooleanField(default=False)
+    excuse_type = models.ForeignKey(
+        ExcuseType,
+        on_delete=models.SET_NULL,
+        null=True,
+        blank=True,
+        verbose_name=_("Excuse type"),
+    )
 
     remarks = models.CharField(max_length=200, blank=True)
 
@@ -34,6 +65,11 @@ class PersonalNote(ExtensibleModel):
         "ExtraMark", null=True, blank=True, verbose_name=_("Extra marks")
     )
 
+    def save(self, *args, **kwargs):
+        if self.excuse_type:
+            self.excused = True
+        super().save(*args, **kwargs)
+
     class Meta:
         verbose_name = _("Personal note")
         verbose_name_plural = _("Personal notes")
diff --git a/aleksis/apps/alsijil/static/css/alsijil/full_register.css b/aleksis/apps/alsijil/static/css/alsijil/full_register.css
index 2c7327003c36e7dde492103eb5ad1b4cb5a65801..9a3dc493aa468c989ed766817436d20b40cd0bff 100644
--- a/aleksis/apps/alsijil/static/css/alsijil/full_register.css
+++ b/aleksis/apps/alsijil/static/css/alsijil/full_register.css
@@ -1,4 +1,4 @@
-table.small-print {
+table.small-print, td.small-print, th.small-print {
     font-size: 10pt;
 }
 
@@ -25,7 +25,7 @@ tr.lessons-day-first {
     border-top: 3px solid rgba(0, 0, 0, 0.3);
 }
 
-th.lessons-day-head {
+th.lessons-day-head, td.rotate, th.rotate {
     text-align: center;
     transform: rotate(-90deg);
 }
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index c3b393b61d8b513157c7984940624394c9e8026f..866da69e08affc731f76d56577aff7e91dee6bfe 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -37,3 +37,23 @@ class ExtraMarkTable(tables.Table):
         text=_("Delete"),
         attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
     )
+
+
+class ExcuseTypeTable(tables.Table):
+    class Meta:
+        attrs = {"class": "highlight"}
+
+    name = tables.LinkColumn("edit_excuse_type", args=[A("id")])
+    short_name = tables.Column()
+    edit = tables.LinkColumn(
+        "edit_excuse_type",
+        args=[A("id")],
+        text=_("Edit"),
+        attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}},
+    )
+    delete = tables.LinkColumn(
+        "delete_excuse_type",
+        args=[A("id")],
+        text=_("Delete"),
+        attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
+    )
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
index b819c932233df2927db8f6ddd2f424043b027078..f803493bf3d75c28f8a094206d8fcb13a33ecdab 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -156,6 +156,7 @@
                 <th>{% blocktrans %}Absent{% endblocktrans %}</th>
                 <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
                 <th>{% blocktrans %}Excused{% endblocktrans %}</th>
+                <th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
                 <th>{% blocktrans %}Extra marks{% endblocktrans %}</th>
                 <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
               </tr>
@@ -185,6 +186,14 @@
                       <span></span>
                     </label>
                   </td>
+                  <td>
+                    <div class="input-field">
+                      {{ form.excuse_type }}
+                      <label for="{{ form.excuse_type.id_for_label }}">
+                        {% trans "Excuse type" %}
+                      </label>
+                    </div>
+                  </td>
                   <td>
                     {% for group, items in form.extra_marks|select_options %}
                       {% for choice, value, selected in items %}
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 5d26d3d294c4e8f4137151b7add4ef0a43c87a2a..594e876b65f561ff8489fae3226bbeacc24bbd98 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -88,20 +88,20 @@
               {% blocktrans %}Personal notes{% endblocktrans %}
             </span>
             {% for person in persons %}
-              <h5 class="card-title">{{ person.full_name }}</h5>
+              <h5 class="card-title">{{ person.person.full_name }}</h5>
               <p class="card-text">
-                {% trans "Absent" %}: {{ person.absences_count }}
-                ({{ person.unexcused_count }} {% trans "unexcused" %})
+                {% trans "Absent" %}: {{ person.person.absences_count }}
+                ({{ person.person.unexcused_count }} {% trans "unexcused" %})
               </p>
               <p class="card-text">
-                {% trans "Summed up tardiness" %}: {{ person.tardiness_sum }}'
+                {% trans "Summed up tardiness" %}: {{ person.person.tardiness_sum }}'
               </p>
               {% for extra_mark in extra_marks %}
                 <p class="card-text">
-                  {{ extra_mark.name }}: {{ person|get_dict:extra_mark.count_label }}
+                  {{ extra_mark.name }}: {{ person.person|get_dict:extra_mark.count_label }}
                 </p>
               {% endfor %}
-              {% for note in person.personal_notes|only_week:week %}
+              {% for note in person.personal_notes %}
                 {% if note.remarks %}
                   <blockquote>
                     {{ note.remarks }}
@@ -123,11 +123,11 @@
     <div class="card red darken-1">
       <div class="card-content white-text">
         <span class="card-title">
-          {% blocktrans %}No group selected{% endblocktrans %}
+          {% blocktrans %}No lessons available{% endblocktrans %}
         </span>
         <p>
           {% blocktrans %}
-            There are no lessons for the selected group, teacher or time.
+            There are no lessons for the selected group or teacher in this week.
           {% endblocktrans %}
         </p>
       </div>
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html
new file mode 100644
index 0000000000000000000000000000000000000000..6fc6faefb2543cecf32b02e7dcb7ea2a40f3d73b
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html
@@ -0,0 +1,18 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block browser_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  {% include "alsijil/excuse_type/warning.html" %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..78396ed66264cc19abdac2085d1cc89ff931bb38
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html
@@ -0,0 +1,17 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block browser_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..2be1f28c96e70f35e63fe4f5cefb50c232e38e0a
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
@@ -0,0 +1,20 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n %}
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  {% include "alsijil/excuse_type/warning.html" %}
+
+  <a class="btn green waves-effect waves-light" href="{% url 'create_excuse_type' %}">
+    <i class="material-icons left">add</i>
+    {% trans "Create excuse type" %}
+  </a>
+
+  {% render_table table %}
+{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html
new file mode 100644
index 0000000000000000000000000000000000000000..d90d2e8205b1c91c18e74e02654fde3daebc4971
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+<div class="alert warning">
+  <p>
+    <i class="material-icons left">warning</i>
+    {% blocktrans %}
+      This function should only be used to define alternatives to the default excuse which also will be counted extra.
+      Don't use this to create a default excuse or if you don't divide between different types of excuse.
+    {% endblocktrans %}
+  </p>
+</div>
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
index 6584142bf36e2828471bbb73367442c80f572aee..6deaa3891c480026b22fbad2cfc75a4f2b260110 100644
--- a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
@@ -1,6 +1,6 @@
 {% load i18n %}
 {% for note in notes %}
   <span class="{% if note.excused %}green-text{% else %}red-text{% endif %}">{{ note.person }}
-    {% if note.excused %}{% trans "(e)" %}{% else %}{% trans "(u)" %}{% endif %}{% if not forloop.last %},{% endif %}
+    {% if note.excused %}{% if note.excuse_type %}({{ note.excuse_type.short_name }}){% else %}{% trans "(e)" %}{% endif %}{% else %}{% trans "(u)" %}{% endif %}{% if not forloop.last %},{% endif %}
   </span>
 {% endfor %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
index 18298344c2083f45fb16543ffdbffd208d96faed..6a81d401f9cfacc7bbbb4da1305b687afc16658a 100644
--- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
+++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
@@ -14,6 +14,8 @@
 
   <div class="center-align">
     <h1>{% trans 'Class register' %}</h1>
+    <h5>{{ school_term }}</h5>
+    <p>({{ school_term.date_start }}–{{ school_term.date_end }})</p>
     {% static "img/aleksis-banner.svg" as aleksis_banner %}
     <img src="{% firstof request.site.preferences.theme__logo.url aleksis_banner %}"
          alt="{{ request.site.preferences.general__title }} – Logo" class="max-size-600 center">
@@ -64,6 +66,40 @@
 
   <div class="page-break">&nbsp;</div>
 
+  <h4>{% trans "Abbreviations" %}</h4>
+
+  <h5>{% trans "General" %}</h5>
+
+  <ul class="collection">
+    <li class="collection-item">
+      <strong>(a)</strong> {% trans "Absent" %}
+    </li>
+    <li class="collection-item">
+      <strong>(b)</strong> {% trans "Late" %}
+    </li>
+    <li class="collection-item">
+      <strong>(u)</strong> {% trans "Unexcused" %}
+    </li>
+    <li class="collection-item">
+      <strong>(e)</strong> {% trans "Excused" %}
+    </li>
+  </ul>
+
+  {% if excuse_types %}
+    <h5>{% trans "Custom excuse types" %}</h5>
+
+    <ul class="collection">
+      {% for excuse_type in excuse_types %}
+        <li class="collection-item">
+          <strong>({{ excuse_type.short_name }})</strong> {{ excuse_type.name }}
+        </li>
+      {% endfor %}
+    </ul>
+  {% endif %}
+
+  <div class="page-break">&nbsp;</div>
+
+
   <h4>{% trans 'Persons in group' %} {{ group.name }}</h4>
 
   <table id="persons">
@@ -74,9 +110,13 @@
       <th>{% trans 'First name' %}</th>
       <th>{% trans 'Sex' %}</th>
       <th>{% trans 'Date of birth' %}</th>
-      <th>{% trans 'Absences' %}</th>
-      <th>{% trans 'Unexcused' %}</th>
-      <th>{% trans 'Tard.' %}</th>
+      <th>{% trans '(a)' %}</th>
+      <th>{% trans "(e)" %}</th>
+      {% for excuse_type in excuse_types %}
+        <th>({{ excuse_type.short_name }})</th>
+      {% endfor %}
+      <th>{% trans '(u)' %}</th>
+      <th>{% trans '(b)' %}</th>
       {% for extra_mark in extra_marks %}
         <th>{{ extra_mark.short_name }}</th>
       {% endfor %}
@@ -92,6 +132,10 @@
         <td>{{ person.get_sex_display }}</td>
         <td>{{ person.date_of_birth }}</td>
         <td>{{ person.absences_count }}</td>
+        <td>{{ person.excused }}</td>
+        {% for excuse_type in excuse_types %}
+          <td>{{ person|get_dict:excuse_type.count_label }}</td>
+        {% endfor %}
         <td>{{ person.unexcused }}</td>
         <td>{{ person.tardiness }}'</td>
         {% for extra_mark in extra_marks %}
@@ -123,8 +167,8 @@
         <tr>
           <td>{{ lesson.subject.name }}</td>
           <td>{{ lesson.teachers.all|join:', ' }}</td>
-          <td>{{ lesson.date_start }}</td>
-          <td>{{ lesson.date_end }}</td>
+          <td>{{ lesson.validity.date_start }}</td>
+          <td>{{ lesson.validity.date_end }}</td>
           <td>{{ lesson.lesson_periods.count }}</td>
         </tr>
       {% endfor %}
@@ -156,8 +200,8 @@
             <td>{{ child_group.name }}</td>
             <td>{{ lesson.subject.name }}</td>
             <td>{{ lesson.teachers.all|join:', ' }}</td>
-            <td>{{ lesson.date_start }}</td>
-            <td>{{ lesson.date_end }}</td>
+            <td>{{ lesson.validity.date_start }}</td>
+            <td>{{ lesson.validity.date_end }}</td>
             <td>{{ lesson.lesson_periods.count }}</td>
           </tr>
         {% endfor %}
@@ -232,21 +276,28 @@
 
     <h5>{% trans 'Absences and tardiness' %}</h5>
     <table>
-      <thead>
       <tr>
-        <th>{% trans 'Absences' %}</th>
-        <th>{% trans 'Unexcused' %}</th>
-        <th>{% trans 'Tardiness' %}</th>
+        <th colspan="2">{% trans 'Absences' %}</th>
+        <td>{{ person.absences_count }}</td>
       </tr>
-      </thead>
-
-      <tbody>
       <tr>
-        <td>{{ person.absences_count }}</td>
+        <td rowspan="{{ excuse_types.count|add:2 }}" style="width: 16mm;"
+            class="rotate small-print">{% trans "thereof" %}</td>
+        <th>{% trans 'Excused' %}</th>
+        <td>{{ person.excused }}</td>
+      </tr>
+      {% for excuse_type in excuse_types %}
+        <th>{{ excuse_type.name }}</th>
+        <td>{{ person|get_dict:excuse_type.count_label }}</td>
+      {% endfor %}
+      <tr>
+        <th>{% trans 'Unexcused' %}</th>
         <td>{{ person.unexcused }}</td>
+      </tr>
+      <tr>
+        <th colspan="2">{% trans 'Tardiness' %}</th>
         <td>{{ person.tardiness }}'</td>
       </tr>
-      </tbody>
     </table>
 
     {% if extra_marks %}
@@ -287,8 +338,12 @@
             <td>
               {% if note.absent %}
                 {% trans 'Yes' %}
-                {% if note.escused %}
-                  ({% trans 'e' %})
+                {% if note.excused %}
+                  {% if note.excuse_type %}
+                    ({{ note.excuse_type.short_name }})
+                  {% else %}
+                    ({% trans 'e' %})
+                  {% endif %}
                 {% endif %}
               {% endif %}
             </td>
@@ -370,8 +425,12 @@
                       {{ note.person.last_name }}, {{ note.person.first_name|slice:"0:1" }}.
                       {% if note.excused %}
                         <span class="lesson-note-excused">
-                               ({% trans 'e' %})
-                              </span>
+                          {% if note.excuse_type %}
+                            ({{ note.excuse_type.short_name }})
+                          {% else %}
+                            ({% trans 'e' %})
+                          {% endif %}
+                        </span>
                       {% endif %}
                       </span>
                   {% endif %}
@@ -381,8 +440,12 @@
                       ({{ note.late }}′)
                       {% if note.excused %}
                         <span class="lesson-note-excused">
-                               ({% trans 'e' %})
-                              </span>
+                          {% if note.excuse_type %}
+                            ({{ note.excuse_type.short_name }})
+                          {% else %}
+                            ({% trans 'e' %})
+                          {% endif %}
+                        </span>
                       {% endif %}
                       </span>
                   {% endif %}
diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py
index 7f022fbb127ecfe523da6840041b7b75f043f849..12c12bc444f9e91a5c05611133ba567eea2f5b1d 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -57,4 +57,20 @@ urlpatterns = [
         views.ExtraMarkDeleteView.as_view(),
         name="delete_extra_mark",
     ),
+    path("excuse_types/", views.ExcuseTypeListView.as_view(), name="excuse_types"),
+    path(
+        "excuse_types/create/",
+        views.ExcuseTypeCreateView.as_view(),
+        name="create_excuse_type",
+    ),
+    path(
+        "excuse_types/<int:pk>/edit/",
+        views.ExcuseTypeEditView.as_view(),
+        name="edit_excuse_type",
+    ),
+    path(
+        "excuse_types/<int:pk>/delete/",
+        views.ExcuseTypeDeleteView.as_view(),
+        name="delete_excuse_type",
+    ),
 ]
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 4231ea11e892f86d9cc1e5ea8f443dd2815f10c8..1e109d7e55e0f22300b7a73f60bc65a828d23705 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -21,6 +21,7 @@ from aleksis.core.models import Group, Person, SchoolTerm
 from aleksis.core.util import messages
 
 from .forms import (
+    ExcuseTypeForm,
     ExtraMarkForm,
     LessonDocumentationForm,
     PersonalNoteFilterForm,
@@ -28,8 +29,8 @@ from .forms import (
     RegisterAbsenceForm,
     SelectForm,
 )
-from .models import ExtraMark, LessonDocumentation, PersonalNoteFilter
-from .tables import ExtraMarkTable, PersonalNoteFilterTable
+from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNoteFilter
+from .tables import ExcuseTypeTable, ExtraMarkTable, PersonalNoteFilterTable
 
 
 def lesson(
@@ -103,6 +104,8 @@ def lesson(
         if lesson_documentation_form.is_valid():
             lesson_documentation_form.save()
 
+            messages.success(request, _("The lesson documentation has been saved."))
+
         if personal_note_formset.is_valid():
             instances = personal_note_formset.save()
 
@@ -113,8 +116,16 @@ def lesson(
                     lesson_period.period.period + 1,
                     instance.absent,
                     instance.excused,
+                    instance.excuse_type,
                 )
 
+            messages.success(request, _("The personal notes have been saved."))
+
+            # Regenerate form here to ensure that programmatically changed data will be shown correctly
+            personal_note_formset = PersonalNoteFormSet(
+                None, queryset=persons_qs, prefix="personal_notes"
+            )
+
     context["lesson_documentation"] = lesson_documentation
     context["lesson_documentation_form"] = lesson_documentation_form
     context["personal_note_formset"] = personal_note_formset
@@ -189,16 +200,25 @@ def week_view(
 
     if lesson_periods:
         # Aggregate all personal notes for this group and week
-        persons = (
-            Person.objects.filter(is_active=True)
-            .filter(member_of__lessons__lesson_periods__in=lesson_periods)
-            .distinct()
+        lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
+
+        persons_qs = Person.objects.filter(is_active=True)
+
+        if 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.distinct()
             .prefetch_related("personal_notes")
             .annotate(
                 absences_count=Count(
                     "personal_notes",
                     filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods,
+                        personal_notes__lesson_period__in=lesson_periods_pk,
                         personal_notes__week=wanted_week.week,
                         personal_notes__absent=True,
                     ),
@@ -207,7 +227,7 @@ def week_view(
                 unexcused_count=Count(
                     "personal_notes",
                     filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods,
+                        personal_notes__lesson_period__in=lesson_periods_pk,
                         personal_notes__week=wanted_week.week,
                         personal_notes__absent=True,
                         personal_notes__excused=False,
@@ -217,7 +237,7 @@ def week_view(
                 tardiness_sum=Subquery(
                     Person.objects.filter(
                         pk=OuterRef("pk"),
-                        personal_notes__lesson_period__in=lesson_periods,
+                        personal_notes__lesson_period__in=lesson_periods_pk,
                         personal_notes__week=wanted_week.week,
                     )
                     .distinct()
@@ -228,12 +248,12 @@ def week_view(
         )
 
         for extra_mark in ExtraMark.objects.all():
-            persons = persons.annotate(
+            persons_qs = persons_qs.annotate(
                 **{
                     extra_mark.count_label: Count(
                         "personal_notes",
                         filter=Q(
-                            personal_notes__lesson_period__in=lesson_periods,
+                            personal_notes__lesson_period__in=lesson_periods_pk,
                             personal_notes__week=wanted_week.week,
                             personal_notes__extra_marks=extra_mark,
                         ),
@@ -242,6 +262,16 @@ def week_view(
                 }
             )
 
+        persons = []
+        for person in persons_qs:
+            persons.append(
+                {
+                    "person": person,
+                    "personal_notes": person.personal_notes.filter(
+                        week=wanted_week.week, lesson_period__in=lesson_periods_pk
+                    ),
+                }
+            )
     else:
         persons = None
 
@@ -325,6 +355,14 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
         absences_count=Count(
             "personal_notes__absent", filter=Q(personal_notes__absent=True)
         ),
+        excused=Count(
+            "personal_notes__absent",
+            filter=Q(
+                personal_notes__absent=True,
+                personal_notes__excused=True,
+                personal_notes__excuse_type__isnull=True,
+            ),
+        ),
         unexcused=Count(
             "personal_notes__absent",
             filter=Q(personal_notes__absent=True, personal_notes__excused=False),
@@ -341,6 +379,19 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
             }
         )
 
+    for excuse_type in ExcuseType.objects.all():
+        persons = persons.annotate(
+            **{
+                excuse_type.count_label: Count(
+                    "personal_notes__absent",
+                    filter=Q(
+                        personal_notes__absent=True,
+                        personal_notes__excuse_type=excuse_type,
+                    ),
+                )
+            }
+        )
+
     # FIXME Move to manager
     personal_note_filters = PersonalNoteFilter.objects.all()
     for personal_note_filter in personal_note_filters:
@@ -356,9 +407,11 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
             }
         )
 
-    context["extra_marks"] = ExtraMark.objects.all()
+    context["school_term"] = current_school_term
     context["persons"] = persons
     context["personal_note_filters"] = personal_note_filters
+    context["excuse_types"] = ExcuseType.objects.all()
+    context["extra_marks"] = ExtraMark.objects.all()
     context["group"] = group
     context["weeks"] = weeks
     context["periods_by_day"] = periods_by_day
@@ -490,3 +543,44 @@ class ExtraMarkDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionM
     template_name = "core/pages/delete.html"
     success_url = reverse_lazy("extra_marks")
     success_message = _("The extra mark has been deleted.")
+
+
+class ExcuseTypeListView(SingleTableView, PermissionRequiredMixin):
+    """Table of all excuse types."""
+
+    model = ExcuseType
+    table_class = ExcuseTypeTable
+    permission_required = "core.view_excusetype"
+    template_name = "alsijil/excuse_type/list.html"
+
+
+class ExcuseTypeCreateView(AdvancedCreateView, PermissionRequiredMixin):
+    """Create view for excuse types."""
+
+    model = ExcuseType
+    form_class = ExcuseTypeForm
+    permission_required = "core.create_excusetype"
+    template_name = "alsijil/excuse_type/create.html"
+    success_url = reverse_lazy("excuse_types")
+    success_message = _("The excuse type has been created.")
+
+
+class ExcuseTypeEditView(AdvancedEditView, PermissionRequiredMixin):
+    """Edit view for excuse types."""
+
+    model = ExcuseType
+    form_class = ExcuseTypeForm
+    permission_required = "core.edit_excusetype"
+    template_name = "alsijil/excuse_type/edit.html"
+    success_url = reverse_lazy("excuse_types")
+    success_message = _("The excuse type has been saved.")
+
+
+class ExcuseTypeDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionMixin):
+    """Delete view for excuse types"""
+
+    model = ExcuseType
+    permission_required = "core.delete_excusetype"
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("excuse_types")
+    success_message = _("The excuse type has been deleted.")