diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 1ed20e8ef9295c73da7bce6aef38be6583d976f5..167a73dbc378e01b1189db08508a4d1828fc6ca2 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 ExcuseType, LessonDocumentation, PersonalNote, PersonalNoteFilter
+from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 
 
 class LessonDocumentationForm(forms.ModelForm):
@@ -28,7 +28,7 @@ class LessonDocumentationForm(forms.ModelForm):
 class PersonalNoteForm(forms.ModelForm):
     class Meta:
         model = PersonalNote
-        fields = ["absent", "late", "excused", "excuse_type", "remarks"]
+        fields = ["absent", "late", "excused", "excuse_type", "extra_marks", "remarks"]
 
     person_name = forms.CharField(disabled=True)
 
@@ -109,12 +109,12 @@ class RegisterAbsenceForm(forms.Form):
     remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False)
 
 
-class PersonalNoteFilterForm(forms.ModelForm):
-    layout = Layout(Row("identifier", "description"), Row("regex"))
+class ExtraMarkForm(forms.ModelForm):
+    layout = Layout("short_name", "name")
 
     class Meta:
-        model = PersonalNoteFilter
-        fields = ["identifier", "description", "regex"]
+        model = ExtraMark
+        fields = ["short_name", "name"]
 
 
 class ExcuseTypeForm(forms.ModelForm):
diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py
index c5fea59e3790fba0407d55d760434ac2e3dd0d3d..a3ec5e9cc443a951ca5ddf4803361bd29e0637f9 100644
--- a/aleksis/apps/alsijil/menus.py
+++ b/aleksis/apps/alsijil/menus.py
@@ -31,14 +31,14 @@ MENUS = {
                     "validators": ["menu_generator.validators.is_superuser"],
                 },
                 {
-                    "name": _("Personal note filters"),
-                    "url": "list_personal_note_filters",
-                    "icon": "filter_list",
+                    "name": _("Excuse types"),
+                    "url": "excuse_types",
+                    "icon": "label",
                     "validators": ["menu_generator.validators.is_superuser"],
                 },
                 {
-                    "name": _("Excuse types"),
-                    "url": "excuse_types",
+                    "name": _("Extra marks"),
+                    "url": "extra_marks",
                     "icon": "label",
                     "validators": ["menu_generator.validators.is_superuser"],
                 },
diff --git a/aleksis/apps/alsijil/migrations/0003_extra_mark.py b/aleksis/apps/alsijil/migrations/0003_extra_mark.py
new file mode 100644
index 0000000000000000000000000000000000000000..307d9168a296661c8b520b55a1b5cd400b5e6df3
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0003_extra_mark.py
@@ -0,0 +1,72 @@
+# Generated by Django 3.0.8 on 2020-07-12 12:43
+
+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", "0002_excuse_type"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="ExtraMark",
+            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": "Extra mark",
+                "verbose_name_plural": "Extra marks",
+                "ordering": ["short_name"],
+            },
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
+        ),
+        migrations.AddField(
+            model_name="personalnote",
+            name="extra_marks",
+            field=models.ManyToManyField(
+                blank=True,
+                null=True,
+                to="alsijil.ExtraMark",
+                verbose_name="Extra marks",
+            ),
+        ),
+    ]
diff --git a/aleksis/apps/alsijil/migrations/0004_delete_personal_notes_filter.py b/aleksis/apps/alsijil/migrations/0004_delete_personal_notes_filter.py
new file mode 100644
index 0000000000000000000000000000000000000000..d04764b80ebf1318630b71830ea8f3bb030c0468
--- /dev/null
+++ b/aleksis/apps/alsijil/migrations/0004_delete_personal_notes_filter.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.8 on 2020-07-18 15:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("alsijil", "0003_extra_mark"),
+    ]
+
+    operations = [
+        migrations.DeleteModel(name="PersonalNoteFilter",),
+    ]
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index a09aa38c9be47c6a73e5d1e86caf6f375c9045e1..ecba9f773f54ba1343fbf86ab2cd2fc7003a760b 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -1,5 +1,5 @@
 from datetime import date
