diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 7b74944b11716f608edf15958f90f7c5aec4c7ca..de9822024a9971276e4001017c15a3bf4018a7f1 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,13 +76,41 @@ class SelectForm(forms.Form):
         return data
 
     def __init__(self, *args, **kwargs):
+        self.request = get_request()
         super().__init__(*args, **kwargs)
-        self.fields["group"].queryset = (
+
+        person = self.request.user.person
+
+        group_pks = (
             Group.objects.for_current_school_term_or_all()
             .annotate(lessons_count=Count("lessons"))
             .filter(lessons_count__gt=0)
+            .values_list("pk", flat=True)
+        )
+        group_qs = Group.objects.filter(
+            Q(child_groups__pk__in=group_pks) | Q(pk__in=group_pks)
+        ).distinct()
+
+        if not check_global_permission(self.request.user, "alsijil.view_week"):
+            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)))
+        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)
+        if not check_global_permission(self.request.user, "alsijil.view_week"):
+            teacher_qs = teacher_qs.filter(pk=person.pk)
+
+        self.fields["teacher"].queryset = teacher_qs
+
 
 PersonalNoteFormSet = forms.modelformset_factory(
     PersonalNote, form=PersonalNoteForm, max_num=0, extra=0
@@ -99,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(
@@ -115,9 +141,29 @@ class RegisterAbsenceForm(forms.Form):
     remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False)
 
     def __init__(self, *args, **kwargs):
+        self.request = kwargs.pop("request")
         super().__init__(*args, **kwargs)
         period_choices = TimePeriod.period_choices
-
+        if check_global_permission(self.request.user, "alsijil.register_absence"):
+            self.fields["person"].queryset = Person.objects.all()
+        else:
+            self.fields["person"].queryset = (
+                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
+                        )
+                    )
+                )
+            )
         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/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/alsijil/locale/de_DE/LC_MESSAGES/django.po
index 37a276b8825ec136cb31b42dbfd0158d5cf1f679..e7d4526046014d7d7055ab8d94f20dc42e81a242 100644
--- a/aleksis/apps/alsijil/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/alsijil/locale/de_DE/LC_MESSAGES/django.po
@@ -8,15 +8,16 @@ msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-08-21 12:39+0200\n"
-"PO-Revision-Date: 2020-08-03 18:41+0000\n"
+"PO-Revision-Date: 2020-08-21 14:02+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
-"Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-app-alsijil/de/>\n"
+"Language-Team: German <https://translate.edugit.org/projects/aleksis/"
+"aleksis-app-alsijil/de/>\n"
 "Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.0.1\n"
+"X-Generator: Weblate 4.1.1\n"
 
 #: forms.py:26
 msgid "Homework for the next lesson"
@@ -44,16 +45,12 @@ msgid "End date"
 msgstr "Enddatum"
 
 #: forms.py:102
-#, fuzzy
-#| msgid "From period"
 msgid "Start period"
-msgstr "Ab Stunde"
+msgstr "Startstunde"
 
 #: forms.py:103
-#, fuzzy
-#| msgid "From period"
 msgid "End period"
-msgstr "Ab Stunde"
+msgstr "Endstunde"
 
 #: forms.py:105 templates/alsijil/class_register/lesson.html:163
 msgid "Person"
@@ -103,15 +100,13 @@ msgid "Current week"
 msgstr "Aktuelle Woche"
 
 #: menus.py:28
-#, fuzzy
-#| msgid "Personal overview"
 msgid "My overview"
-msgstr "Persönliche Übersicht"
+msgstr "Meine Übersicht"
 
 #: menus.py:34 templates/alsijil/class_register/persons.html:7
 #: templates/alsijil/class_register/persons.html:11
 msgid "My students"
-msgstr ""
+msgstr "Meine Schüler*innen"
 
 #: menus.py:40 templates/alsijil/absences/register.html:5
 #: templates/alsijil/absences/register.html:6
@@ -141,7 +136,7 @@ msgstr "Name"
 
 #: models.py:54 models.py:106
 msgid "Year"
