diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 3632661e3bf3f616c5a06ba4e830b9cbc8d877f8..c39709aa2b1d0161fff0ad4d13d4839b43d7ba06 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -2,15 +2,18 @@ from datetime import datetime
 
 from django import forms
 from django.core.exceptions import ValidationError
-from django.db.models import Count
+from django.db.models import Count, Q
 from django.utils.translation import gettext_lazy as _
 
+from django_global_request.middleware import get_request
 from django_select2.forms import Select2Widget
+from guardian.shortcuts import get_objects_for_user
 from material import Fieldset, Layout, Row
 
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.apps.chronos.models import TimePeriod
 from aleksis.core.models import Group, Person
+from aleksis.core.util.predicates import check_global_permission
 
 from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 
@@ -51,12 +54,7 @@ class SelectForm(forms.Form):
         queryset=None, label=_("Group"), required=False, widget=Select2Widget,
     )
     teacher = forms.ModelChoiceField(
-        queryset=Person.objects.annotate(
-            lessons_count=Count("lessons_as_teacher")
-        ).filter(lessons_count__gt=0),
-        label=_("Teacher"),
-        required=False,
-        widget=Select2Widget,
+        queryset=None, label=_("Teacher"), required=False, widget=Select2Widget,
     )
 
     def clean(self) -> dict:
@@ -78,8 +76,40 @@ class SelectForm(forms.Form):
         return data
 
     def __init__(self, *args, **kwargs):
+        self.request = get_request()
         super().__init__(*args, **kwargs)
-        self.fields["group"].queryset = Group.get_groups_with_lessons()
+
+        person = self.request.user.person
+
+        group_qs = Group.get_groups_with_lessons()
+
+        # Filter selectable groups by permissions
+        if not check_global_permission(self.request.user, "alsijil.view_week"):
+            # 1) All groups the user is allowed to see the week view by object permissions
+            # 2) All groups the user is a member of an owner of
+            group_qs = (
+                group_qs.filter(
+                    pk__in=get_objects_for_user(
+                        self.request.user, "core.view_week_class_register_group", Group
+                    ).values_list("pk", flat=True)
+                )
+            ).union(group_qs.filter(Q(members=person) | Q(owners=person)))
+
+        # Flatten query by filtering groups by pk
+        self.fields["group"].queryset = Group.objects.filter(
+            pk__in=list(group_qs.values_list("pk", flat=True))
+        )
+
+        teacher_qs = Person.objects.annotate(
+            lessons_count=Count("lessons_as_teacher")
+        ).filter(lessons_count__gt=0)
+
+        # Filter selectable teachers by permissions
+        if not check_global_permission(self.request.user, "alsijil.view_week"):
+            # If the user hasn't the global permission, the user is only allowed to see his own person
+            teacher_qs = teacher_qs.filter(pk=person.pk)
+
+        self.fields["teacher"].queryset = teacher_qs
 
 
 PersonalNoteFormSet = forms.modelformset_factory(
@@ -95,11 +125,11 @@ class RegisterAbsenceForm(forms.Form):
     )
     date_start = forms.DateField(label=_("Start date"), initial=datetime.today)
     date_end = forms.DateField(label=_("End date"), initial=datetime.today)
-    from_period = forms.ChoiceField(label=_("Start period"))
-    to_period = forms.ChoiceField(label=_("End period"))
     person = forms.ModelChoiceField(
-        label=_("Person"), queryset=Person.objects.all(), widget=Select2Widget
+        label=_("Person"), queryset=None, widget=Select2Widget
     )
+    from_period = forms.ChoiceField(label=_("Start period"))
+    to_period = forms.ChoiceField(label=_("End period"))
     absent = forms.BooleanField(label=_("Absent"), initial=True, required=False)
     excused = forms.BooleanField(label=_("Excused"), initial=True, required=False)
     excuse_type = forms.ModelChoiceField(
@@ -111,9 +141,41 @@ class RegisterAbsenceForm(forms.Form):
     remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False)
 
     def __init__(self, *args, **kwargs):
+        self.request = get_request()
         super().__init__(*args, **kwargs)
         period_choices = TimePeriod.period_choices
 
+        # Filter selectable persons by permissions
+        if check_global_permission(self.request.user, "alsijil.register_absence"):
+            # Global permission, user can register absences for all persons
+            self.fields["person"].queryset = Person.objects.all()
+        else:
+            # 1) All persons the user is allowed to register an absence for by object permissions
+            # 2) All persons the user is the primary group owner
+            # 3) All persons the user is allowed to register an absence for by object permissions of the person's group
+            persons_qs = (
+                get_objects_for_user(
+                    self.request.user, "core.register_absence_person", Person
+                )
+                .union(
+                    Person.objects.filter(
+                        primary_group__owners=self.request.user.person
+                    )
+                )
+                .union(
+                    Person.objects.filter(
+                        member_of__in=get_objects_for_user(
+                            self.request.user, "core.register_absence_group", Group
+                        )
+                    )
+                )
+            )
+
+            # Flatten query by getting all pks and filter persons
+            self.fields["person"].queryset = Person.objects.filter(
+                pk__in=list(persons_qs.values_list("pk", flat=True))
+            )
+
         self.fields["from_period"].choices = period_choices
         self.fields["to_period"].choices = period_choices
         self.fields["from_period"].initial = TimePeriod.period_min
diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py
index 8b51bb206862364dbed6c57ff9af25d598b48e33..87a1daa3c93fb4985d9ccbf5f6a8afb3e956503a 100644
--- a/aleksis/apps/alsijil/menus.py
+++ b/aleksis/apps/alsijil/menus.py
@@ -16,49 +16,89 @@ MENUS = {
                     "name": _("Current lesson"),
                     "url": "lesson",
                     "icon": "alarm",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_lesson_menu",
+                        ),
+                    ],
                 },
                 {
                     "name": _("Current week"),
                     "url": "week_view",
                     "icon": "view_week",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_week_menu",
+                        ),
+                    ],
                 },
                 {
                     "name": _("My groups"),
                     "url": "my_groups",
                     "icon": "people",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_my_groups",
+                        ),
+                    ],
                 },
                 {
                     "name": _("My overview"),
                     "url": "overview_me",
                     "icon": "insert_chart",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_person_overview_menu",
+                        ),
+                    ],
                 },
                 {
                     "name": _("My students"),
                     "url": "my_students",
                     "icon": "people",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_my_students",
+                        ),
+                    ],
                 },
                 {
                     "name": _("Register absence"),
                     "url": "register_absence",
                     "icon": "rate_review",
-                    "validators": ["menu_generator.validators.is_superuser"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_register_absence",
+                        ),
+                    ],
                 },
                 {
                     "name": _("Excuse types"),
                     "url": "excuse_types",
                     "icon": "label",
-                    "validators": ["menu_generator.validators.is_superuser"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_excusetypes",
+                        ),
+                    ],
                 },
                 {
                     "name": _("Extra marks"),
                     "url": "extra_marks",
                     "icon": "label",
-                    "validators": ["menu_generator.validators.is_superuser"],
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "alsijil.view_extramarks",
+                        ),
+                    ],
                 },
             ],
         }
diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py
index 847d815c72f1893bbf3a269cff4f273da984c327..b688332997fcc890dd719467c8997cae403758bd 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -3,6 +3,7 @@ from typing import Dict, Optional, Union
 
 from django.db.models import Exists, OuterRef, Q, QuerySet
 from django.db.models.aggregates import Count
+from django.utils.translation import gettext as _
 
 import reversion
 from calendarweek import CalendarWeek
