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 5748e84a69f478a8a26d0a51cb54cdc8d981a992..6545bf9c3a79d8ab1efaf8e7544c30451bc4b508 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -144,6 +144,7 @@
                 <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>
@@ -172,6 +173,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/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 6e7c704073c918d4a548fab948e25260bfcc71e9..8cd9938b62e4bd39c0feead34b46fe4360987ec0 100644
--- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
+++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
@@ -66,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">
@@ -76,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>
 
@@ -91,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>
@@ -228,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>
@@ -271,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>
@@ -349,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 %}
@@ -360,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 574c2bd06e2482947079334f3274665bbb50a05d..efad0b58c07a8c87cd258e14f728f71c9340786a 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
 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(
@@ -99,6 +103,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()
 
@@ -109,8 +115,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
@@ -324,6 +338,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),
@@ -331,6 +353,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:
@@ -349,6 +384,7 @@ 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
@@ -439,3 +475,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.")