-msgstr ""
+msgstr "Jahr"
 
 #: models.py:86
 msgid "Personal note"
@@ -185,19 +180,25 @@ msgstr "Blockiere das Hinzufügen von persönlichen Notizen für ausgefallene St
 
 #: preferences.py:25
 msgid "Carry over data from first lesson period to the following lesson periods in lessons over multiple periods"
-msgstr ""
+msgstr "Daten von der ersten Stunde zu weiteren folgenden Stunden übernehmen"
 
 #: preferences.py:28
 msgid "This will carry over data only if the data in the following periods are empty."
 msgstr ""
+"Dies wird die Daten nur übernehmen, wenn die Daten in den Folgestunden leer "
+"sind."
 
 #: preferences.py:38
 msgid "Allow teachers to open lesson periods on the same day and not just at the beginning of the period"
 msgstr ""
+"Erlaube Lehrkräften, Unterrichtsstunden bereits am gleichen Tag und nicht "
+"erst zu Beginn der Stunde zu öffnen"
 
 #: preferences.py:41
 msgid "Lessons in the past are not affected by this setting, you can open them whenever you want."
 msgstr ""
+"Unterrichtsstunden in der Vergangenheit werden nicht durch diese Einstellung "
+"beeinflusst, sie können immer geöffnet werden."
 
 #: tables.py:16 tables.py:36
 msgid "Edit"
@@ -265,10 +266,8 @@ msgid "Tardiness (in m)"
 msgstr "Verspätung (in m)"
 
 #: templates/alsijil/class_register/person.html:7
-#, fuzzy
-#| msgid "Class register:"
 msgid "Class register: person"
-msgstr "Klassenbuch:"
+msgstr "Klassenbuch: Person"
 
 #: templates/alsijil/class_register/person.html:11
 #, python-format
@@ -277,29 +276,28 @@ msgid ""
 "    Class register overview for %(person)s\n"
 "  "
 msgstr ""
+"\n"
+"    Klassenbuchübersicht für %(person)s\n"
+"  "
 
 #: templates/alsijil/class_register/person.html:19
-#, fuzzy
-#| msgid "Unexcused"
 msgid "Unexcused absences"
-msgstr "Unentschuldigt"
+msgstr "Unentschuldigte Fehlzeiten"
 
 #: templates/alsijil/class_register/person.html:27
 #: templates/alsijil/class_register/person.html:40
 #: templates/alsijil/class_register/person.html:152
 #: templates/alsijil/class_register/person.html:190
 msgid "Mark as"
-msgstr ""
+msgstr "Markiere als"
 
 #: templates/alsijil/class_register/person.html:47
 msgid "There are unexcused lessons."
-msgstr ""
+msgstr "Es gibt keine unentschuldigten Unterrichtsstunden."
 
 #: templates/alsijil/class_register/person.html:51
-#, fuzzy
-#| msgid "Statistics on remarks"
 msgid "Statistics on absences, tardiness and remarks"
-msgstr "Statistiken zu Bemerkungen"
+msgstr "Statistiken zu Fehlzeiten, Verspätungen und Bemerkungen"
 
 #: templates/alsijil/class_register/person.html:60
 #: templates/alsijil/print/full_register.html:269
@@ -325,23 +323,21 @@ msgstr "Relevante persönliche Notizen"
 #: templates/alsijil/class_register/person.html:110
 #, python-format
 msgid "Week %(week)s"
-msgstr ""
+msgstr "Woche %(week)s"
 
 #: templates/alsijil/class_register/person.html:118
 #: templates/alsijil/class_register/person.html:127
 msgid "Mark all as"
-msgstr ""
+msgstr "Alle als markieren"
 
 #: templates/alsijil/class_register/person.html:175
 #, python-format
 msgid "%(late)s' late"
-msgstr ""
+msgstr "%(late)s' verspätet"
 
 #: templates/alsijil/class_register/persons.html:22
-#, fuzzy
-#| msgid "No lessons available"
 msgid "No students available."
