diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index c09381a0d0012fe5ae57f912996a570677336d31..92a48aa48e254a9c12a53328c20ea334b43fca93 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -1,15 +1,14 @@
 from datetime import datetime
 
-from aleksis.apps.chronos.managers import TimetableType
 from django import forms
 from django.core.exceptions import ValidationError
 from django.db.models import Count
 from django.utils.translation import gettext_lazy as _
 
 from django_select2.forms import Select2Widget
+from material import Layout, Row
 
-from material import Row, Layout
-
+from aleksis.apps.chronos.managers import TimetableType
 from aleksis.core.models import Group, Person
 
 from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter
@@ -43,15 +42,17 @@ class SelectForm(forms.Form):
     layout = Layout(Row("group", "teacher"))
 
     group = forms.ModelChoiceField(
-        queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter(lessons_count__gt=0),
+        queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter(
+            lessons_count__gt=0
+        ),
         label=_("Group"),
         required=False,
         widget=Select2Widget,
     )
     teacher = forms.ModelChoiceField(
-        queryset=Person.objects.annotate(lessons_count=Count("lessons_as_teacher")).filter(
-            lessons_count__gt=0
-        ),
+        queryset=Person.objects.annotate(
+            lessons_count=Count("lessons_as_teacher")
+        ).filter(lessons_count__gt=0),
         label=_("Teacher"),
         required=False,
         widget=Select2Widget,
@@ -60,7 +61,7 @@ class SelectForm(forms.Form):
     def clean(self) -> dict:
         data = super().clean()
 
-        if data.get("group") and not data.get("teacher") :
+        if data.get("group") and not data.get("teacher"):
             type_ = TimetableType.GROUP
             instance = data["group"]
         elif data.get("teacher") and not data.get("group"):
@@ -75,24 +76,22 @@ class SelectForm(forms.Form):
         data["instance"] = instance
         return data
 
+
 PersonalNoteFormSet = forms.modelformset_factory(
     PersonalNote, form=PersonalNoteForm, max_num=0, extra=0
 )
 
 
 class RegisterAbsenceForm(forms.Form):
-    layout = Layout(Row("date_start", "date_end"),
-                    Row("from_period"),
-                    Row("absent", "excused"),
-                    Row("person"),
-                    Row("remarks")
-                    )
-    date_start = forms.DateField(
-        label=_("Start date"), initial=datetime.today
-    )
-    date_end = forms.DateField(
-        label=_("End date"), initial=datetime.today
+    layout = Layout(
+        Row("date_start", "date_end"),
+        Row("from_period"),
+        Row("absent", "excused"),
+        Row("person"),
+        Row("remarks"),
     )
+    date_start = forms.DateField(label=_("Start date"), initial=datetime.today)
+    date_end = forms.DateField(label=_("End date"), initial=datetime.today)
     from_period = forms.IntegerField(label=_("From period"), initial=0, min_value=0)
     person = forms.ModelChoiceField(
         label=_("Person"), queryset=Person.objects.all(), widget=Select2Widget
diff --git a/aleksis/apps/alsijil/migrations/0001_initial.py b/aleksis/apps/alsijil/migrations/0001_initial.py
index 3b396eaaa92bbf3bdef22379f94ad6055094eab7..ada9a2f3fde844dab66bc31ec2e0c1bc1fc697c9 100644
--- a/aleksis/apps/alsijil/migrations/0001_initial.py
+++ b/aleksis/apps/alsijil/migrations/0001_initial.py
@@ -1,10 +1,11 @@
 # Generated by Django 3.0.6 on 2020-05-29 10:29
 
-import aleksis.apps.alsijil.models
 import django.contrib.postgres.fields.jsonb
 import django.contrib.sites.managers
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
+
+import aleksis.apps.alsijil.models
 
 
 class Migration(migrations.Migration):
@@ -12,74 +13,195 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('core', '0001_initial'),
-        ('chronos', '0001_initial'),
-        ('sites', '0002_alter_domain_unique'),
+        ("core", "0001_initial"),
+        ("chronos", "0001_initial"),
+        ("sites", "0002_alter_domain_unique"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='PersonalNoteFilter',
+            name="PersonalNoteFilter",
             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)),
-                ('identifier', models.CharField(max_length=30, unique=True, validators=[aleksis.apps.alsijil.models.isidentifier], verbose_name='Identifier')),
-                ('description', models.CharField(blank=True, max_length=60, unique=True, verbose_name='Description')),
-                ('regex', models.CharField(max_length=100, unique=True, verbose_name='Match expression')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+                (
+                    "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
+                    ),
+                ),
+                (
+                    "identifier",
+                    models.CharField(
+                        max_length=30,
+                        unique=True,
+                        validators=[aleksis.apps.alsijil.models.isidentifier],
+                        verbose_name="Identifier",
+                    ),
+                ),
+                (
+                    "description",
+                    models.CharField(
+                        blank=True,
+                        max_length=60,
+                        unique=True,
+                        verbose_name="Description",
+                    ),
+                ),
+                (
+                    "regex",
+                    models.CharField(
+                        max_length=100, unique=True, verbose_name="Match expression"
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Personal note filter',
-                'verbose_name_plural': 'Personal note filters',
-                'ordering': ['identifier'],
+                "verbose_name": "Personal note filter",
+                "verbose_name_plural": "Personal note filters",
+                "ordering": ["identifier"],
             },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
-            ],
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
         migrations.CreateModel(
-            name='PersonalNote',
+            name="PersonalNote",
             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)),
-                ('week', models.IntegerField()),
-                ('absent', models.BooleanField(default=False)),
-                ('late', models.IntegerField(default=0)),
-                ('excused', models.BooleanField(default=False)),
-                ('remarks', models.CharField(blank=True, max_length=200)),
-                ('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='chronos.LessonPeriod')),
-                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='core.Person')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+                (
+                    "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
+                    ),
+                ),
+                ("week", models.IntegerField()),
+                ("absent", models.BooleanField(default=False)),
+                ("late", models.IntegerField(default=0)),
+                ("excused", models.BooleanField(default=False)),
+                ("remarks", models.CharField(blank=True, max_length=200)),
+                (
+                    "lesson_period",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="personal_notes",
+                        to="chronos.LessonPeriod",
+                    ),
+                ),
+                (
+                    "person",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="personal_notes",
+                        to="core.Person",
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Personal note',
-                'verbose_name_plural': 'Personal notes',
-                'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period', 'person__last_name', 'person__first_name'],
-                'unique_together': {('lesson_period', 'week', 'person')},
+                "verbose_name": "Personal note",
+                "verbose_name_plural": "Personal notes",
+                "ordering": [
+                    "lesson_period__lesson__date_start",
+                    "week",
+                    "lesson_period__period__weekday",
+                    "lesson_period__period__period",
+                    "person__last_name",
+                    "person__first_name",
+                ],
+                "unique_together": {("lesson_period", "week", "person")},
             },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