-from typing import Optional, Union
+from typing import Dict, Optional, Union
 
 from django.db.models import Exists, OuterRef, QuerySet
 
@@ -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 ExcuseType, LessonDocumentation, PersonalNote
+from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 
 
 @Person.method
@@ -151,3 +151,20 @@ def get_tardinesses(self, week: Optional[CalendarWeek] = None) -> QuerySet:
     if not week:
         week = self.week
     return self.personal_notes.filter(week=week.week, late__gt=0)
+
+
+@LessonPeriod.method
+def get_extra_marks(
+    self, week: Optional[CalendarWeek] = None
+) -> Dict[ExtraMark, QuerySet]:
+    """Get all statistics on extra marks for this lesson."""
+    if not week:
+        week = self.week
+
+    stats = {}
+    for extra_mark in ExtraMark.objects.all():
+        qs = self.personal_notes.filter(week=week.week, extra_marks=extra_mark)
+        if qs:
+            stats[extra_mark] = qs
+
+    return stats
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index b8491bf6dcd3def3c8bb399fbc4e9721171db274..dee6542fccb73fb0e179e355487d3752a23f2736 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -61,6 +61,10 @@ class PersonalNote(ExtensibleModel):
 
     remarks = models.CharField(max_length=200, blank=True)
 
+    extra_marks = models.ManyToManyField(
+        "ExtraMark", null=True, blank=True, verbose_name=_("Extra marks")
+    )
+
     def save(self, *args, **kwargs):
         if self.excuse_type:
             self.excused = True
@@ -107,24 +111,25 @@ class LessonDocumentation(ExtensibleModel):
         ]
 
 
-class PersonalNoteFilter(ExtensibleModel):
-    """A filter definition that can generate statistics on personal note texts."""
+class ExtraMark(ExtensibleModel):
+    """A model for extra marks.
 
-    identifier = models.CharField(
-        verbose_name=_("Identifier"),
-        max_length=30,
-        validators=[isidentifier],
-        unique=True,
-    )
-    description = models.CharField(
-        verbose_name=_("Description"), max_length=60, blank=True, unique=True
-    )
+    Can be used for lesson-based counting of things (like forgotten homework).
+    """
 
-    regex = models.CharField(
-        verbose_name=_("Match expression"), max_length=100, unique=True
+    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}"
+
+    @property
+    def count_label(self):
+        return f"{self.short_name}_count"
 
     class Meta:
-        verbose_name = _("Personal note filter")
-        verbose_name_plural = _("Personal note filters")
-        ordering = ["identifier"]
+        ordering = ["short_name"]
+        verbose_name = _("Extra mark")
+        verbose_name_plural = _("Extra marks")
diff --git a/aleksis/apps/alsijil/static/css/alsijil/lesson.css b/aleksis/apps/alsijil/static/css/alsijil/lesson.css
index 83b7db8e90d35891d0106f73ebf6176b36d32262..6d57c7e9910063dc8dfe359c643e65765cb6ad9c 100644
--- a/aleksis/apps/alsijil/static/css/alsijil/lesson.css
+++ b/aleksis/apps/alsijil/static/css/alsijil/lesson.css
@@ -1,3 +1,11 @@
+.alsijil-check-box {
+    margin-right: 10px;
+}
+
+.alsijil-check-box [type="checkbox"] {
+    padding-left: 30px;
+}
+
 .alsijil-lesson-cancelled {
     text-decoration: line-through;
 }
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index 3a6762c3744ead9d02e9917aa975b530b5e448f0..bd6b47a73f34c5e4343138993ee70162cd19b847 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -4,18 +4,23 @@ import django_tables2 as tables
 from django_tables2.utils import A
 
 
-class PersonalNoteFilterTable(tables.Table):
+class ExtraMarkTable(tables.Table):
     class Meta:
         attrs = {"class": "highlight"}
 