-msgstr "Keine Stunden verfügbar"
+msgstr "Keine Schüler*innen verfügbar."
 
 #: templates/alsijil/class_register/week_view.html:6
 msgid "Week view"
@@ -352,8 +348,7 @@ msgid "Select"
 msgstr "Auswählen"
 
 #: templates/alsijil/class_register/week_view.html:38
-#, fuzzy, python-format
-#| msgid "CW %(week)s: %(instance)s"
+#, python-format
 msgid ""
 "CW %(week)s:\n"
 "      %(instance)s"
@@ -672,16 +667,12 @@ msgid "There is no current school term."
 msgstr "Es gibt aktuell kein Schuljahr."
 
 #: views.py:513
-#, fuzzy
-#| msgid "The absence has been saved."
 msgid "The absences have been marked as excused."
-msgstr "Die Abwesenheit wurde gespeichert."
+msgstr "Die Fehlzeiten wurden als entschuldigt markiert."
 
 #: views.py:528
-#, fuzzy
-#| msgid "The absence has been saved."
 msgid "The absence has been marked as excused."
-msgstr "Die Abwesenheit wurde gespeichert."
+msgstr "Die Fehlzeit wurde als entschuldigt markiert."
 
 #: views.py:633
 msgid "The absence has been saved."
diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py
index d20c88b567ad7d4feb86231eafa4e35d880f9e1c..46ca6d023b3a7deff2e10c10dbf49ee7ab0738c1 100644
--- a/aleksis/apps/alsijil/menus.py
+++ b/aleksis/apps/alsijil/menus.py
@@ -16,43 +16,78 @@ 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 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 e381407c49f10943904f11d50f6d56a7d8c31757..4c5872aa8e3cbe73cd06b48e99cb75392ff5ea19 100644
--- a/aleksis/apps/alsijil/model_extensions.py
+++ b/aleksis/apps/alsijil/model_extensions.py
@@ -2,7 +2,9 @@ from datetime import date
 from typing import Dict, Optional, Union
 
 from django.db.models import Exists, OuterRef, QuerySet
+from django.utils.translation import gettext as _
 
+import reversion
 from calendarweek import CalendarWeek
 
 from aleksis.apps.chronos.models import LessonPeriod