@@ -54,16 +55,20 @@ def mark_absent(
             continue
 
         with reversion.create_revision():
-            personal_note, created = PersonalNote.objects.update_or_create(
-                person=self,
-                lesson_period=lesson_period,
-                week=wanted_week.week,
-                year=wanted_week.year,
-                defaults={
-                    "absent": absent,
-                    "excused": excused,
-                    "excuse_type": excuse_type,
-                },
+            personal_note, created = (
+                PersonalNote.objects.select_related(None)
+                .prefetch_related(None)
+                .update_or_create(
+                    person=self,
+                    lesson_period=lesson_period,
+                    week=wanted_week.week,
+                    year=wanted_week.year,
+                    defaults={
+                        "absent": absent,
+                        "excused": excused,
+                        "excuse_type": excuse_type,
+                    },
+                )
             )
             personal_note.groups_of_person.set(self.member_of.all())
 
@@ -76,7 +81,7 @@ def mark_absent(
 
 
 @LessonPeriod.method
-def get_personal_notes(self, wanted_week: CalendarWeek):
+def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek):
     """Get all personal notes for that lesson in a specified week.
 
     Returns all linked `PersonalNote` objects, filtered by the given weeek,
@@ -89,7 +94,7 @@ def get_personal_notes(self, wanted_week: CalendarWeek):
         - 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 = Person.objects.annotate(
+    missing_persons = persons.annotate(
         no_personal_notes=~Exists(
             PersonalNote.objects.filter(
                 week=wanted_week.week,
@@ -119,11 +124,51 @@ def get_personal_notes(self, wanted_week: CalendarWeek):
     for personal_note in new_personal_notes:
         personal_note.groups_of_person.set(personal_note.person.member_of.all())
 
-    return PersonalNote.objects.select_related("person").filter(
-        lesson_period=self, week=wanted_week.week, year=wanted_week.year
+    return (
+        PersonalNote.objects.filter(
+            lesson_period=self,
+            week=wanted_week.week,
+            year=wanted_week.year,
+            person__in=persons,
+        )
+        .select_related(None)
+        .prefetch_related(None)
+        .select_related("person", "excuse_type")
+        .prefetch_related("extra_marks")
     )
 
 
+# Dynamically add extra permissions to Group and Person models in core
+# Note: requires migrate afterwards
+Group.add_permission(
+    "view_week_class_register_group",
+    _("Can view week overview of group class register"),
+)
+Group.add_permission(
+    "view_lesson_class_register_group",
+    _("Can view lesson overview of group class register"),
+)
+Group.add_permission(
+    "view_personalnote_group", _("Can view all personal notes of a group")
+)
+Group.add_permission(
+    "edit_personalnote_group", _("Can edit all personal notes of a group")
+)
+Group.add_permission(
+    "view_lessondocumentation_group", _("Can view all lesson documentation of a group")
+)
+Group.add_permission(
+    "edit_lessondocumentation_group", _("Can edit all lesson documentation of a group")
+)
+Group.add_permission("view_full_register_group", _("Can view full register of a group"))
+Group.add_permission(
+    "register_absence_group", _("Can register an absence for all members of a group")
+)
+Person.add_permission(
+    "register_absence_person", _("Can register an absence for a person")
+)
+
+
 @LessonPeriod.method
 def get_lesson_documentation(
     self, week: Optional[CalendarWeek] = None
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index ea951fd97a4a7676d775070440a77e8b1b68783e..66f6477d22165c085abd0855502686d505d75c75 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -208,3 +208,13 @@ class ExtraMark(ExtensibleModel):
         ordering = ["short_name"]
         verbose_name = _("Extra mark")
         verbose_name_plural = _("Extra marks")
+
+
+class AlsijilGlobalPermissions(ExtensibleModel):
+    class Meta:
+        managed = False
+        permissions = (
+            ("view_week", _("Can view week overview")),
+            ("register_absence", _("Can register absence")),
+            ("list_personal_note_filters", _("Can list all personal note filters")),
+        )
diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py
index bcefc075e2ac5025ebaf2b361abe3f2325b16563..d5552c5e03a37d530d1355c99ef56706616e5380 100644
--- a/aleksis/apps/alsijil/preferences.py
+++ b/aleksis/apps/alsijil/preferences.py
@@ -16,6 +16,24 @@ class BlockPersonalNotesForCancelled(BooleanPreference):
     verbose_name = _("Block adding personal notes for cancelled lessons")
 
 
+@site_preferences_registry.register
+class ViewOwnPersonalNotes(BooleanPreference):
+    section = alsijil
+    name = "view_own_personal_notes"
+    default = True
+    verbose_name = _("Allow users to view their own personal notes")
+
+
+@site_preferences_registry.register
+class RegisterAbsenceAsPrimaryGroupOwner(BooleanPreference):
+    section = alsijil
+    name = "register_absence_as_primary_group_owner"
+    default = True
+    verbose_name = _(
+        "Allow primary group owners to register future absences for students in their groups"
+    )
+
+
 @site_preferences_registry.register
 class CarryOverDataToNextPeriods(BooleanPreference):
     section = alsijil
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ee92ec5eb2f53b472e0b873995a3522d54c6d32
--- /dev/null
+++ b/aleksis/apps/alsijil/rules.py
@@ -0,0 +1,237 @@
+from rules import add_perm
+
+from aleksis.core.util.predicates import (
+    has_global_perm,
+    has_object_perm,
+    has_person,
+    is_current_person,
+    is_site_preference_set,
+)
+
+from .util.predicates import (
+    has_any_object_absence,
+    has_lesson_group_object_perm,
+    has_person_group_object_perm,
+    has_personal_note_group_perm,
+    is_group_member,
+    is_group_owner,
+    is_lesson_parent_group_owner,
+    is_lesson_participant,
+    is_lesson_teacher,
+    is_none,
+    is_own_personal_note,
+    is_person_group_owner,
+    is_person_primary_group_owner,
+    is_personal_note_lesson_parent_group_owner,
+    is_personal_note_lesson_teacher,
+    is_teacher,
+)
+
+# View lesson
+view_lesson_predicate = has_person & (
+    has_global_perm("alsijil.view_lesson")
+    | is_none  # View is opened as "Current lesson"
+    | is_lesson_teacher
+    | is_lesson_participant
+    | is_lesson_parent_group_owner
+    | has_lesson_group_object_perm("core.view_week_class_register_group")
+)
+add_perm("alsijil.view_lesson", view_lesson_predicate)
+
+# View lesson in menu
+add_perm("alsijil.view_lesson_menu", has_person)
+
+# View lesson personal notes
+view_lesson_personal_notes_predicate = view_lesson_predicate & (
+    has_global_perm("alsijil.view_personalnote")
+    | ~is_lesson_participant
+    | has_lesson_group_object_perm("core.view_personalnote_group")
+)
+add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate)
+
+# Edit personal note
+edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & (
+    has_global_perm("alsijil.change_personalnote")
+    | ~is_lesson_parent_group_owner
+    | has_lesson_group_object_perm("core.edit_personalnote_group")
+)
+add_perm("alsijil.edit_lesson_personalnote", edit_lesson_personal_note_predicate)
+
+# View personal note
+view_personal_note_predicate = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | is_personal_note_lesson_teacher
+    | (
+        is_own_personal_note
+        & is_site_preference_set("alsijil", "view_own_personal_notes")
+    )
+    | is_personal_note_lesson_parent_group_owner
+    | has_personal_note_group_perm("core.view_personalnote_group")
+)
+add_perm("alsijil.view_personalnote", view_personal_note_predicate)
+
+# Edit personal note
+edit_personal_note_predicate = view_personal_note_predicate & (
+    has_global_perm("alsijil.view_personalnote")
+    | ~is_own_personal_note
+    | has_personal_note_group_perm("core.edit_personalnote_group")
+)
+add_perm("alsijil.edit_personalnote", edit_personal_note_predicate)
+
+# View lesson documentation
+view_lesson_documentation_predicate = view_lesson_predicate
+add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate)
+
+# Edit lesson documentation
+edit_lesson_documentation_predicate = view_lesson_predicate & (
+    has_global_perm("alsijil.change_lessondocumentation")
+    | is_lesson_teacher
+    | has_lesson_group_object_perm("core.edit_lessondocumentation_group")
+)
+add_perm("alsijil.edit_lessondocumentation", edit_lesson_documentation_predicate)
+
+# View week overview
+view_week_predicate = has_person & (
+    has_global_perm("alsijil.view_week")
+    | is_current_person
+    | is_group_member
+    | is_group_owner
+    | has_object_perm("core.view_week_class_register_group")
+)
+add_perm("alsijil.view_week", view_week_predicate)
+
+# View week overview in menu
+add_perm("alsijil.view_week_menu", has_person)
+
+# View week personal notes
+view_week_personal_notes_predicate = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_object_perm("core.view_personalnote_group")
+    | is_group_owner
+    | (is_current_person & is_teacher)
+)
+add_perm("alsijil.view_week_personalnote", view_week_personal_notes_predicate)
+
+# View register absence page
+view_register_absence_predicate = has_person & (
+    has_global_perm("alsijil.register_absence") | has_any_object_absence
+)
+add_perm("alsijil.view_register_absence", view_register_absence_predicate)
+
+# Register absence
+register_absence_predicate = has_person & (
+    has_global_perm("alsijil.register_absence")
+    | has_person_group_object_perm("core.register_absence_group")
+    | has_object_perm("core.register_absence_person")
+    | (
+        is_person_primary_group_owner
+        & is_site_preference_set("alsijil", "register_absence_as_primary_group_owner")
+    )
+)
+add_perm("alsijil.register_absence", register_absence_predicate)
+
+# View full register for group
+view_full_register_predicate = has_person & (
+    has_global_perm("alsijil.view_full_register")
+    | has_object_perm("core.view_full_register_group")
+    | is_group_owner
+)
+add_perm("alsijil.view_full_register", view_full_register_predicate)
+
+# View students list
+view_my_students_predicate = has_person & is_teacher
+add_perm("alsijil.view_my_students", view_my_students_predicate)
+
+# View groups list
+view_my_groups_predicate = has_person & is_teacher
+add_perm("alsijil.view_my_groups", view_my_groups_predicate)
+
+# View person overview
+view_person_overview_predicate = has_person & (
+    (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes"))
+    | is_person_group_owner
+)
+add_perm("alsijil.view_person_overview", view_person_overview_predicate)
+
+# View person overview
+view_person_overview_menu_predicate = has_person
+add_perm("alsijil.view_person_overview_menu", view_person_overview_menu_predicate)
+
+# View person overview personal notes
+view_person_overview_personal_notes_predicate = view_person_overview_predicate & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_person_group_object_perm("core.view_personalnote_group")
+    | is_person_primary_group_owner
+    | (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes"))
+)
+add_perm(
+    "alsijil.view_person_overview_personalnote",
+    view_person_overview_personal_notes_predicate,
+)
+
+# Edit person overview personal notes
+edit_person_overview_personal_notes_predicate = (
+    view_person_overview_personal_notes_predicate
+    & (
+        has_global_perm("alsijil.edit_personalnote")
+        | ~is_current_person
+        | has_person_group_object_perm("core.edit_personalnote_group")
+    )
+)
+add_perm(
+    "alsijil.edit_person_overview_personalnote",
+    edit_person_overview_personal_notes_predicate,
+)
+
+# View person statistics on personal notes
+view_person_statistics_personal_notes_predicate = (
+    view_person_overview_personal_notes_predicate
+)
+add_perm(
+    "alsijil.view_person_statistics_personalnote",
+    view_person_statistics_personal_notes_predicate,
+)
+
+# View excuse type list
+view_excusetypes_predicate = has_person & has_global_perm("alsijil.view_excusetype")
+add_perm("alsijil.view_excusetypes", view_excusetypes_predicate)
+
+# Add excuse type
+add_excusetype_predicate = view_excusetypes_predicate & has_global_perm(
+    "alsijil.add_excusetype"
+)
+add_perm("alsijil.add_excusetype", add_excusetype_predicate)
+
+# Edit excuse type
+edit_excusetype_predicate = view_excusetypes_predicate & has_global_perm(
+    "alsijil.change_excusetype"
+)
+add_perm("alsijil.edit_excusetype", edit_excusetype_predicate)
+
+# Delete excuse type
+delete_excusetype_predicate = view_excusetypes_predicate & has_global_perm(
+    "alsijil.delete_excusetype"
+)
+add_perm("alsijil.delete_excusetype", delete_excusetype_predicate)
+
+# View extra mark list
+view_extramarks_predicate = has_person & has_global_perm("alsijil.view_extramark")
+add_perm("alsijil.view_extramarks", view_extramarks_predicate)
+
+# Add extra mark
+add_extramark_predicate = view_extramarks_predicate & has_global_perm(
+    "alsijil.add_extramark"
+)
+add_perm("alsijil.add_extramark", add_extramark_predicate)
+
+# Edit extra mark
+edit_extramark_predicate = view_extramarks_predicate & has_global_perm(
+    "alsijil.change_extramark"
+)
+add_perm("alsijil.edit_extramark", edit_extramark_predicate)
+
+# Delete extra mark
+delete_extramark_predicate = view_extramarks_predicate & has_global_perm(
+    "alsijil.delete_extramark"
+)
+add_perm("alsijil.delete_extramark", delete_extramark_predicate)
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index bd6b47a73f34c5e4343138993ee70162cd19b847..b9a8e68404d6b2672dfbb37d2433e55cad08cf08 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -42,3 +42,9 @@ class ExcuseTypeTable(tables.Table):
         text=_("Delete"),
         attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
     )
+
+    def before_render(self, request):
+        if not request.user.has_perm("alsijil.edit_excusetype"):
+            self.columns.hide("edit")
+        if not request.user.has_perm("alsijil.delete_excusetype"):
+            self.columns.hide("delete")
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
index 9fca5de56fc8bd6316a4db3728ae449cd3882cbb..f5a30a7ab3dc5cc9def7d6f5fcfc1011c8385380 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
@@ -1,7 +1,6 @@
 {# -*- engine:django -*- #}
 {% extends "core/base.html" %}
-{% load week_helpers material_form_internal %}
-{% load material_form i18n static %}
+{% load week_helpers material_form_internal material_form i18n static rules %}
 
 {% block browser_title %}{% blocktrans %}Lesson{% endblocktrans %}{% endblock %}
 
@@ -29,6 +28,10 @@
 {% endblock %}
 
 {% block content %}
+  {% has_perm "alsijil.view_lessondocumentation" user lesson_period as can_view_lesson_documentation %}
+  {% has_perm "alsijil.edit_lessondocumentation" user lesson_period as can_edit_lesson_documentation %}
+  {% has_perm "alsijil.edit_lesson_personalnote" user lesson_period as can_edit_lesson_personalnote %}
+
   <div class="row">
     <div class="col s12">
       {% with prev_lesson=lesson_period.prev %}
@@ -50,7 +53,10 @@
   </div>
 
   <form method="post">
-    <p>{% include "core/partials/save_button.html" %}</p>
+    {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
+      <p>{% include "core/partials/save_button.html" %}</p>
+    {% endif %}
+
     {% csrf_token %}
 
     <div class="row">
@@ -73,7 +79,8 @@
       <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 extra_marks=prev_lesson.get_extra_marks %}
-            {% if prev_doc %}
+            {% has_perm "alsijil.view_lessondocumentation" user prev_lesson as can_view_prev_lesson_documentation %}
+            {% if prev_doc and can_view_prev_lesson_documentation %}
               {% weekday_to_date prev_lesson.week prev_lesson.period.weekday as prev_date %}
 
               <div class="card">
@@ -124,7 +131,10 @@
                         <th>{{ extra_mark.name }}</th>
                         <td>
                           {% for note in notes %}
-                            <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span>
+                            {% has_perm "alsijil.view_personalnote" user note as can_view_personalnote %}
+                            {% if can_view_personalnote %}
+                              <span>{{ note.person }}{% if not forloop.last %},{% endif %}</span>
+                            {% endif %}
                           {% endfor %}
                         </td>
                       </tr>
@@ -143,7 +153,36 @@
             {% blocktrans %}Lesson documentation{% endblocktrans %}
           </span>
 
-            {% form form=lesson_documentation_form %}{% endform %}
+            {% if can_edit_lesson_documentation %}
+              {% form form=lesson_documentation_form %}{% endform %}
+            {% elif can_view_lesson_documentation %}
+              <table>
+                <tr>
+                  <th>
+                    {% trans "Lesson topic" %}
+                  </th>
+                  <td>
+                    {{ lesson_documentation.topic }}
+                  </td>
+                </tr>
+                <tr>
+                  <th>
+                    {% trans "Homework" %}
+                  </th>
+                  <td>
+                    {{ lesson_documentation.homework }}
+                  </td>
+                </tr>
+                <tr>
+                  <th>
+                    {% trans "Group note" %}
+                  </th>
+                  <td>
+                    {{ lesson_documentation.group_note }}
+                  </td>
+                </tr>
+              </table>
+            {% endif %}
           </div>
         </div>
       </div>
@@ -152,10 +191,12 @@
         <div class="col s12" id="personal-notes">
           <div class="card">
             <div class="card-content">
-      <span class="card-title">
-        {% blocktrans %}Personal notes{% endblocktrans %}
-      </span>
-              {% form form=personal_note_formset.management_form %}{% endform %}
+              <span class="card-title">
+                {% blocktrans %}Personal notes{% endblocktrans %}
+              </span>
+              {% if can_edit_lesson_personalnote %}
+                {% form form=personal_note_formset.management_form %}{% endform %}
+              {% endif %}
 
               <table class="striped responsive-table alsijil-table">
                 <thead>
@@ -166,94 +207,104 @@
                   <th>{% blocktrans %}Excused{% endblocktrans %}</th>
                   <th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
                   <th>{% blocktrans %}Extra marks{% endblocktrans %}</th>
-
                   <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
                 </tr>
                 </thead>
                 <tbody>
                 {% for form in personal_note_formset %}
-                  <tr>
-                    {{ form.id }}
-                    <td>{{ form.person_name }}{{ form.person_name.value }}</td>
-                    <td class="center-align">
-                      <label>
-                        {{ form.absent }}
-                        <span></span>
-                      </label>
-                    </td>
-                    <td>
-                      <div class="input-field">
-                        {{ form.late }}
-                        <label for="{{ form.absent.id_for_label }}">
-                          {% trans "Tardiness (in m)" %}
+                  {% if can_edit_lesson_personalnote %}
+                    <tr>
+                      {{ form.id }}
+                      <td>{{ form.person_name }}{{ form.person_name.value }}</td>
+                      <td class="center-align">
+                        <label>
+                          {{ form.absent }}
+                          <span></span>
                         </label>
-                      </div>
-                    </td>
-                    <td class="center-align">
-                      <label>
-                        {{ form.excused }}
-                        <span></span>
-                      </label>
-                    </td>
-                    <td>
-                      <div class="input-field">
-                        {{ form.excuse_type }}
-                        <label for="{{ form.excuse_type.id_for_label }}">
-                          {% trans "Excuse type" %}
+                      </td>
+                      <td>
+                        <div class="input-field">
+                          {{ form.late }}
+                          <label for="{{ form.absent.id_for_label }}">
+                            {% trans "Tardiness (in m)" %}
+                          </label>
+                        </div>
+                      </td>
+                      <td class="center-align">
+                        <label>
+                          {{ form.excused }}
+                          <span></span>
                         </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>
+                      </td>
+                      <td>
+                        <div class="input-field">
+                          {{ form.excuse_type }}
+                          <label for="{{ form.excuse_type.id_for_label }}">
+                            {% trans "Excuse type" %}
                           </label>
+                        </div>
+                      </td>
+                      <td>
+                        {% for group, items in form.extra_marks|select_options %}
+                          {% for choice, value, selected in items %}
+                            <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 %}
-                      {% endfor %}
-                    </td>
-                    <td>
-                      <div class="input-field">
-                        {{ form.remarks }}
-                        <label for="{{ form.absent.id_for_label }}">
-                          {% trans "Remarks" %}
-                        </label>
-                      </div>
-                    </td>
-                    <td>
-                      <div class="input-field">
-                        {{ form.remarks }}
-                        <label for="{{ form.absent.id_for_label }}">
-                          {% trans "Remarks" %}
-                        </label>
-                      </div>
-                    </td>
-                  </tr>
+                      </td>
+                      <td>
+                        <div class="input-field">
+                          {{ form.remarks }}
+                          <label for="{{ form.absent.id_for_label }}">
+                            {% trans "Remarks" %}
+                          </label>
+                        </div>
+                      </td>
+                    </tr>
+                  {% else %}
+                    <tr>
+                      <td>{{ form.person_name.value }}</td>
+                      <td>{{ form.absent.value }}</td>
+                      <td>{{ form.late.value }}</td>
+                      <td>{{ form.excused.value }}</td>
+                      <td>{{ form.excuse_type.value }}</td>
+                      <td>
+                        {% for extra_mark in form.extra_marks.value %}
+                          {{ extra_mark }}{% if not forloop.last %},{% endif %}
+                        {% endfor %}
+                      </td>
+                      <td>{{ form.remarks.value }}</td>
+                    </tr>
+                  {% endif %}
                 {% endfor %}
                 </tbody>
               </table>
             </div>
           </div>
         </div>
-
       {% endif %}
 
-      <div class="col s12" id="version-history">
-        <div class="card">
-          <div class="card-content">
+      {% if can_view_lesson_documentation %}
+        <div class="col s12" id="version-history">
+          <div class="card">
+            <div class="card-content">
           <span class="card-title">
             {% blocktrans %}Change history{% endblocktrans %}
           </span>
-            {% include 'core/partials/crud_events.html' with obj=lesson_documentation %}
+              {% include 'core/partials/crud_events.html' with obj=lesson_documentation %}
+            </div>
           </div>
         </div>
-      </div>
+      {% endif %}
     </div>
 
-    <p>{% include "core/partials/save_button.html" %}</p>
+    {% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
+      <p>{% include "core/partials/save_button.html" %}</p>
+    {% endif %}
   </form>
 {% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
index dd907383585e258879539a07508a5e9c0aae7b81..efb5179adbc622ab80c47802d2230eb57a19b679 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
@@ -1,5 +1,6 @@
 {# -*- engine:django -*- #}
 {% extends "core/base.html" %}
+{% load rules %}
 {% load data_helpers %}
 {% load week_helpers %}
 {% load i18n %}
@@ -14,6 +15,8 @@
 {% endblock %}
 
 {% block content %}
+  {% has_perm "alsijil.edit_person_overview_personalnote" user person as can_mark_all_as_excused %}
+
   <div class="row">
   <div class="col s12 m12 l6">
     <h5>{% trans "Unexcused absences" %}</h5>
@@ -22,16 +25,19 @@
       {% for note in unexcused_absences %}
         {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
         <li class="collection-item">
-          <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
-            {% csrf_token %}
-            {% trans "Mark as" %}
-            <input type="hidden" value="{{ note.pk }}" name="personal_note">
-            {% include "alsijil/partials/mark_as_buttons.html" %}
-            <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-               href="{% url "delete_personal_note" note.pk %}">
-              <i class="material-icons center">cancel</i>
-            </a>
-          </form>
+          {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
+          {% if can_edit_personal_note %}
+            <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+              {% csrf_token %}
+              {% trans "Mark as" %}
+              <input type="hidden" value="{{ note.pk }}" name="personal_note">
+              {% include "alsijil/partials/mark_as_buttons.html" %}
+              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                 href="{% url "delete_personal_note" note.pk %}">
+                <i class="material-icons center">cancel</i>
+              </a>
+            </form>
+          {% endif %}
           <i class="material-icons left red-text">warning</i>
           <p class="no-margin">
             <a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">{{ note_date }}, {{ note.lesson_period }}</a>
@@ -39,16 +45,18 @@
           {% if note.remarks %}
             <p class="no-margin"><em>{{ note.remarks }}</em></p>
           {% endif %}
-          <form action="" method="post" class="hide-on-med-and-up">
-            {% csrf_token %}
-            {% trans "Mark as" %}
-            <input type="hidden" value="{{ note.pk }}" name="personal_note">
-            {% include "alsijil/partials/mark_as_buttons.html" %}
-            <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-               href="{% url "delete_personal_note" note.pk %}">
-              <i class="material-icons center">cancel</i>
-            </a>
-          </form>
+          {% if can_edit_personal_note %}
+            <form action="" method="post" class="hide-on-med-and-up">
+              {% csrf_token %}
+              {% trans "Mark as" %}
+              <input type="hidden" value="{{ note.pk }}" name="personal_note">
+              {% include "alsijil/partials/mark_as_buttons.html" %}
+              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                 href="{% url "delete_personal_note" note.pk %}">
+                <i class="material-icons center">cancel</i>
+              </a>
+            </form>
+          {% endif %}
         </li>
       {% empty %}
         <li class="collection-item flow-text">
@@ -56,47 +64,49 @@
         </li>
       {% endfor %}
     </ul>
-    <h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
-    <ul class="collapsible">
-      {% for school_term, stat in stats %}
-        <li {% if forloop.first %}class="active"{% endif %}>
-          <div class="collapsible-header">
-            <i class="material-icons">date_range</i>{{ school_term }}</div>
-          <div class="collapsible-body">
-            <table>
-              <tr>
-                <th colspan="2">{% trans 'Absences' %}</th>
-                <td>{{ stat.absences_count }}</td>
-              </tr>
-              <tr>
-                <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
-                <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
-                <th class="truncate">{% trans 'Excused' %}</th>
-                <td>{{ stat.excused }}</td>
-              </tr>
-              {% for excuse_type in excuse_types %}
-                <th>{{ excuse_type.name }}</th>
-                <td>{{ stat|get_dict:excuse_type.count_label }}</td>
-              {% endfor %}
-              <tr>
-                <th>{% trans 'Unexcused' %}</th>
-                <td>{{ stat.unexcused }}</td>
-              </tr>
-              <tr>
-                <th colspan="2">{% trans 'Tardiness' %}</th>
-                <td>{{ stat.tardiness }}'</td>
-              </tr>
-              {% for extra_mark in extra_marks %}
+    {% if stats %}
+      <h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
+      <ul class="collapsible">
+        {% for school_term, stat in stats %}
+          <li {% if forloop.first %}class="active"{% endif %}>
+            <div class="collapsible-header">
+              <i class="material-icons">date_range</i>{{ school_term }}</div>
+            <div class="collapsible-body">
+              <table>
                 <tr>
-                  <th colspan="2">{{ extra_mark.name }}</th>
-                  <td>{{ stat|get_dict:extra_mark.count_label }}</td>
+                  <th colspan="2">{% trans 'Absences' %}</th>
+                  <td>{{ stat.absences_count }}</td>
                 </tr>
-              {% endfor %}
-            </table>
-          </div>
-        </li>
-      {% endfor %}
-    </ul>
+                <tr>
+                  <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
+                  <td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
+                  <th class="truncate">{% trans 'Excused' %}</th>
+                  <td>{{ stat.excused }}</td>
+                </tr>
+                {% for excuse_type in excuse_types %}
+                  <th>{{ excuse_type.name }}</th>
+                  <td>{{ stat|get_dict:excuse_type.count_label }}</td>
+                {% endfor %}
+                <tr>
+                  <th>{% trans 'Unexcused' %}</th>
+                  <td>{{ stat.unexcused }}</td>
+                </tr>
+                <tr>
+                  <th colspan="2">{% trans 'Tardiness' %}</th>
+                  <td>{{ stat.tardiness }}'</td>
+                </tr>
+                {% for extra_mark in extra_marks %}
+                  <tr>
+                    <th colspan="2">{{ extra_mark.name }}</th>
+                    <td>{{ stat|get_dict:extra_mark.count_label }}</td>
+                  </tr>
+                {% endfor %}
+              </table>
+            </div>
+          </li>
+        {% endfor %}
+      </ul>
+    {% endif %}
   </div>
   <div class="col s12 m12 l6">
     <h5>{% trans "Relevant personal notes" %}</h5>
@@ -121,21 +131,25 @@
               {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
               {% ifchanged note_date %}
                 <li class="collection-item">
-                  <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
-                    {% csrf_token %}
-                    {% trans "Mark all as" %}
-                    <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
-                    {% include "alsijil/partials/mark_as_buttons.html" %}
-                  </form>
+                  {% if can_mark_all_as_excused %}
+                    <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+                      {% csrf_token %}
+                      {% trans "Mark all as" %}
+                      <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
+                      {% include "alsijil/partials/mark_as_buttons.html" %}
+                    </form>
+                  {% endif %}
                   <i class="material-icons left">schedule</i>
                   {{ note_date }}
 
-                  <form action="" method="post" class="hide-on-med-and-up">
-                    {% csrf_token %}
-                    {% trans "Mark all as" %}
-                    <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
-                    {% include "alsijil/partials/mark_as_buttons.html" %}
-                  </form>
+                  {% if can_mark_all_as_excused %}
+                    <form action="" method="post" class="hide-on-med-and-up">
+                      {% csrf_token %}
+                      {% trans "Mark all as" %}
+                      <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
+                      {% include "alsijil/partials/mark_as_buttons.html" %}
+                    </form>
+                  {% endif %}
                 </li>
               {% endifchanged %}
 
@@ -154,7 +168,8 @@
                   </div>
 
                   <div class="col s12 m7 no-padding">
-                    {% if note.absent and not note.excused %}
+                    {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
+                    {% if note.absent and not note.excused and can_edit_personal_note %}
                       <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
                         {% csrf_token %}
                         {% trans "Mark as" %}
@@ -165,6 +180,11 @@
                           <i class="material-icons center">cancel</i>
                         </a>
                       </form>
+                    {% elif can_edit_personal_note %}
+                      <a class="btn-flat red-text right hide-on-small-only" title="{% trans "Delete note" %}"
+                         href="{% url "delete_personal_note" note.pk %}">
+                        <i class="material-icons center">cancel</i>
+                      </a>
                     {% endif %}
 
                     {% if note.absent %}
@@ -196,7 +216,7 @@
 
                   </div>
                   <div class="col s12 hide-on-med-and-up">
-                    {% if note.absent and not note.excused %}
+                    {% if note.absent and not note.excused and can_edit_personal_note %}
                       <form action="" method="post">
                         {% csrf_token %}
                         {% trans "Mark as" %}
@@ -207,6 +227,12 @@
                           <i class="material-icons center">cancel</i>
                         </a>
                       </form>
+                    {% elif can_edit_personal_note %}
+                      <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                         href="{% url "delete_personal_note" note.pk %}">
+                        <i class="material-icons left">cancel</i>
+                        {% trans "Delete" %}
+                      </a>
                     {% endif %}
                   </div>
               </li>
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 ea549f7f15783769f79a9d598304b856481adb18..de7c30ecd010294cc05e37a1aa58e27c519036f4 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 data_helpers %}
+{% load material_form i18n week_helpers static data_helpers rules %}
 
 {% block browser_title %}{% blocktrans %}Week view{% endblocktrans %}{% endblock %}
 
@@ -66,38 +66,41 @@
                 </thead>
                 <tbody>
                 {% for period in periods %}
-                  <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 }}.
-                      </a>
-                    </td>
-                    {% if not group %}
+                  {% has_perm "alsijil.view_lessondocumentation" user period as can_view_lesson_documentation %}
+                  {% if can_view_lesson_documentation %}
+                    <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 }}.
+                        </a>
+                      </td>
+                      {% if not group %}
+                        <td>
+                          <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
+                            {{ period.lesson.group_names }}
+                          </a>
+                        </td>
+                      {% endif %}
                       <td>
                         <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                          {{ period.lesson.group_names }}
+                          {{ period.get_subject.name }}
                         </a>
                       </td>
-                    {% endif %}
-                    <td>
-                      <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                        {{ period.get_subject.name }}
-                      </a>
-                    </td>
-                    <td>
-                      <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                        {{ period.get_teacher_names }}
-                      </a>
-                    </td>
-                    <td>
-                      <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                        {{ period.get_lesson_documentation.topic }}
-                      </a>
-                    </td>
-                  </tr>
+                      <td>
+                        <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
+                          {{ period.get_teacher_names }}
+                        </a>
+                      </td>
+                      <td>
+                        <a class="tr-link" href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
+                          {{ period.get_lesson_documentation.topic }}
+                        </a>
+                      </td>
+                    </tr>
+                  {% endif %}
                 {% endfor %}
                 </tbody>
               </table>
@@ -113,7 +116,12 @@
             </span>
             {% for person in persons %}
               <h5 class="card-title">
-                <a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
+                {% has_perm "alsijil.view_person_overview" user person.person as can_view_person_overview %}
+                {% if can_view_person_overview %}
+                  <a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
+                {% else %}
+                  {{ person.person.full_name }}
+                {% endif %}
               </h5>
               <p class="card-text">
                 {% trans "Absent" %}: {{ person.person.absences_count }}
diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
index 2be1f28c96e70f35e63fe4f5cefb50c232e38e0a..9c74d62e127f6a03b1ad7637114badfc2f4d9de2 100644
--- a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
+++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html
@@ -2,7 +2,7 @@
 
 {% extends "core/base.html" %}
 
-{% load i18n %}
+{% load i18n rules %}
 {% load render_table from django_tables2 %}
 
 {% block browser_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %}
@@ -11,10 +11,13 @@
 {% 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>
+  {% has_perm "alsijil.add_excusetype" user as add_excusetype %}
+  {% if add_excusetype %}
+    <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>
+  {% endif %}
 
   {% render_table table %}
 {% endblock %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
index 6deaa3891c480026b22fbad2cfc75a4f2b260110..c72204c556efee0c5308d607947b5cd9670d85c3 100644
--- a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html
@@ -1,6 +1,9 @@
-{% load i18n %}
+{% load i18n rules %}
 {% for note in notes %}
-  <span class="{% if note.excused %}green-text{% else %}red-text{% endif %}">{{ note.person }}
-    {% 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>
+  {% has_perm "alsijil.view_personalnote" user note as can_view_personalnote %}
+  {% if can_view_personalnote %}
+    <span class="{% if note.excused %}green-text{% else %}red-text{% endif %}">{{ note.person }}
+      {% 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>
+  {% endif %}
 {% endfor %}
diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/tardinesses.html b/aleksis/apps/alsijil/templates/alsijil/partials/tardinesses.html
index b3639ea88b779bd746ab551f0a9f2e5d938fd786..3bb86191612f2bdface729be8d048a6fb05edcd5 100644
--- a/aleksis/apps/alsijil/templates/alsijil/partials/tardinesses.html
+++ b/aleksis/apps/alsijil/templates/alsijil/partials/tardinesses.html
@@ -1,3 +1,7 @@
+{% load rules %}
 {% for note in notes %}
-  <span>{{ note.person }} ({{ note.late }}'){% if not forloop.last %},{% endif %}</span>
+  {% has_perm "alsijil.view_personalnote" user note as can_view_personalnote %}
+  {% if can_view_personalnote %}
+    <span>{{ note.person }} ({{ note.late }}'){% if not forloop.last %},{% endif %}</span>
+  {% endif %}
 {% endfor %}
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..10bf5b4e7e4c67b2f998209849e7fa60f5659945
--- /dev/null
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -0,0 +1,52 @@
+from typing import Optional
+
+from django.http import HttpRequest
+
+from calendarweek import CalendarWeek
+
+from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
+
+
+def get_lesson_period_by_pk(
+    request: HttpRequest,
+    year: Optional[int] = None,
+    week: Optional[int] = None,
+    period_id: Optional[int] = None,
+):
+    """Get LessonPeriod object either by given object_id or by time and current person."""
+    wanted_week = CalendarWeek(year=year, week=week)
+    if period_id:
+        lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(
+            pk=period_id
+        )
+    elif hasattr(request, "user") and hasattr(request.user, "person"):
+        if request.user.person.lessons_as_teacher.exists():
+            lesson_period = (
+                LessonPeriod.objects.at_time()
+                .filter_teacher(request.user.person)
+                .first()
+            )
+        else:
+            lesson_period = (
+                LessonPeriod.objects.at_time()
+                .filter_participant(request.user.person)
+                .first()
+            )
+    else:
+        lesson_period = None
+    return lesson_period
+
+
+def get_timetable_instance_by_pk(
+    request: HttpRequest,
+    year: Optional[int] = None,
+    week: Optional[int] = None,
+    type_: Optional[str] = None,
+    id_: Optional[int] = None,
+):
+    """Get timetable object (teacher, room or group) by given type and id or the current person."""
+    if type_ and id_:
+        return get_el_by_pk(request, type_, id_)
+    elif hasattr(request, "user") and hasattr(request.user, "person"):
+        return request.user.person
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d988c8f4c3134b46451d04b24a570177473c44a
--- /dev/null
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -0,0 +1,236 @@
+from typing import Any, Union
+
+from django.contrib.auth.models import User
+
+from guardian.shortcuts import get_objects_for_user
+from rules import predicate
+
+from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.core.models import Group, Person
+from aleksis.core.util.predicates import check_object_permission
+
+from ..models import PersonalNote
+
+
+@predicate
+def is_none(user: User, obj: Any) -> bool:
+    """Predicate that checks if the provided object is None-like."""
+    return bool(obj)
+
+
+@predicate
+def is_lesson_teacher(user: User, obj: LessonPeriod) -> bool:
+    """Predicate for teachers of a lesson.
+
+    Checks whether the person linked to the user is a teacher
+    in the lesson or the substitution linked to the given LessonPeriod.
+    """
+    if obj:
+        sub = obj.get_substitution()
+        if sub and sub in user.person.lesson_substitutions.all():
+            return True
+        return user.person in obj.lesson.teachers.all()
+    return False
+
+
+@predicate
+def is_lesson_participant(user: User, obj: LessonPeriod) -> bool:
+    """Predicate for participants of a lesson.
+
+    Checks whether the person linked to the user is a member in
+    the groups linked to the given LessonPeriod.
+    """
+    if hasattr(obj, "lesson"):
+        return obj.lesson.groups.filter(members=user.person).exists()
+    return False
+
+
+@predicate
+def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool:
+    """
+    Predicate for parent group owners of a lesson.
+
+    Checks whether the person linked to the user is the owner of
+    any parent groups of any groups of the given LessonPeriods lesson.
+    """
+    if hasattr(obj, "lesson"):
+        return obj.lesson.groups.filter(parent_groups__owners=user.person).exists()
+    return False
+
+
+@predicate
+def is_group_owner(user: User, obj: Union[Group, Person]) -> bool:
+    """Predicate for group owners of a given group.
+
+    Checks whether the person linked to the user is the owner of the given group.
+    If there isn't provided a group, it will return `False`.
+    """
+    if isinstance(obj, Group):
+        if obj.owners.filter(pk=user.person.pk).exists():
+            return True
+
+    return False
+
+
+@predicate
+def is_person_group_owner(user: User, obj: Person) -> bool:
+    """
+    Predicate for group owners of any group.
+
+    Checks whether the person linked to the user is
+    the owner of any group of the given person.
+    """
+    if obj:
+        return obj.member_of.filter(owners=user.person).exists()
+    return False
+
+
+@predicate
+def is_person_primary_group_owner(user: User, obj: Person) -> bool:
+    """
+    Predicate for group owners of the person's primary group.
+
+    Checks whether the person linked to the user is
+    the owner of the primary group of the given person.
+    """
+    if obj.primary_group:
+        return user.person in obj.primary_group.owners.all()
+    return False
+
+
+def has_person_group_object_perm(perm: str):
+    """Predicate builder for permissions on a set of member groups.
+
+    Checks whether a user has a permission on any group of a person.
+    """
+    name = f"has_person_group_object_perm:{perm}"
+
+    @predicate(name)
+    def fn(user: User, obj: Person) -> bool:
+        for group in obj.member_of.all():
+            if check_object_permission(user, perm, group):
+                return True
+        return False
+
+    return fn
+
+
+@predicate
+def is_group_member(user: User, obj: Union[Group, Person]) -> bool:
+    """Predicate for group membership.
+
+    Checks whether the person linked to the user is a member of the given group.
+    If there isn't provided a group, it will return `False`.
+    """
+    if isinstance(obj, Group):
+        if obj.members.filter(pk=user.person.pk).exists():
+            return True
+
+    return False
+
+
+def has_lesson_group_object_perm(perm: str):
+    """Predicate builder for permissions on lesson groups.
+
+    Checks whether a user has a permission on any group of a LessonPeriod.
+    """
+    name = f"has_lesson_group_object_perm:{perm}"
+
+    @predicate(name)
+    def fn(user: User, obj: LessonPeriod) -> bool:
+        if hasattr(obj, "lesson"):
+            for group in obj.lesson.groups.all():
+                if check_object_permission(user, perm, group):
+                    return True
+            return False
+        return True
+
+    return fn
+
+
+def has_personal_note_group_perm(perm: str):
+    """Predicate builder for permissions on personal notes
+
+    Checks whether a user has a permission on any group of a person of a PersonalNote.
+    """
+    name = f"has_personal_note_person_or_group_perm:{perm}"
+
+    @predicate(name)
+    def fn(user: User, obj: PersonalNote) -> bool:
+        if hasattr(obj, "person"):
+            for group in obj.person.member_of.all():
+                if check_object_permission(user, perm, group):
+                    return True
+            return False
+
+    return fn
+
+
+@predicate
+def is_own_personal_note(user: User, obj: PersonalNote) -> bool:
+    """Predicate for users referred to in a personal note
+
+    Checks whether the user referred to in a PersonalNote is the active user.
+    """
+    if hasattr(obj, "person") and obj.person is user.person:
+        return True
+    return False
+
+
+@predicate
+def is_personal_note_lesson_teacher(user: User, obj: PersonalNote) -> bool:
+    """Predicate for teachers of a lesson referred to in the lesson period of a personal note.
+
+    Checks whether the person linked to the user is a teacher
+    in the lesson or the substitution linked to the LessonPeriod of the given PersonalNote.
+    """
+    if hasattr(obj, "lesson_period"):
+        if hasattr(obj.lesson_period, "lesson"):
+            sub = obj.lesson_period.get_substitution()
+            if sub and user.person in Person.objects.filter(
+                lesson_substitutions=obj.lesson_period.get_substitution()
+            ):
+                return True
+
+            return user.person in obj.lesson_period.lesson.teachers.all()
+
+        return False
+    return False
+
+
+@predicate
+def is_personal_note_lesson_parent_group_owner(user: User, obj: PersonalNote) -> bool:
+    """
+    Predicate for parent group owners of a lesson referred to in the lesson period of a personal note.
+
+    Checks whether the person linked to the user is the owner of
+    any parent groups of any groups of the given LessonPeriod lesson of the given PersonalNote.
+    """
+    if hasattr(obj, "lesson_period"):
+        if hasattr(obj.lesson_period, "lesson"):
+            return obj.lesson_period.lesson.groups.filter(
+                parent_groups__owners=user.person
+            ).exists()
+        return False
+    return False
+
+
+@predicate
+def has_any_object_absence(user: User) -> bool:
+    """
+    Predicate which builds a query with all the persons the given users is allowed to register an absence for.
+    """
+    if get_objects_for_user(user, "core.register_absence_person", Person).exists():
+        return True
+    if Person.objects.filter(member_of__owners=user.person).exists():
+        return True
+    if Person.objects.filter(
+        member_of__in=get_objects_for_user(user, "core.register_absence_group", Group)
+    ).exists():
+        return True
+
+
+@predicate
+def is_teacher(user: User, obj: Person) -> bool:
+    """Predicate which checks if the provided object is a teacher."""
+    return user.person.is_teacher
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 0919d0ed4856c947882f5e84d24767bc687cd875..6120e8dca3e0ba06bc0d93ed05c1b11adee003c7 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -13,7 +13,7 @@ import reversion
 from calendarweek import CalendarWeek
 from django_tables2 import SingleTableView
 from reversion.views import RevisionMixin
-from rules.contrib.views import PermissionRequiredMixin
+from rules.contrib.views import PermissionRequiredMixin, permission_required
 
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.apps.chronos.models import LessonPeriod, TimePeriod
@@ -34,8 +34,10 @@ from .forms import (
 )
 from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 from .tables import ExcuseTypeTable, ExtraMarkTable
+from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk
 
 
+@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk)
 def lesson(
     request: HttpRequest,
     year: Optional[int] = None,
@@ -44,27 +46,16 @@ def lesson(
 ) -> HttpResponse:
     context = {}
 
-    if year and week and period_id:
-        # Get a specific lesson period if provided in URL
-        wanted_week = CalendarWeek(year=year, week=week)
-        lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(
-            pk=period_id
-        )
-
-        date_of_lesson = week_weekday_to_date(wanted_week, lesson_period.period.weekday)
+    lesson_period = get_lesson_period_by_pk(request, year, week, period_id)
 
-        if (
-            date_of_lesson < lesson_period.lesson.validity.date_start
-            or date_of_lesson > lesson_period.lesson.validity.date_end
-        ):
-            return HttpResponseNotFound()
-    else:
-        # Determine current lesson by current date and time
-        lesson_period = (
-            LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
-        )
+    if period_id:
+        wanted_week = CalendarWeek(year=year, week=week)
+    elif hasattr(request, "user") and hasattr(request.user, "person"):
         wanted_week = CalendarWeek()
+    else:
+        wanted_week = None
 
+    if not all((year, week, period_id)):
         if lesson_period:
             return redirect(
                 "lesson_by_week_and_period",
@@ -80,6 +71,14 @@ def lesson(
                 )
             )
 
+    date_of_lesson = week_weekday_to_date(wanted_week, lesson_period.period.weekday)
+
+    if (
+        date_of_lesson < lesson_period.lesson.validity.date_start
+        or date_of_lesson > lesson_period.lesson.validity.date_end
+    ):
+        return HttpResponseNotFound()
+
     if (
         datetime.combine(
             wanted_week[lesson_period.period.weekday], lesson_period.period.time_start,
@@ -110,13 +109,20 @@ def lesson(
     )
 
     # Create a formset that holds all personal notes for all persons in this lesson
-    persons_qs = lesson_period.get_personal_notes(wanted_week)
+    if not request.user.has_perm("alsijil.view_lesson_personalnote", lesson_period):
+        persons = Person.objects.filter(pk=request.user.person.pk)
+    else:
+        persons = Person.objects.all()
+
+    persons_qs = lesson_period.get_personal_notes(persons, wanted_week)
     personal_note_formset = PersonalNoteFormSet(
         request.POST or None, queryset=persons_qs, prefix="personal_notes"
     )
 
     if request.method == "POST":
-        if lesson_documentation_form.is_valid():
+        if lesson_documentation_form.is_valid() and request.user.has_perm(
+            "alsijil.edit_lessondocumentation", lesson_period
+        ):
             lesson_documentation_form.save()
 
             messages.success(request, _("The lesson documentation has been saved."))
@@ -126,7 +132,9 @@ def lesson(
             not getattr(substitution, "cancelled", False)
             or not get_site_preferences()["alsijil__block_personal_notes_for_cancelled"]
         ):
-            if personal_note_formset.is_valid():
+            if personal_note_formset.is_valid() and request.user.has_perm(
+                "alsijil.edit_lesson_personalnote", lesson_period
+            ):
                 with reversion.create_revision():
                     instances = personal_note_formset.save()
 
@@ -154,6 +162,7 @@ def lesson(
     return render(request, "alsijil/class_register/lesson.html", context)
 
 
+@permission_required("alsijil.view_week", fn=get_timetable_instance_by_pk)
 def week_view(
     request: HttpRequest,
     year: Optional[int] = None,
@@ -168,7 +177,9 @@ def week_view(
     else:
         wanted_week = CalendarWeek()
 
-    lesson_periods = LessonPeriod.objects.annotate(
+    instance = get_timetable_instance_by_pk(request, year, week, type_, id_)
+
+    lesson_periods = LessonPeriod.objects.in_week(wanted_week).annotate(
         has_documentation=Exists(
             LessonDocumentation.objects.filter(
                 ~Q(topic__exact=""),
@@ -177,23 +188,16 @@ def week_view(
                 year=wanted_week.year,
             )
         )
-    ).in_week(wanted_week)
+    )
 
-    group = None
     if type_ and id_:
-        instance = get_el_by_pk(request, type_, id_)
-
         if isinstance(instance, HttpResponseNotFound):
             return HttpResponseNotFound()
 
         type_ = TimetableType.from_string(type_)
 
-        if type_ == TimetableType.GROUP:
-            group = instance
-
         lesson_periods = lesson_periods.filter_from_type(type_, instance)
     elif hasattr(request, "user") and hasattr(request.user, "person"):
-        instance = request.user.person
         if request.user.person.lessons_as_teacher.exists():
             lesson_periods = lesson_periods.filter_teacher(request.user.person)
             type_ = TimetableType.TEACHER
@@ -222,13 +226,20 @@ def week_view(
                     select_form.cleaned_data["instance"].pk,
                 )
 
+    if type_ == TimetableType.GROUP:
+        group = instance
+    else:
+        group = None
+
     if lesson_periods:
         # Aggregate all personal notes for this group and week
         lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
 
         persons_qs = Person.objects.filter(is_active=True)
 
-        if group:
+        if not request.user.has_perm("alsijil.view_week_personalnote", instance):
+            persons_qs = persons_qs.filter(pk=request.user.person.pk)
+        elif group:
             persons_qs = persons_qs.filter(member_of=group)
         else:
             persons_qs = persons_qs.filter(
@@ -340,6 +351,9 @@ def week_view(
     return render(request, "alsijil/class_register/week_view.html", context)
 
 
+@permission_required(
+    "alsijil.view_full_register", fn=objectgetter_optional(Group, None, False)
+)
 def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     context = {}
 
@@ -461,6 +475,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
     return render(request, "alsijil/print/full_register.html", context)
 
 
+@permission_required("alsijil.view_my_students")
 def my_students(request: HttpRequest) -> HttpResponse:
     context = {}
     relevant_groups = request.user.person.get_owner_groups_with_lessons()
@@ -469,12 +484,17 @@ def my_students(request: HttpRequest) -> HttpResponse:
     return render(request, "alsijil/class_register/persons.html", context)
 
 
+@permission_required("alsijil.view_my_groups",)
 def my_groups(request: HttpRequest) -> HttpResponse:
     context = {}
     context["groups"] = request.user.person.get_owner_groups_with_lessons()
     return render(request, "alsijil/class_register/groups.html", context)
 
 
+@permission_required(
+    "alsijil.view_person_overview",
+    fn=objectgetter_optional(Person, "request.user.person", True),
+)
 def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     context = {}
     person = objectgetter_optional(
@@ -505,6 +525,11 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
                             request.POST["date"], "%Y-%m-%d"
                         ).date()
 
+                        if not request.user.has_perm(
+                            "alsijil.edit_person_overview_personalnote", person
+                        ):
+                            raise PermissionDenied()
+
                         notes = person.personal_notes.filter(
                             week=date.isocalendar()[1],
                             lesson_period__period__weekday=date.weekday(),
@@ -530,6 +555,8 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
                         note = PersonalNote.objects.get(
                             pk=int(request.POST["personal_note"])
                         )
+                        if not request.user.has_perm("alsijil.edit_personalnote", note):
+                            raise PermissionDenied()
                         if note.absent:
                             note.excused = True
                             note.excuse_type = excuse_type
@@ -543,10 +570,17 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
 
                 person.refresh_from_db()
 
-    unexcused_absences = person.personal_notes.filter(absent=True, excused=False)
+    if request.user.has_perm("alsijil.view_person_overview_personalnote", person):
+        allowed_personal_notes = person.personal_notes.all()
+    else:
+        allowed_personal_notes = person.personal_notes.filter(
+            lesson_period__lesson__groups__owners=request.user.person
+        )
+
+    unexcused_absences = allowed_personal_notes.filter(absent=True, excused=False)
     context["unexcused_absences"] = unexcused_absences
 
-    personal_notes = person.personal_notes.filter(
+    personal_notes = allowed_personal_notes.filter(
         Q(absent=True) | Q(late__gt=0) | ~Q(remarks="") | Q(extra_marks__isnull=False)
     ).order_by(
         "-lesson_period__lesson__validity__date_start",
@@ -557,60 +591,66 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
     context["personal_notes"] = personal_notes
     context["excuse_types"] = ExcuseType.objects.all()
 
-    school_terms = SchoolTerm.objects.all().order_by("-date_start")
-    stats = []
-    for school_term in school_terms:
-        stat = {}
-        personal_notes = PersonalNote.objects.filter(
-            person=person, lesson_period__lesson__validity__school_term=school_term
-        )
-
-        if not personal_notes.exists():
-            continue
-
-        stat.update(
-            personal_notes.filter(absent=True).aggregate(absences_count=Count("absent"))
-        )
-        stat.update(
-            personal_notes.filter(
-                absent=True, excused=True, excuse_type__isnull=True
-            ).aggregate(excused=Count("absent"))
-        )
-        stat.update(
-            personal_notes.filter(absent=True, excused=False).aggregate(
-                unexcused=Count("absent")
+    if request.user.has_perm("alsijil.view_person_statistics_personalnote", person):
+        school_terms = SchoolTerm.objects.all().order_by("-date_start")
+        stats = []
+        for school_term in school_terms:
+            stat = {}
+            personal_notes = PersonalNote.objects.filter(
+                person=person, lesson_period__lesson__validity__school_term=school_term
             )
-        )
-        stat.update(personal_notes.aggregate(tardiness=Sum("late")))
 
-        for extra_mark in ExtraMark.objects.all():
+            if not personal_notes.exists():
+                continue
+
             stat.update(
-                personal_notes.filter(extra_marks=extra_mark).aggregate(
-                    **{extra_mark.count_label: Count("pk")}
+                personal_notes.filter(absent=True).aggregate(
+                    absences_count=Count("absent")
                 )
             )
-
-        for excuse_type in ExcuseType.objects.all():
             stat.update(
-                personal_notes.filter(absent=True, excuse_type=excuse_type).aggregate(
-                    **{excuse_type.count_label: Count("absent")}
+                personal_notes.filter(
+                    absent=True, excused=True, excuse_type__isnull=True
+                ).aggregate(excused=Count("absent"))
+            )
+            stat.update(
+                personal_notes.filter(absent=True, excused=False).aggregate(
+                    unexcused=Count("absent")
                 )
             )
+            stat.update(personal_notes.aggregate(tardiness=Sum("late")))
 
-        stats.append((school_term, stat))
-    context["stats"] = stats
+            for extra_mark in ExtraMark.objects.all():
+                stat.update(
+                    personal_notes.filter(extra_marks=extra_mark).aggregate(
+                        **{extra_mark.count_label: Count("pk")}
+                    )
+                )
+
+            for excuse_type in ExcuseType.objects.all():
+                stat.update(
+                    personal_notes.filter(
+                        absent=True, excuse_type=excuse_type
+                    ).aggregate(**{excuse_type.count_label: Count("absent")})
+                )
+
+            stats.append((school_term, stat))
+        context["stats"] = stats
     context["excuse_types"] = ExcuseType.objects.all()
     context["extra_marks"] = ExtraMark.objects.all()
     return render(request, "alsijil/class_register/person.html", context)
 
 
+@permission_required("alsijil.view_register_absence")
 def register_absence(request: HttpRequest) -> HttpResponse:
     context = {}
 
     register_absence_form = RegisterAbsenceForm(request.POST or None)
 
     if request.method == "POST":
-        if register_absence_form.is_valid():
+        if register_absence_form.is_valid() and request.user.has_perm(
+            "alsijil.register_absence", register_absence_form.cleaned_data["person"]
+        ):
             # Get data from form
             person = register_absence_form.cleaned_data["person"]
             start_date = register_absence_form.cleaned_data["date_start"]
@@ -649,9 +689,10 @@ def register_absence(request: HttpRequest) -> HttpResponse:
     return render(request, "alsijil/absences/register.html", context)
 
 
-class DeletePersonalNoteView(DetailView):
+class DeletePersonalNoteView(PermissionRequiredMixin, DetailView):
     model = PersonalNote
     template_name = "core/pages/delete.html"
+    permission_required = "alsijil.edit_personalnote"
 
     def post(self, request, *args, **kwargs):
         note = self.get_object()
@@ -662,83 +703,83 @@ class DeletePersonalNoteView(DetailView):
         return redirect("overview_person", note.person.pk)
 
 
-class ExtraMarkListView(SingleTableView, PermissionRequiredMixin):
+class ExtraMarkListView(PermissionRequiredMixin, SingleTableView):
     """Table of all extra marks."""
 
     model = ExtraMark
     table_class = ExtraMarkTable
-    permission_required = "core.view_extramark"
+    permission_required = "alsijil.view_extramark"
     template_name = "alsijil/extra_mark/list.html"
 
 
-class ExtraMarkCreateView(AdvancedCreateView, PermissionRequiredMixin):
+class ExtraMarkCreateView(PermissionRequiredMixin, AdvancedCreateView):
     """Create view for extra marks."""
 
     model = ExtraMark
     form_class = ExtraMarkForm
-    permission_required = "core.create_extramark"
+    permission_required = "alsijil.create_extramark"
     template_name = "alsijil/extra_mark/create.html"
     success_url = reverse_lazy("extra_marks")
     success_message = _("The extra mark has been created.")
 
 
-class ExtraMarkEditView(AdvancedEditView, PermissionRequiredMixin):
+class ExtraMarkEditView(PermissionRequiredMixin, AdvancedEditView):
     """Edit view for extra marks."""
 
     model = ExtraMark
     form_class = ExtraMarkForm
-    permission_required = "core.edit_extramark"
+    permission_required = "alsijil.edit_extramark"
     template_name = "alsijil/extra_mark/edit.html"
     success_url = reverse_lazy("extra_marks")
     success_message = _("The extra mark has been saved.")
 
 
-class ExtraMarkDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionMixin):
+class ExtraMarkDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView):
     """Delete view for extra marks"""
 
     model = ExtraMark
-    permission_required = "core.delete_extramark"
+    permission_required = "alsijil.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):
+class ExcuseTypeListView(PermissionRequiredMixin, SingleTableView):
     """Table of all excuse types."""
 
     model = ExcuseType
     table_class = ExcuseTypeTable
-    permission_required = "core.view_excusetype"
+    permission_required = "alsijil.view_excusetypes"
     template_name = "alsijil/excuse_type/list.html"
 
 
-class ExcuseTypeCreateView(AdvancedCreateView, PermissionRequiredMixin):
+class ExcuseTypeCreateView(PermissionRequiredMixin, AdvancedCreateView):
     """Create view for excuse types."""
 
     model = ExcuseType
     form_class = ExcuseTypeForm
-    permission_required = "core.create_excusetype"
+    permission_required = "alsijil.add_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):
+class ExcuseTypeEditView(PermissionRequiredMixin, AdvancedEditView):
     """Edit view for excuse types."""
 
     model = ExcuseType
     form_class = ExcuseTypeForm
-    permission_required = "core.edit_excusetype"
+    permission_required = "alsijil.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):
+class ExcuseTypeDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView):
     """Delete view for excuse types"""
 
     model = ExcuseType
-    permission_required = "core.delete_excusetype"
+    permission_required = "alsijil.delete_excusetype"
     template_name = "core/pages/delete.html"
     success_url = reverse_lazy("excuse_types")
     success_message = _("The excuse type has been deleted.")