-            ],
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
         migrations.CreateModel(
-            name='LessonDocumentation',
+            name="LessonDocumentation",
             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)),
-                ('week', models.IntegerField()),
-                ('topic', models.CharField(blank=True, max_length=200, verbose_name='Lesson topic')),
-                ('homework', models.CharField(blank=True, max_length=200, verbose_name='Homework')),
-                ('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documentations', to='chronos.LessonPeriod')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+                (
+                    "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
+                    ),
+                ),
+                ("week", models.IntegerField()),
+                (
+                    "topic",
+                    models.CharField(
+                        blank=True, max_length=200, verbose_name="Lesson topic"
+                    ),
+                ),
+                (
+                    "homework",
+                    models.CharField(
+                        blank=True, max_length=200, verbose_name="Homework"
+                    ),
+                ),
+                (
+                    "lesson_period",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="documentations",
+                        to="chronos.LessonPeriod",
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Lesson documentation',
-                'verbose_name_plural': 'Lesson documentations',
-                'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period'],
-                'unique_together': {('lesson_period', 'week')},
+                "verbose_name": "Lesson documentation",
+                "verbose_name_plural": "Lesson documentations",
+                "ordering": [
+                    "lesson_period__lesson__date_start",
+                    "week",
+                    "lesson_period__period__weekday",
+                    "lesson_period__period__period",
+                ],
+                "unique_together": {("lesson_period", "week")},
             },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