-    identifier = tables.Column()
-    description = tables.Column()
-    regex = tables.Column()
-    edit_filter = tables.LinkColumn(
-        "edit_personal_note_filter",
+    name = tables.LinkColumn("edit_extra_mark", args=[A("id")])
+    short_name = tables.Column()
+    edit = tables.LinkColumn(
+        "edit_extra_mark",
         args=[A("id")],
         text=_("Edit"),
-        attrs={"a": {"class": "btn-flat waves-effect waves-orange"}},
+        attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}},
+    )
+    delete = tables.LinkColumn(
+        "delete_extra_mark",
+        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 71277476a293ec3b798c19866291e3326177b9d2..3f2c5be29af5e4dcc48df7678d4019665774a7c1 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -1,6 +1,6 @@
 {# -*- engine:django -*- #}
 {% extends "core/base.html" %}
-{% load week_helpers %}
+{% load week_helpers material_form_internal %}
 {% load material_form i18n static %}
 
 {% block browser_title %}{% blocktrans %}Lesson{% endblocktrans %}{% endblock %}
@@ -70,7 +70,7 @@
 
       <div class="col s12" id="lesson-documentation">
         {% with prev_lesson=lesson_period.prev prev_doc=prev_lesson.get_lesson_documentation %}
-          {% with prev_doc=prev_lesson.get_lesson_documentation absences=prev_lesson.get_absences tardinesses=prev_lesson.get_tardinesses %}
+          {% with prev_doc=prev_lesson.get_lesson_documentation absences=prev_lesson.get_absences tardinesses=prev_lesson.get_tardinesses extra_marks=prev_lesson.get_extra_marks %}
             {% if prev_doc %}
               {% weekday_to_date prev_lesson.week prev_lesson.period.weekday as prev_date %}
 
@@ -116,6 +116,18 @@
                         <td>{% include "alsijil/partials/tardinesses.html" with notes=tardinesses %}</td>
                       </tr>
                     {% endif %}
+
+                    {% for extra_mark, notes in extra_marks.items %}
+                      <tr>
+                        <th>{{ extra_mark.name }}</th>
+                        <td>
+                          {% for note in notes %}
+                            <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span>
+                          {% endfor %}
+                        </td>
+                      </tr>
+                    {% endfor %}
+
                   </table>
                 </div>
               </div>
@@ -150,6 +162,7 @@
                 <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
                 <th>{% blocktrans %}Excused{% endblocktrans %}</th>
                 <th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
+                <th>{% blocktrans %}Extra marks{% endblocktrans %}</th>
                 <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
               </tr>
               </thead>
@@ -186,6 +199,19 @@
                       </label>
                     </div>
                   </td>
+                  <td>
+                    {% for group, items in form.extra_marks|select_options %}
+                      {% for choice, value, selected in items %}
+                        <label class="{% if selected %} active{% endif %} alsijil-check-box">
+                          <input type="checkbox"
+                                 {% if value == None or value == '' %}disabled{% else %}value="{{ value }}"{% endif %}
+                                  {% if selected %} checked="checked"{% endif %}
+                                 name="{{ form.extra_marks.html_name }}">
+                          <span>{{ choice }}</span>
+                        </label>
+                      {% endfor %}
+                    {% endfor %}
+                  </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 f1b94d845db471586524254cc582df7d3cebd2f0..594e876b65f561ff8489fae3226bbeacc24bbd98 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -1,7 +1,7 @@
 {# -*- engine:django -*- #}
 
 {% extends "core/base.html" %}
-{% load material_form i18n week_helpers static %}
+{% load material_form i18n week_helpers static data_helpers %}
 
 {% block browser_title %}{% blocktrans %}Week view{% endblocktrans %}{% endblock %}
 
@@ -96,6 +96,11 @@
               <p class="card-text">
                 {% trans "Summed up tardiness" %}: {{ person.person.tardiness_sum }}'
               </p>
+              {% for extra_mark in extra_marks %}
+                <p class="card-text">
+                  {{ extra_mark.name }}: {{ person.person|get_dict:extra_mark.count_label }}
+                </p>
+              {% endfor %}
               {% for note in person.personal_notes %}
                 {% if note.remarks %}
                   <blockquote>
diff --git a/aleksis/apps/alsijil/templates/alsijil/extra_mark/create.html b/aleksis/apps/alsijil/templates/alsijil/extra_mark/create.html
new file mode 100644
index 0000000000000000000000000000000000000000..d0ee3a9055561df1f468692f79d794404a60c1d1
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/extra_mark/create.html
@@ -0,0 +1,17 @@
+	{# -*- engine:django -*- #}
+
+	{% extends "core/base.html" %}
+	{% load material_form i18n %}
+
+	{% block browser_title %}{% blocktrans %}Create extra mark{% endblocktrans %}{% endblock %}
+	{% block page_title %}{% blocktrans %}Create extra mark{% 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/extra_mark/edit.html b/aleksis/apps/alsijil/templates/alsijil/extra_mark/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..7adee30a1cfd30256d70b2a823827384de331e05
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/extra_mark/edit.html
@@ -0,0 +1,17 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block browser_title %}{% blocktrans %}Edit extra mark{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Edit extra mark{% 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/extra_mark/list.html b/aleksis/apps/alsijil/templates/alsijil/extra_mark/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..a1a12b38096de60bae34d61425a1da9c688ab9d6
--- /dev/null
+++ b/aleksis/apps/alsijil/templates/alsijil/extra_mark/list.html
@@ -0,0 +1,18 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n %}
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}{% blocktrans %}Extra marks{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Extra marks{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <a class="btn green waves-effect waves-light" href="{% url 'create_extra_mark' %}">
+    <i class="material-icons left">add</i>
+    {% trans "Create extra mark" %}
+  </a>
+
+  {% render_table table %}
+{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/list.html b/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/list.html
deleted file mode 100644
index 3673e60abb2395a368d6721320dbf654d139d953..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/list.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{# -*- engine:django -*- #}
-
-{% extends "core/base.html" %}
-{% load i18n %}
-{% load render_table from django_tables2 %}
-
-{% block browser_title %}{% blocktrans %}All personal note filters{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Personal note filters{% endblocktrans %}{% endblock %}
-
-{% block content %}
-  <a href="{% url 'create_personal_note_filter' %}" class="waves-effect waves-light green btn">
-    <i class="material-icons left">add</i>{% blocktrans %}Add filter{% endblocktrans %}
-  </a>
-
-  {% render_table personal_note_filters_table %}
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/manage.html b/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/manage.html
deleted file mode 100644
index 427777439d73975a0cf3434bd507d27077cb9a2a..0000000000000000000000000000000000000000
--- a/aleksis/apps/alsijil/templates/alsijil/personal_note_filter/manage.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load material_form i18n static %}
-
-{% block browser_title %}
-  {% if personal_note_filter %}
-    {% trans "Update personal note filter" %}
-  {% else %}
-    {% trans "Create personal note filter" %}
-  {% endif %}
-{% endblock %}
-{% block page_title %}
-  {% if personal_note_filter %}
-    {% trans "Update personal note filter" %}
-  {% else %}
-    {% trans "Create personal note filter" %}
-  {% endif %}
-{% endblock %}
-
-
-{% block content %}
-  <form method="post">
-    {% csrf_token %}
-    {% form form=personal_note_filter_form %}{% endform %}
-    {% include "core/partials/save_button.html" %}
-    {% if personal_note_filter %}
-      <a href="{% url 'delete_personal_note_filter' personal_note_filter.id %}"
-         class="waves-effect waves-light btn red">
-        <i class="material-icons left">delete</i>{% blocktrans %}Delete filter{% endblocktrans %}
-      </a>
-    {% endif %}
-  </form>
-
-{% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
index bb866c6d2beab7d8960678f42c324a4363692c60..2090a42682a5fb8a7144f12606363da0b62680a9 100644
--- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
+++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html
@@ -97,6 +97,18 @@
     </ul>
   {% endif %}
 
+  {% if extra_marks %}
+    <h5>{% trans "Available extra marks" %}</h5>
+
+    <ul class="collection">
+      {% for extra_mark in extra_marks %}
+        <li class="collection-item">
+          <strong>{{ extra_mark.short_name }}</strong> {{ extra_mark.name }}
+        </li>
+      {% endfor %}
+    </ul>
+  {% endif %}
+
   <div class="page-break">&nbsp;</div>
 
 
@@ -117,6 +129,9 @@
       {% endfor %}
       <th>{% trans '(u)' %}</th>
       <th>{% trans '(b)' %}</th>
+      {% for extra_mark in extra_marks %}
+        <th>{{ extra_mark.short_name }}</th>
+      {% endfor %}
     </tr>
     </thead>
 
@@ -135,6 +150,9 @@
         {% endfor %}
         <td>{{ person.unexcused }}</td>
         <td>{{ person.tardiness }}'</td>
+        {% for extra_mark in extra_marks %}
+          <td>{{ person|get_dict:extra_mark.count_label }}</td>
+        {% endfor %}
       </tr>
     {% endfor %}
     </tbody>
@@ -245,29 +263,6 @@
       </tr>
     </table>
 
-    {% if personal_note_filters %}
-      <h5>{% trans 'Statistics on remarks' %}</h5>
-      <table>
-        <thead>
-        <tr>
-          <th>{% trans 'Description' %}</th>
-          <th>{% trans 'Count' %}</th>
-        </tr>
-        </thead>
-
-        <tbody>
-        {% for note_filter in personal_note_filters %}
-          <tr>
-            <td>{{ note_filter.description }}</td>
-            {% with "_personal_notes_with_"|add:note_filter.identifier as identifier %}
-              <td>{{ person|get_dict:identifier }}</td>
-            {% endwith %}
-          </tr>
-        {% endfor %}
-        </tbody>
-      </table>
-    {% endif %}
-
     <h5>{% trans 'Absences and tardiness' %}</h5>
     <table>
       <tr>
@@ -294,6 +289,18 @@
       </tr>
     </table>
 
+    {% if extra_marks %}
+      <h5>{% trans 'Extra marks' %}</h5>
+      <table>
+        {% for extra_mark in extra_marks %}
+          <tr>
+            <th>{{ extra_mark.name }}</th>
+            <td>{{ person|get_dict:extra_mark.count_label }}</td>
+          </tr>
+        {% endfor %}
+      </table>
+    {% endif %}
+
     <h5>{% trans 'Relevant personal notes' %}</h5>
     <table class="small-print">
       <thead>
@@ -304,14 +311,14 @@
         <th>{% trans 'Te.' %}</th>
         <th>{% trans 'Absent' %}</th>
         <th>{% trans 'Tard.' %}</th>
-        <th>{% trans 'Remarks' %}</th>
+        <th colspan="2">{% trans 'Remarks' %}</th>
       </tr>
       </thead>
 
       <tbody>
       {% for note in person.personal_notes.all %}
-        {% if note.lesson_period in lesson_periods or note.lesson_period in lesson_periods %}
-          {% if note.absent or note.late or note.remarks %}
+        {% if note.lesson_period in lesson_periods %}
+          {% if note.absent or note.late or note.remarks or note.extra_marks.all %}
             {% period_to_date note.week note.lesson_period.period as note_date %}
             <tr>
               <td>{{ note_date }}</td>
@@ -335,6 +342,11 @@
                   {{ note.late }}'
                 {% endif %}
               </td>
+              <td>
+                {% for extra_mark in note.extra_marks.all %}
+                  {{ extra_mark.short_name }}{% if not forloop.last %},{% endif %}
+                {% endfor %}
+              </td>
               <td>{{ note.remarks }}</td>
             </tr>
           {% endif %}
@@ -429,6 +441,12 @@
                       {% endif %}
                       </span>
                   {% endif %}
+                  {% for extra_mark in note.extra_marks.all %}
+                    <span>
+                      {{ note.person.last_name }}, {{ note.person.first_name|slice:"0:1" }}.
+                      ({{ extra_mark.short_name }})
+                    </span>
+                  {% endfor %}
                 {% endfor %}
               </td>
               <td class="lesson-te">
diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py
index 89ab0ffe5f47c8cfebb015a6f76e597af4dd11dd..3f68362598ec4c8e624f0d5df42e3d028de7ceb9 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -21,25 +21,21 @@ urlpatterns = [
         "print/group/<int:id_>", views.full_register_group, name="full_register_group"
     ),
     path("absence/new", views.register_absence, name="register_absence"),
+    path("extra_marks/", views.ExtraMarkListView.as_view(), name="extra_marks"),
     path(
-        "filters/list",
-        views.list_personal_note_filters,
-        name="list_personal_note_filters",
+        "extra_marks/create/",
+        views.ExtraMarkCreateView.as_view(),
+        name="create_extra_mark",
     ),
     path(
-        "filters/create",
-        views.edit_personal_note_filter,
-        name="create_personal_note_filter",
+        "extra_marks/<int:pk>/edit/",
+        views.ExtraMarkEditView.as_view(),
+        name="edit_extra_mark",
     ),
     path(
-        "filters/edit/<int:id_>",
-        views.edit_personal_note_filter,
-        name="edit_personal_note_filter",
-    ),
-    path(
-        "filters/delete/<int:id_>",
-        views.delete_personal_note_filter,
-        name="delete_personal_note_filter",
+        "extra_marks/<int:pk>/delete/",
+        views.ExtraMarkDeleteView.as_view(),
+        name="delete_extra_mark",
     ),
     path("excuse_types/", views.ExcuseTypeListView.as_view(), name="excuse_types"),
     path(
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 82973f4b220a30eac5eb197293fa214ff7457dc0..7a041822212b4fca3f8b6a03c7f92f859a19bb33 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -23,14 +23,14 @@ from aleksis.core.util import messages
 
 from .forms import (
     ExcuseTypeForm,
+    ExtraMarkForm,
     LessonDocumentationForm,
-    PersonalNoteFilterForm,
     PersonalNoteFormSet,
     RegisterAbsenceForm,
     SelectForm,
 )
-from .models import ExcuseType, LessonDocumentation, PersonalNoteFilter
-from .tables import ExcuseTypeTable, PersonalNoteFilterTable
+from .models import ExcuseType, ExtraMark, LessonDocumentation
+from .tables import ExcuseTypeTable, ExtraMarkTable
 
 
 def lesson(
@@ -255,6 +255,21 @@ def week_view(
             )
         )
 
+        for extra_mark in ExtraMark.objects.all():
+            persons_qs = persons_qs.annotate(
+                **{
+                    extra_mark.count_label: Count(
+                        "personal_notes",
+                        filter=Q(
+                            personal_notes__lesson_period__in=lesson_periods_pk,
+                            personal_notes__week=wanted_week.week,
+                            personal_notes__extra_marks=extra_mark,
+                        ),
+                        distinct=True,
+                    )
+                }
+            )
+
         persons = []
         for person in persons_qs:
             persons.append(
@@ -273,6 +288,7 @@ def week_view(
         lesson_periods, key=lambda x: (x.period.weekday, x.period.period)
     )
 
+    context["extra_marks"] = ExtraMark.objects.all()
     context["week"] = wanted_week
     context["lesson_periods"] = lesson_periods
     context["persons"] = persons
@@ -372,30 +388,23 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
         tardiness=Sum("personal_notes__late"),
     )
 
-    for excuse_type in ExcuseType.objects.all():
+    for extra_mark in ExtraMark.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,
-                        personal_notes__lesson_period__lesson__validity__school_term=current_school_term,
-                    ),
+                extra_mark.count_label: Count(
+                    "personal_notes", filter=Q(personal_notes__extra_marks=extra_mark,personal_notes__lesson_period__lesson__validity__school_term=current_school_term),
                 )
             }
         )
 
-    # FIXME Move to manager
-    personal_note_filters = PersonalNoteFilter.objects.all()
-    for personal_note_filter in personal_note_filters:
+    for excuse_type in ExcuseType.objects.all():
         persons = persons.annotate(
             **{
-                "_personal_notes_with_%s"
-                % personal_note_filter.identifier: Count(
-                    "personal_notes__remarks",
+                excuse_type.count_label: Count(
+                    "personal_notes__absent",
                     filter=Q(
-                        personal_notes__remarks__iregex=personal_note_filter.regex,
+                        personal_notes__absent=True,
+                        personal_notes__excuse_type=excuse_type,
                         personal_notes__lesson_period__lesson__validity__school_term=current_school_term,
                     ),
                 )
@@ -404,8 +413,8 @@ 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["extra_marks"] = ExtraMark.objects.all()
     context["group"] = group
     context["weeks"] = weeks
     context["periods_by_day"] = periods_by_day
@@ -446,57 +455,45 @@ def register_absence(request: HttpRequest) -> HttpResponse:
     return render(request, "alsijil/absences/register.html", context)
 
 
-def list_personal_note_filters(request: HttpRequest) -> HttpResponse:
-    context = {}
-
-    personal_note_filters = PersonalNoteFilter.objects.all()
-
-    # Prepare table
-    personal_note_filters_table = PersonalNoteFilterTable(personal_note_filters)
-    RequestConfig(request).configure(personal_note_filters_table)
-
-    context["personal_note_filters_table"] = personal_note_filters_table
-
-    return render(request, "alsijil/personal_note_filter/list.html", context)
-
-
-def edit_personal_note_filter(
-    request: HttpRequest, id_: Optional["int"] = None
-) -> HttpResponse:
-    context = {}
-
-    if id_:
-        personal_note_filter = PersonalNoteFilter.objects.get(id=id_)
-        context["personal_note_filter"] = personal_note_filter
-        personal_note_filter_form = PersonalNoteFilterForm(
-            request.POST or None, instance=personal_note_filter
-        )
-    else:
-        personal_note_filter_form = PersonalNoteFilterForm(request.POST or None)
+class ExtraMarkListView(SingleTableView, PermissionRequiredMixin):
+    """Table of all extra marks."""
 
-    if request.method == "POST":
-        if personal_note_filter_form.is_valid():
-            personal_note_filter_form.save(commit=True)
+    model = ExtraMark
+    table_class = ExtraMarkTable
+    permission_required = "core.view_extramark"
+    template_name = "alsijil/extra_mark/list.html"
 
-            messages.success(request, _("The filter has been saved"))
-            return redirect("list_personal_note_filters")
 
-    context["personal_note_filter_form"] = personal_note_filter_form
+class ExtraMarkCreateView(AdvancedCreateView, PermissionRequiredMixin):
+    """Create view for extra marks."""
 
-    return render(request, "alsijil/personal_note_filter/manage.html", context)
+    model = ExtraMark
+    form_class = ExtraMarkForm
+    permission_required = "core.create_extramark"
+    template_name = "alsijil/extra_mark/create.html"
+    success_url = reverse_lazy("extra_marks")
+    success_message = _("The extra mark has been created.")
 
 
-def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
-    context = {}
+class ExtraMarkEditView(AdvancedEditView, PermissionRequiredMixin):
+    """Edit view for extra marks."""
 
-    personal_note_filter = get_object_or_404(PersonalNoteFilter, pk=id_)
+    model = ExtraMark
+    form_class = ExtraMarkForm
+    permission_required = "core.edit_extramark"
+    template_name = "alsijil/extra_mark/edit.html"
+    success_url = reverse_lazy("extra_marks")
+    success_message = _("The extra mark has been saved.")
 
-    PersonalNoteFilter.objects.filter(pk=id_).delete()
 
-    messages.success(request, _("The filter has been deleted."))
+class ExtraMarkDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionMixin):
+    """Delete view for extra marks"""
 
-    context["personal_note_filter"] = personal_note_filter
-    return redirect("list_personal_note_filters")
+    model = ExtraMark
+    permission_required = "core.delete_extramark"
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("extra_marks")
+    success_message = _("The extra mark has been deleted.")
 
 
 class ExcuseTypeListView(SingleTableView, PermissionRequiredMixin):