@@ -36,8 +38,10 @@ def mark_absent(
     wanted_week = CalendarWeek.from_date(day)
 
     # Get all lessons of this person on the specified day
-    lesson_periods = self.lesson_periods_as_participant.on_day(day).filter(
-        period__period__gte=from_period
+    lesson_periods = (
+        self.lesson_periods_as_participant.on_day(day)
+        .filter(period__period__gte=from_period)
+        .annotate_week(wanted_week)
     )
 
     if to_period:
@@ -45,25 +49,34 @@ def mark_absent(
 
     # Create and update all personal notes for the discovered lesson periods
     for lesson_period in lesson_periods:
-        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.groups_of_person.set(self.member_of.all())
+        sub = lesson_period.get_substitution()
+        if sub and sub.is_cancelled:
+            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.groups_of_person.set(self.member_of.all())
 
-        if remarks:
-            if personal_note.remarks:
-                personal_note.remarks += "; %s" % remarks
-            else:
-                personal_note.remarks = remarks
-            personal_note.save()
+            if remarks:
+                if personal_note.remarks:
+                    personal_note.remarks += "; %s" % remarks
+                else:
+                    personal_note.remarks = remarks
+                personal_note.save()
 
 
 @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,
@@ -76,7 +89,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,
@@ -107,10 +120,44 @@ def get_personal_notes(self, wanted_week: CalendarWeek):
         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
+        lesson_period=self,
+        week=wanted_week.week,
+        year=wanted_week.year,
+        person__in=persons,
     )
 
 
+# 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 721648bc4f52ddb4728de84f21319635ee9a014f..66f6477d22165c085abd0855502686d505d75c75 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,4 +1,5 @@
 from django.db import models
+from django.utils.formats import date_format
 from django.utils.translation import gettext_lazy as _
 
 from calendarweek import CalendarWeek
@@ -82,6 +83,25 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
             self.excuse_type = None
         super().save(*args, **kwargs)
 
+    def reset_values(self):
+        """Reset all saved data to default values.
+
+        .. warning ::
+
+            This won't save the data, please execute ``save`` extra.
+        """
+        defaults = PersonalNote()
+
+        self.absent = defaults.absent
+        self.late = defaults.late
+        self.excused = defaults.excused
+        self.excuse_type = defaults.excuse_type
+        self.remarks = defaults.remarks
+        self.extra_marks.clear()
+
+    def __str__(self):
+        return f"{date_format(self.date)}, {self.lesson_period}, {self.person}"
+
     class Meta:
         verbose_name = _("Personal note")
         verbose_name_plural = _("Personal notes")
@@ -188,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..9958d72d831b8978178301c8fa7123d0222e47e4 100644
--- a/aleksis/apps/alsijil/preferences.py
+++ b/aleksis/apps/alsijil/preferences.py
@@ -16,6 +16,14 @@ 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 CarryOverDataToNextPeriods(BooleanPreference):
     section = alsijil
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
new file mode 100644
index 0000000000000000000000000000000000000000..065c9c95c607271eb7ec814c171fe2a9f381063c
--- /dev/null
+++ b/aleksis/apps/alsijil/rules.py
@@ -0,0 +1,220 @@
+from rules import add_perm
+
+from aleksis.core.util.predicates import (
+    has_global_perm,
+    has_object_perm,
+    has_person,
+    is_current_person,
+)
+
+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_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_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 = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_lesson_group_object_perm("core.view_personalnote_group")
+    | is_lesson_teacher
+    | is_lesson_parent_group_owner
+)
+add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate)
+
+# Edit personal note
+edit_lesson_personal_note_predicate = has_person & (
+    has_global_perm("alsijil.change_personalnote")
+    | has_lesson_group_object_perm("core.edit_personalnote_group")
+    | is_lesson_teacher
+)
+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")
+    | has_personal_note_group_perm("core.view_personalnote_group")
+    | is_personal_note_lesson_teacher
+    | is_own_personal_note
+    | is_personal_note_lesson_parent_group_owner
+)
+add_perm("alsijil.view_personalnote", view_personal_note_predicate)
+
+# Edit personal note
+edit_personal_note_predicate = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_personal_note_group_perm("core.edit_personalnote_group")
+    | is_personal_note_lesson_teacher
+    | is_personal_note_lesson_parent_group_owner
+)
+add_perm("alsijil.edit_personalnote", edit_personal_note_predicate)
+
+# View lesson documentation
+view_lesson_documentation_predicate = has_person & (
+    has_global_perm("alsijil.view_lessondocumentation")
+    | has_lesson_group_object_perm("core.view_lessondocumentation_group")
+    | is_lesson_teacher
+    | is_lesson_parent_group_owner
+    | is_lesson_participant
+)
+add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate)
+
+# Edit lesson documentation
+edit_lesson_documentation_predicate = has_person & (
+    has_global_perm("alsijil.change_lessondocumentation")
+    | has_lesson_group_object_perm("core.edit_lessondocumentation_group")
+    | is_lesson_teacher
+)
+add_perm("alsijil.edit_lessondocumentation", edit_lesson_documentation_predicate)
+
+# View week overview
+view_week_predicate = has_person & (
+    has_global_perm("alsijil.view_week")
+    | has_object_perm("core.view_week_class_register_group")
+    | is_group_member
+    | is_group_owner
+    | is_current_person
+)
+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
+)
+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 person overview
+view_person_overview_predicate = has_person & (
+    is_current_person | 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 = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_person_group_object_perm("core.view_personalnote_group")
+    | is_person_primary_group_owner
+    | is_current_person
+)
+add_perm(
+    "alsijil.view_person_overview_personalnote",
+    view_person_overview_personal_notes_predicate,
+)
+
+# Edit person overview personal notes
+edit_person_overview_personal_notes_predicate = has_person & (
+    has_global_perm("alsijil.edit_personalnote")
+    | has_person_group_object_perm("core.edit_personalnote_group")
+    | is_person_primary_group_owner
+)
+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 = has_person & (
+    has_global_perm("alsijil.view_personalnote")
+    | has_person_group_object_perm("core.view_personalnote_group")
+    | is_person_primary_group_owner
+    | is_current_person
+)
+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 = has_person & has_global_perm("alsijil.add_excusetype")
+add_perm("alsijil.add_excusetype", add_excusetype_predicate)
+
+# Edit excuse type
+edit_excusetype_predicate = has_person & has_global_perm("alsijil.change_excusetype")
+add_perm("alsijil.edit_excusetype", edit_excusetype_predicate)
+
+# Delete excuse type
+delete_excusetype_predicate = has_person & 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 = has_person & has_global_perm("alsijil.add_extramark")
+add_perm("alsijil.add_extramark", add_extramark_predicate)
+
+# Edit extra mark
+edit_extramark_predicate = has_person & has_global_perm("alsijil.change_extramark")
+add_perm("alsijil.edit_extramark", edit_extramark_predicate)
+
+# Delete extra mark
+delete_extramark_predicate = has_person & 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 5a442b81c4fbb2956034bb26dc1f7c98ccbab4f8..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,12 +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" %}
-          </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>
@@ -35,12 +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" %}
-          </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">
@@ -48,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>
@@ -113,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 %}
 
@@ -146,13 +168,23 @@
                   </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" %}
                         <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>
+                    {% 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 %}
@@ -184,13 +216,23 @@
 
                   </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" %}
                         <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>
+                    {% 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 bb0bfd771378be43f70a76b629bb8f3d6dc5ebc7..dadf8f5d35c55d3da1c352abd409a2ffdc1329d4 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>
@@ -140,7 +143,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/urls.py b/aleksis/apps/alsijil/urls.py
index 0482571bd3cd9cf7c7da4a15b4cd7fc3e0a58c36..e2bba60a0df002ccf4b110ea8ac5f1e22df8387a 100644
--- a/aleksis/apps/alsijil/urls.py
+++ b/aleksis/apps/alsijil/urls.py
@@ -29,6 +29,11 @@ urlpatterns = [
     path("persons/", views.my_students, name="my_students"),
     path("persons/<int:id_>/", views.overview_person, name="overview_person"),
     path("me/", views.overview_person, name="overview_me"),
+    path(
+        "notes/<int:pk>/delete/",
+        views.DeletePersonalNoteView.as_view(),
+        name="delete_personal_note",
+    ),
     path("absence/new", views.register_absence, name="register_absence"),
     path("extra_marks/", views.ExtraMarkListView.as_view(), name="extra_marks"),
     path(
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..51279dc62615a8d5432a3b654095d8d10c895eaa
--- /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_instance_by_pk(
+    request: HttpRequest,
+    year: Optional[int] = None,
+    week: Optional[int] = None,
+    type_: Optional[str] = None,
+    id_: Optional[int] = None,
+):
+    """Get Instance object 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..b33a9e27e97e06841f33749425669c56dbf863de
--- /dev/null
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -0,0 +1,236 @@
+from typing import 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.core_helpers import get_site_preferences
+from aleksis.core.util.predicates import check_object_permission
+
+from ..models import PersonalNote
+
+
+@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 True
+
+
+@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 True
+
+
+@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 True
+
+
+@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.
+    Is configurable via dynamic preferences.
+    """
+    if hasattr(obj, "person"):
+        if (
+            get_site_preferences()["alsijil__view_own_personal_notes"]
+            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 f9840a124ee64626238b77031039837b96525554..f8ce8211857eb8c2616be62fd6ad53bdc0175e9c 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -7,11 +7,13 @@ from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse, reverse_lazy
 from django.utils.translation import ugettext as _
+from django.views.generic import DetailView
 
+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
@@ -32,8 +34,10 @@ from .forms import (
 )
 from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
 from .tables import ExcuseTypeTable, ExtraMarkTable
+from .util.alsijil_helpers import get_instance_by_pk, get_lesson_period_by_pk
 
 
+@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk)
 def lesson(
     request: HttpRequest,
     year: Optional[int] = None,
@@ -42,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
-        )
+    lesson_period = get_lesson_period_by_pk(request, year, week, period_id)
 
-        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()
-    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 (year and week and period_id):
         if lesson_period:
             return redirect(
                 "lesson_by_week_and_period",
@@ -78,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,
@@ -108,13 +109,18 @@ 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)
+    persons = Person.objects.all()
+    if not request.user.has_perm("alsijil.view_lesson_personalnote", lesson_period):
+        persons = persons.filter(pk=request.user.person.pk)
+    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."))
@@ -124,8 +130,11 @@ def lesson(
             not getattr(substitution, "cancelled", False)
             or not get_site_preferences()["alsijil__block_personal_notes_for_cancelled"]
         ):
-            if personal_note_formset.is_valid():
-                instances = personal_note_formset.save()
+            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()
 
                 # Iterate over personal notes and carry changed absences to following lessons
                 for instance in instances:
@@ -151,6 +160,7 @@ def lesson(
     return render(request, "alsijil/class_register/lesson.html", context)
 
 
+@permission_required("alsijil.view_week", fn=get_instance_by_pk)
 def week_view(
     request: HttpRequest,
     year: Optional[int] = None,
@@ -165,7 +175,9 @@ def week_view(
     else:
         wanted_week = CalendarWeek()
 
-    lesson_periods = LessonPeriod.objects.annotate(
+    instance = get_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=""),
@@ -174,23 +186,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
@@ -219,13 +224,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(
@@ -337,6 +349,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 = {}
 
@@ -458,6 +473,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 = (
@@ -465,11 +481,15 @@ def my_students(request: HttpRequest) -> HttpResponse:
         .annotate(lessons_count=Count("lessons"))
         .filter(lessons_count__gt=0, owners=request.user.person)
     )
-    persons = Person.objects.filter(member_of__in=relevant_groups)
+    persons = Person.objects.filter(member_of__in=relevant_groups).distinct()
     context["persons"] = persons
     return render(request, "alsijil/class_register/persons.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(
@@ -500,6 +520,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(),
@@ -508,7 +533,12 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
                             absent=True,
                             excused=False,
                         )
-                        notes.update(excused=True, excuse_type=excuse_type)
+                        for note in notes:
+                            note.excused = True
+                            note.excuse_type = excuse_type
+                            with reversion.create_revision():
+                                note.save()
+
                         messages.success(
                             request, _("The absences have been marked as excused.")
                         )
@@ -520,10 +550,13 @@ 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
-                            note.save()
+                            with reversion.create_revision():
+                                note.save()
                             messages.success(
                                 request, _("The absence has been marked as excused.")
                             )
@@ -532,10 +565,20 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
 
                 person.refresh_from_db()
 
-    unexcused_absences = person.personal_notes.filter(absent=True, excused=False)
+    allowed_personal_notes = person.personal_notes.all()
+
+    if not request.user.has_perm("alsijil.view_person_overview_personalnote", person):
+        print("has")
+        allowed_personal_notes = allowed_personal_notes.filter(
+            lesson_period__lesson__groups__owners=request.user.person
+        )
+
+    print(allowed_personal_notes)
+
+    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",
@@ -546,60 +589,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)
+    register_absence_form = RegisterAbsenceForm(request.POST or None, request=request)
 
     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"]
@@ -638,83 +687,97 @@ def register_absence(request: HttpRequest) -> HttpResponse:
     return render(request, "alsijil/absences/register.html", context)
 
 
-class ExtraMarkListView(SingleTableView, PermissionRequiredMixin):
+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()
+        with reversion.create_revision():
+            note.reset_values()
+            note.save()
+        messages.success(request, _("The personal note has been deleted."))
+        return redirect("overview_person", note.person.pk)
+
+
+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.")