-            ],
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
     ]
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index ac5d929a4f31e57bb40c6ad1b82ba2906e5f4834..98642c5f6d6d81b48fb004aa89bff4862f350d03 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -1,6 +1,6 @@
 from datetime import date
 
-from django.db.models import Exists, F, OuterRef, QuerySet
+from django.db.models import Exists, OuterRef, QuerySet
 from django.utils.translation import gettext as _
 
 from calendarweek import CalendarWeek
@@ -20,9 +20,8 @@ def mark_absent(
     excused: bool = False,
     remarks: str = "",
 ):
-    """ Mark a person absent for all lessons in a day, optionally starting with
-    a selected period number.
-    
+    """Mark a person absent for all lessons in a day, optionally starting with a selected period number.
+
     This function creates `PersonalNote` objects for every `LessonPeriod` the person
     participates in on the selected day and marks them as absent/excused.
 
@@ -32,7 +31,6 @@ def mark_absent(
     :Authors:
         - Dominik George <dominik.george@teckids.org>
     """
-
     wanted_week = CalendarWeek.from_date(day)
 
     # Get all lessons of this person on the specified day
@@ -59,18 +57,17 @@ def mark_absent(
 
 @LessonPeriod.method
 def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
-    """ Get all personal notes for that lesson in a specified week.
-    
+    """Get all personal notes for that lesson in a specified week.
+
     Returns all linked `PersonalNote` objects, filtered by the given weeek,
     creating those objects that haven't been created yet.
-    
+
     ..note:: Only available when AlekSIS-App-Alsijil is installed.
 
     :Date: 2019-11-09
     :Authors:
         - Dominik George <dominik.george@teckids.org>
     """
-
     # Find all persons in the associated groups that do not yet have a personal note for this lesson
     missing_persons = persons.annotate(
         no_personal_notes=~Exists(
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 1a32039e1d1312d7cef8ea8befa39c6d449a33a4..0f29ffbb871afe44a8c511063d444fba1c6cc7ff 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -9,11 +9,15 @@ def isidentifier(value: str) -> bool:
 
 
 class PersonalNote(ExtensibleModel):
-    """ A personal note about a single person. Used in the class register to note
-    absences, excuses and remarks about a student in a single lesson period.
+    """A personal note about a single person.
+
+    Used in the class register to note absences, excuses
+    and remarks about a student in a single lesson period.
     """
 
-    person = models.ForeignKey("core.Person", models.CASCADE, related_name="personal_notes")
+    person = models.ForeignKey(
+        "core.Person", models.CASCADE, related_name="personal_notes"
+    )
 
     week = models.IntegerField()
     lesson_period = models.ForeignKey(
@@ -41,8 +45,9 @@ class PersonalNote(ExtensibleModel):
 
 
 class LessonDocumentation(ExtensibleModel):
-    """ A documentation on a single lesson period. Non-personal, includes
-    the topic and homework of the lesson.
+    """A documentation on a single lesson period.
+
+    Non-personal, includes the topic and homework of the lesson.
     """
 
     week = models.IntegerField()
@@ -66,16 +71,21 @@ class LessonDocumentation(ExtensibleModel):
 
 
 class PersonalNoteFilter(ExtensibleModel):
-    """ A filter definition that can generate statistics on personal note texts. """
+    """A filter definition that can generate statistics on personal note texts."""
 
     identifier = models.CharField(
-        verbose_name=_("Identifier"), max_length=30, validators=[isidentifier], unique=True,
+        verbose_name=_("Identifier"),
+        max_length=30,
+        validators=[isidentifier],
+        unique=True,
     )
     description = models.CharField(
         verbose_name=_("Description"), max_length=60, blank=True, unique=True
     )
 
-    regex = models.CharField(verbose_name=_("Match expression"), max_length=100, unique=True)
+    regex = models.CharField(
+        verbose_name=_("Match expression"), max_length=100, unique=True
+    )
 
     class Meta:
         verbose_name = _("Personal note filter")
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
index 6f262a7deb9d408d48b1fb4532620837f585b9bd..f2ec6519f0d0eae946a4431244bbbf96396a6846 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -21,6 +21,10 @@
   {% for teacher in lesson_period.get_teachers.all %}
     {{ teacher.short_name }}
   {% endfor %}
+
+  <span class="right">
+    {% include "alsijil/partials/lesson_status_icon.html" with period=lesson_period css_class="medium" %}
+  </span>
 {% endblock %}
 
 {% block content %}
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 f5d5e4f7430ae8361fa1e36063e248cc82a8dd89..e42575fd07fc5ad520575643a1db2effa61ba66a 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -46,6 +46,7 @@
               <table class="striped datatable">
                 <thead>
                 <tr>
+                  <th></th>
                   <th>{% blocktrans %}Period{% endblocktrans %}</th>
                   <th>{% blocktrans %}Subject{% endblocktrans %}</th>
                   <th>{% blocktrans %}Teachers{% endblocktrans %}</th>
@@ -53,24 +54,10 @@
                 </thead>
                 <tbody>
                 {% for period in periods %}
-                  <tr class="
-                      {% if period.has_documentation %}
-                        success
-                      {% else %}
-                        {% weekday_to_date week period.period.weekday as current_date %}
-                        {% today as today %}
-                        {% if current_date < today %}
-                          error
-                        {% else %}
-                          {% if period.get_substitution %}
-                            warning
-                            {% if period.get_substitution.cancelled %}
-                              alsijil-lesson-cancelled
-                            {% endif %}
-                          {% endif %}
-                        {% endif %}
-                      {% endif %}
-                    ">
+                  <tr>
+                    <td class="center-align">
+                      {% include "alsijil/partials/lesson_status_icon.html" with period=period %}
+                    </td>
                     <td class="tr-link">
                       <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
                         {{ period.period.period }}.
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
new file mode 100644
index 0000000000000000000000000000000000000000..08746b5c9791b1b3f118387b3835dd4cbc4eec03
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/lesson_status_icon.html
@@ -0,0 +1,24 @@
+{% load i18n week_helpers %}
+
+{% now_datetime as now_dt %}
+
+{% if period.has_documentation %}
+  <i class="material-icons green-text tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Data complete" %}" title="{% trans "Data complete" %}">check_circle</i>
+{% else %}
+  {% period_to_time_start week period.period as time_start %}
+  {% period_to_time_end week period.period as time_end %}
+
+  {% if now_dt > time_end %}
+    <i class="material-icons red-text tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i>
+  {% elif now_dt > time_start and now_dt < time_end %}
+    <i class="material-icons orange-text tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i>
+  {% else %}
+    {% if period.get_substitution %}
+      {% if period.get_substitution.cancelled %}
+        <i class="material-icons red-text tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Lesson cancelled" %}" title="{% trans "Lesson cancelled" %}">cancel</i>
+      {% else %}
+        <i class="material-icons orange-text tooltipped {{ css_class }}"  data-position="bottom" data-tooltip="{% trans "Substitution" %}" title="{% trans "Substitution" %}">update</i>
+      {% endif %}
+    {% endif %}
+  {% endif %}
+{% endif %}
diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py
index fe40f18ee256cba2f90c48489024cdde211a9b90..4406a06852ae2489eaf5b84c66a28098d91f0010 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -12,13 +12,29 @@ urlpatterns = [
     path("week", views.week_view, name="week_view"),
     path("week/<int:year>/<int:week>", views.week_view, name="week_view_by_week"),
     path("week/<str:type_>/<int:id_>/", views.week_view, name="week_view"),
-    path("week/<int:year>/<int:week>/<str:type_>/<int:id_>/", views.week_view, name="week_view_by_week"),
-    path("print/group/<int:id_>", views.full_register_group, name="full_register_group"),
+    path(
+        "week/<int:year>/<int:week>/<str:type_>/<int:id_>/",
+        views.week_view,
+        name="week_view_by_week",
+    ),
+    path(
+        "print/group/<int:id_>", views.full_register_group, name="full_register_group"
+    ),
     path("absence/new", views.register_absence, name="register_absence"),
-    path("filters/list", views.list_personal_note_filters, name="list_personal_note_filters",),
-    path("filters/create", views.edit_personal_note_filter, name="create_personal_note_filter",),
     path(
-        "filters/edit/<int:id>", views.edit_personal_note_filter, name="edit_personal_note_filter",
+        "filters/list",
+        views.list_personal_note_filters,
+        name="list_personal_note_filters",
+    ),
+    path(
+        "filters/create",
+        views.edit_personal_note_filter,
+        name="create_personal_note_filter",
+    ),
+    path(
+        "filters/edit/<int:id_>",
+        views.edit_personal_note_filter,
+        name="edit_personal_note_filter",
     ),
     path(
         "filters/delete/<int:id_>",
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 8c58736fff71c0d3bb3be2c696c27584d396211f..6ea8c2f451ed3a9132191b0b73109369cfbb0343 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,10 +1,8 @@
 from datetime import date, datetime, timedelta
 from typing import Optional
 
-from aleksis.apps.chronos.managers import TimetableType
-from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
 from django.core.exceptions import PermissionDenied
-from django.db.models import Count, Exists, OuterRef, Q, Sum
+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
@@ -14,7 +12,9 @@ from calendarweek import CalendarWeek
 from django_tables2 import RequestConfig
 from rules.contrib.views import permission_required
 
+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.models import Group, Person, SchoolTerm
 from aleksis.core.util import messages
 from aleksis.core.util.core_helpers import objectgetter_optional
@@ -51,24 +51,31 @@ def lesson(
     if not (year and week and period_id):
         if lesson_period:
             return redirect(
-                "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk,
+                "lesson_by_week_and_period",
+                wanted_week.year,
+                wanted_week.week,
+                lesson_period.pk,
             )
         else:
             raise Http404(
                 _(
-                    "You either selected an invalid lesson or there is currently no lesson in progress."
+                    "You either selected an invalid lesson or "
+                    "there is currently no lesson in progress."
                 )
             )
 
     if (
         datetime.combine(
-            wanted_week[lesson_period.period.weekday - 1], lesson_period.period.time_start,
+            wanted_week[lesson_period.period.weekday - 1],
+            lesson_period.period.time_start,
         )
         > datetime.now()
         and not request.user.is_superuser
     ):
         raise PermissionDenied(
-            _("You are not allowed to create a lesson documentation for a lesson in the future.")
+            _(
+                "You are not allowed to create a lesson documentation for a lesson in the future."
+            )
         )
 
     context["lesson_period"] = lesson_period
@@ -80,7 +87,9 @@ def lesson(
         lesson_period=lesson_period, week=wanted_week.week
     )
     lesson_documentation_form = LessonDocumentationForm(
-        request.POST or None, instance=lesson_documentation, prefix="lesson_documentation",
+        request.POST or None,
+        instance=lesson_documentation,
+        prefix="lesson_documentation",
     )
 
     # Create a formset that holds all personal notes for all persons in this lesson
@@ -117,7 +126,11 @@ def lesson(
 
 @permission_required("alsijil.view_week", fn=get_instance_by_pk)
 def week_view(
-    request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None, type_: Optional[str] = None, id_: Optional[int] = None
+    request: HttpRequest,
+    year: Optional[int] = None,
+    week: Optional[int] = None,
+    type_: Optional[str] = None,
+    id_: Optional[int] = None,
 ) -> HttpResponse:
     context = {}
 
@@ -166,8 +179,13 @@ def week_view(
             if "type_" not in select_form.cleaned_data:
                 return redirect("week_view_by_week", wanted_week.year, wanted_week.week)
             else:
-                return redirect("week_view_by_week", wanted_week.year, wanted_week.week,
-                                select_form.cleaned_data["type_"].value, select_form.cleaned_data["instance"].pk)
+                return redirect(
+                    "week_view_by_week",
+                    wanted_week.year,
+                    wanted_week.week,
+                    select_form.cleaned_data["type_"].value,
+                    select_form.cleaned_data["instance"].pk,
+                )
 
     if type_ == TimetableType.GROUP:
         group = instance
@@ -189,28 +207,33 @@ def week_view(
             .prefetch_related("personal_notes")
             .annotate(
                 absences_count=Count(
-                    "personal_notes__absent",
+                    "personal_notes",
                     filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
+                        personal_notes__lesson_period__in=lesson_periods,
                         personal_notes__week=wanted_week.week,
                         personal_notes__absent=True,
                     ),
+                    distinct=True,
                 ),
                 unexcused_count=Count(
-                    "personal_notes__absent",
+                    "personal_notes",
                     filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
+                        personal_notes__lesson_period__in=lesson_periods,
                         personal_notes__week=wanted_week.week,
                         personal_notes__absent=True,
                         personal_notes__excused=False,
                     ),
+                    distinct=True,
                 ),
-                tardiness_sum=Sum(
-                    "personal_notes__late",
-                    filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
+                tardiness_sum=Subquery(
+                    Person.objects.filter(
+                        pk=OuterRef("pk"),
+                        personal_notes__lesson_period__in=lesson_periods,
                         personal_notes__week=wanted_week.week,
-                    ),
+                    )
+                    .distinct()
+                    .annotate(tardiness_sum=Sum("personal_notes__late"))
+                    .values("tardiness_sum")
                 ),
             )
         )
@@ -218,7 +241,9 @@ def week_view(
         persons = None
 
     # Resort lesson periods manually because an union queryset doesn't support order_by
-    lesson_periods = sorted(lesson_periods, key=lambda x: (x.period.weekday, x.period.period))
+    lesson_periods = sorted(
+        lesson_periods, key=lambda x: (x.period.weekday, x.period.period)
+    )
 
     context["week"] = wanted_week
     context["lesson_periods"] = lesson_periods
@@ -250,8 +275,8 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     # Get all lesson periods for the selected group
     lesson_periods = (
         LessonPeriod.objects.filter_group(group)
-            .distinct()
-            .prefetch_related("documentations", "personal_notes")
+        .distinct()
+        .prefetch_related("documentations", "personal_notes")
     )
 
     current_school_term = SchoolTerm.current
@@ -260,8 +285,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
         return HttpResponseNotFound(_("There is no current school term."))
 
     weeks = CalendarWeek.weeks_within(
-        current_school_term.date_start,
-        current_school_term.date_end,
+        current_school_term.date_start, current_school_term.date_end,
     )
 
     periods_by_day = {}
@@ -271,10 +295,16 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
 
             if lesson_period.lesson.date_start <= day <= lesson_period.lesson.date_end:
                 documentations = list(
-                    filter(lambda d: d.week == week.week, lesson_period.documentations.all(),)
+                    filter(
+                        lambda d: d.week == week.week,
+                        lesson_period.documentations.all(),
+                    )
                 )
                 notes = list(
-                    filter(lambda d: d.week == week.week, lesson_period.personal_notes.all(),)
+                    filter(
+                        lambda d: d.week == week.week,
+                        lesson_period.personal_notes.all(),
+                    )
                 )
                 substitution = lesson_period.get_substitution(week.week)
 
@@ -283,7 +313,9 @@ 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)),
+        absences_count=Count(
+            "personal_notes__absent", filter=Q(personal_notes__absent=True)
+        ),
         unexcused=Count(
             "personal_notes__absent",
             filter=Q(personal_notes__absent=True, personal_notes__excused=False),
@@ -299,7 +331,9 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
                 "_personal_notes_with_%s"
                 % personal_note_filter.identifier: Count(
                     "personal_notes__remarks",
-                    filter=Q(personal_notes__remarks__iregex=personal_note_filter.regex),
+                    filter=Q(
+                        personal_notes__remarks__iregex=personal_note_filter.regex
+                    ),
                 )
             }
         )
@@ -362,7 +396,9 @@ def list_personal_note_filters(request: HttpRequest) -> HttpResponse:
 
 
 @permission_required("alsijil.edit_personal_note_filter", fn=objectgetter_optional(PersonalNoteFilter, None, False))
-def edit_personal_note_filter(request: HttpRequest, id: Optional["int"] = None) -> HttpResponse:
+def edit_personal_note_filter(
+    request: HttpRequest, id_: Optional["int"] = None
+) -> HttpResponse:
     context = {}
 
     if id: