diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 3d7f554eb2750784c3217eeaff4c09baae330520..88127e98c45d71fbe22ec9572fc04d89891489a7 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -11,7 +11,7 @@ from material import Layout, Row
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.core.models import Group, Person
 
-from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter
+from .models import ExcuseType, LessonDocumentation, PersonalNote, PersonalNoteFilter
 
 
 class LessonDocumentationForm(forms.ModelForm):
@@ -28,7 +28,7 @@ class LessonDocumentationForm(forms.ModelForm):
 class PersonalNoteForm(forms.ModelForm):
     class Meta:
         model = PersonalNote
-        fields = ["absent", "late", "excused", "remarks"]
+        fields = ["absent", "late", "excused", "excuse_type", "remarks"]
 
     person_name = forms.CharField(disabled=True)
 
@@ -47,10 +47,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 +78,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)
         )
 
 
@@ -116,3 +115,11 @@ class PersonalNoteFilterForm(forms.ModelForm):
     class Meta:
         model = PersonalNoteFilter
         fields = ["identifier", "description", "regex"]
+
+
+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 2b84cb6f7f70b644a34f79f170dfc4cb6751f773..c5fea59e3790fba0407d55d760434ac2e3dd0d3d 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"],
+                },
             ],
         }
     ]
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 57768babbc029e3da42359550120a01fb5cf6517..a09aa38c9be47c6a73e5d1e86caf6f375c9045e1 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 LessonDocumentation, PersonalNote
+from .models import ExcuseType, 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 eb1bc4eb67f37744e589c8daf9d496ff6913f6d3..d10243b0deabba12b49d4b3d28211be925f80f79 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,9 +51,21 @@ 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)
 
+    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 bad0dc9f3923421035d0701251012461df13fec8..3a6762c3744ead9d02e9917aa975b530b5e448f0 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -17,3 +17,23 @@ class PersonalNoteFilterTable(tables.Table):
         text=_("Edit"),
         attrs={"a": {"class": "btn-flat waves-effect waves-orange"}},
     )
+
+
+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 e7ece9d4ae5626eb27f7196243ac2d8ed97fda78..8cd338b597318a08d3a03bf31fb7b61507d68d54 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -133,9 +133,9 @@
         <div class="col s12">
           <div class="card">
             <div class="card-content">
-              <span class="card-title">
-                {% blocktrans %}Personal notes{% endblocktrans %}
-              </span>
+      <span class="card-title">
+        {% blocktrans %}Personal notes{% endblocktrans %}
+      </span>
               {% form form=personal_note_formset.management_form %}{% endform %}
 
               <table class="striped responsive-table alsijil-table">
@@ -145,10 +145,13 @@
                   <th>{% blocktrans %}Absent{% endblocktrans %}</th>
                   <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
                   <th>{% blocktrans %}Excused{% endblocktrans %}</th>
+                  <th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
                   <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
                 </tr>
                 </thead>
                 <tbody>
+                {% for form in personal_note_formset %}
+                <tbody>
                 {% for form in personal_note_formset %}
                   <tr>
                     {{ form.id }}
@@ -173,6 +176,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>
                       <div class="input-field">
                         {{ form.remarks }}
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 e42575fd07fc5ad520575643a1db2effa61ba66a..f1b94d845db471586524254cc582df7d3cebd2f0 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -88,15 +88,15 @@
               {% 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 note in person.personal_notes|only_week:week %}
+              {% for note in person.personal_notes %}
                 {% if note.remarks %}
                   <blockquote>
                     {{ note.remarks }}
@@ -118,11 +118,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 91747e2114c88056c7cfafb157b9b9cd4168946b..8cd9938b62e4bd39c0feead34b46fe4360987ec0 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>
     </tr>
     </thead>
 
@@ -89,6 +129,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>
       </tr>
@@ -117,8 +161,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 %}
@@ -150,8 +194,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 %}
@@ -226,21 +270,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>
 
     <h5>{% trans 'Relevant personal notes' %}</h5>
@@ -269,8 +320,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>
@@ -347,8 +402,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 %}
@@ -358,8 +417,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 4406a06852ae2489eaf5b84c66a28098d91f0010..89ab0ffe5f47c8cfebb015a6f76e597af4dd11dd 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -41,4 +41,20 @@ urlpatterns = [
         views.delete_personal_note_filter,
         name="delete_personal_note_filter",
     ),
+    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 0caa7674a2bd92cd4cc9faac61901a2376355754..1ca4b4cabeec1ea0cd5edbfb1588f9255ee2f55a 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -5,27 +5,31 @@ from django.core.exceptions import PermissionDenied
 from django.db.models import Count, Exists, F, OuterRef, Q, Subquery, Sum
 from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound
 from django.shortcuts import get_object_or_404, redirect, render
-from django.urls import reverse
+from django.urls import reverse, reverse_lazy
 from django.utils.translation import ugettext as _
 
 from calendarweek import CalendarWeek
-from django_tables2 import RequestConfig
+from django_tables2 import RequestConfig, SingleTableView
+from reversion.views import RevisionMixin
+from rules.contrib.views import PermissionRequiredMixin
 
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.apps.chronos.models import LessonPeriod, LessonSubstitution
 from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
+from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
 from aleksis.core.models import Group, Person, SchoolTerm
 from aleksis.core.util import messages
 
 from .forms import (
+    ExcuseTypeForm,
     LessonDocumentationForm,
     PersonalNoteFilterForm,
     PersonalNoteFormSet,
     RegisterAbsenceForm,
     SelectForm,
 )
-from .models import LessonDocumentation, PersonalNoteFilter
-from .tables import PersonalNoteFilterTable
+from .models import ExcuseType, LessonDocumentation, PersonalNoteFilter
+from .tables import ExcuseTypeTable, PersonalNoteFilterTable
 
 
 def lesson(
@@ -39,7 +43,9 @@ def lesson(
     if year and week and period_id:
         # Get a specific lesson period if provided in URL
         wanted_week = CalendarWeek(year=year, week=week)
-        lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(pk=period_id)
+        lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(
+            pk=period_id
+        )
     else:
         # Determine current lesson by current date and time
         lesson_period = (
@@ -64,8 +70,7 @@ def lesson(
 
     if (
         datetime.combine(
-            wanted_week[lesson_period.period.weekday],
-            lesson_period.period.time_start,
+            wanted_week[lesson_period.period.weekday], lesson_period.period.time_start,
         )
         > datetime.now()
         and not request.user.is_superuser
@@ -98,6 +103,8 @@ def lesson(
         if lesson_documentation_form.is_valid():
             lesson_documentation_form.save()
 
+            messages.success(request, _("The lesson documentation has been saved."))
+
         substitution = lesson_period.get_substitution()
         if not getattr(substitution, "cancelled", False):
             if personal_note_formset.is_valid():
@@ -110,8 +117,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
@@ -186,16 +201,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,
                     ),
@@ -204,7 +228,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,
@@ -214,7 +238,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()
@@ -223,6 +247,17 @@ 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
 
@@ -278,7 +313,11 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
         for week in weeks:
             day = week[lesson_period.period.weekday]
 
-            if lesson_period.lesson.validity.date_start <= day <= lesson_period.lesson.validity.date_end:
+            if (
+                lesson_period.lesson.validity.date_start
+                <= day
+                <= lesson_period.lesson.validity.date_end
+            ):
                 documentations = list(
                     filter(
                         lambda d: d.week == week.week,
@@ -299,13 +338,25 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
 
     persons = group.members.annotate(
         absences_count=Count(
-            "personal_notes__absent", filter=Q(personal_notes__absent=True) & ~Q(personal_notes__lesson_period__substitutions=Subquery(
+            "personal_notes__absent",
+            filter=Q(personal_notes__absent=True)
+            & ~Q(
+                personal_notes__lesson_period__substitutions=Subquery(
                     LessonSubstitution.objects.filter(
                         lesson_period__pk=OuterRef("personal_notes__lesson_period__pk"),
                         cancelled=True,
                         week=OuterRef("personal_notes__week"),
                     ).values("pk")
-            ))
+                )
+            ),
+        ),
+        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",
@@ -314,6 +365,19 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
         tardiness=Sum("personal_notes__late"),
     )
 
+    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:
@@ -329,8 +393,10 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
             }
         )
 
+    context["school_term"] = current_school_term
     context["persons"] = persons
     context["personal_note_filters"] = personal_note_filters
+    context["excuse_types"] = ExcuseType.objects.all()
     context["group"] = group
     context["weeks"] = weeks
     context["periods_by_day"] = periods_by_day
@@ -421,3 +487,44 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
 
     context["personal_note_filter"] = personal_note_filter
     return redirect("list_personal_note_filters")
+
+
+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.")
diff --git a/poetry.lock b/poetry.lock
index e0886108b572be79db7f2d7b316e6ca457d13d12..d0ecccb2126253bc14e4e9b40f71c0371e045768 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -68,11 +68,11 @@ extras = ["phonenumbers"]
 version = ">=3.0,<4.0"
 
 [package.dependencies.django-two-factor-auth]
-extras = ["sms", "yubikey", "call", "phonenumbers"]
+extras = ["sms", "phonenumbers", "call", "yubikey"]
 version = ">=1.11.0,<2.0.0"
 
 [package.dependencies.dynaconf]
-extras = ["yaml", "ini", "toml"]
+extras = ["yaml", "toml", "ini"]
 version = ">=2.0,<3.0"
 
 [package.extras]
@@ -1112,14 +1112,12 @@ category = "dev"
 description = "A Python utility / library to sort Python imports."
 name = "isort"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "4.3.21"
+python-versions = ">=3.6,<4.0"
+version = "5.0.3"
 
 [package.extras]
-pipfile = ["pipreqs", "requirementslib"]
-pyproject = ["toml"]
-requirements = ["pipreqs", "pip-api"]
-xdg_home = ["appdirs (>=1.4.0)"]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib", "tomlkit (>=0.5.3)"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
 
 [[package]]
 category = "dev"
@@ -1949,7 +1947,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
 testing = ["jaraco.itertools", "func-timeout"]
 
 [metadata]
-content-hash = "14697a0280b9caecec47c1bb76bc7c2a18fa04b715560b3d5699cf2efdce67f5"
+content-hash = "c948e61801e83c7105de786b85616a1b61957e6950cd649039bd67f87193559c"
 python-versions = "^3.7"
 
 [metadata.files]
@@ -2319,8 +2317,8 @@ importlib-metadata = [
     {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
 ]
 isort = [
-    {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
-    {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
+    {file = "isort-5.0.3-py3-none-any.whl", hash = "sha256:3fbfad425b0a08a2969c5e1821d88785c210a08656c029c28931a1620f2d0f12"},
+    {file = "isort-5.0.3.tar.gz", hash = "sha256:4c48d4cd773a6226baaaa176839e6f7ff82ef7c7842f6c54374fe2b14df4024b"},
 ]
 jinja2 = [
     {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
diff --git a/pyproject.toml b/pyproject.toml
index 0ea6dfb027ebbd37f2acdbff53861303b6695848..6db7e5c53cacd7c21225a23a9cbaafc154d5cb8f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,7 +44,7 @@ flake8-docstrings = "^1.5.0"
 flake8-rst-docstrings = "^0.0.13"
 black = "^19.10b0"
 flake8-black = "^0.2.0"
-isort = "^4.3.21"
+isort = "^5.0.0"
 flake8-isort = "^3.0.0"
 pytest-cov = "^2.8.1"
 pytest-sugar = "^0.9.2"