diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
index 8a82714c7d8de7117005b74da5b744faf0557076..5c4537f502af6415c76cf78f43bc19a73c149d2f 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue
@@ -65,7 +65,9 @@
 
     <template #no-results>
       <CoursebookEmptyMessage icon="mdi-book-alert-outline">
-        {{ $t("alsijil.coursebook.no_results", { search: $refs.iterator.search }) }}
+        {{
+          $t("alsijil.coursebook.no_results", { search: $refs.iterator.search })
+        }}
       </CoursebookEmptyMessage>
     </template>
   </c-r-u-d-iterator>
@@ -74,8 +76,12 @@
 <script>
 import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
 import DocumentationModal from "./documentation/DocumentationModal.vue";
-import {DateTime} from "luxon";
-import {coursesOfTeacher, documentationsForCoursebook, groupsByOwner,} from "./coursebook.graphql";
+import { DateTime } from "luxon";
+import {
+  coursesOfPerson,
+  documentationsForCoursebook,
+  groupsByPerson,
+} from "./coursebook.graphql";
 import CoursebookLoader from "./CoursebookLoader.vue";
 import CoursebookEmptyMessage from "./CoursebookEmptyMessage.vue";
 import CoursebookDateSelect from "./CoursebookDateSelect.vue";
@@ -126,10 +132,10 @@ export default {
   },
   apollo: {
     groups: {
-      query: groupsByOwner,
+      query: groupsByPerson,
     },
     courses: {
-      query: coursesOfTeacher,
+      query: coursesOfPerson,
     },
   },
   computed: {
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
index b3682de4216f7694c3b6b1f9bb1a2d23ea9a855e..ac54bff3b33861bab60ebad1928291bff66b8ddd 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookDateSelect.vue
@@ -44,14 +44,14 @@ export default {
       } else {
         this.$emit("input", DateTime.fromISO(this.value).plus({ days: 1 }).toISODate());
       }
-    }
+    },
   },
   emits: ["input", "click", "prev", "next"],
-}
+};
 </script>
 
 <template>
-<v-bottom-sheet
+  <v-bottom-sheet
     :value="true"
     persistent
     hide-overlay
@@ -88,4 +88,4 @@ export default {
   margin: auto;
   max-width: 500px;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookEmptyMessage.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookEmptyMessage.vue
index 059d613c3b4fb0a1d6b8234ae0c74c2d7a673cec..346ecc63c272ab5c57a1da9f5e5f78b825739a79 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookEmptyMessage.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookEmptyMessage.vue
@@ -14,12 +14,12 @@
 </template>
 <script>
 export default {
-  name: 'CoursebookEmptyMessage',
+  name: "CoursebookEmptyMessage",
   props: {
     icon: {
       type: String,
-      default: 'mdi-book-alert-outline',
+      default: "mdi-book-alert-outline",
     },
   },
-}
-</script>
\ No newline at end of file
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
index 5d7adabf478caa77a3cc2f1bbf1bd5ab16fe75b0..08064df0901ce59c0de868b522c3fa141aa9ed2a 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql
@@ -1,12 +1,12 @@
-query groupsByOwner {
-  groups: groupsByOwner {
+query groupsByPerson {
+  groups: groupsByPerson {
     id
     name
   }
 }
 
-query coursesOfTeacher {
-  courses: coursesOfTeacher {
+query coursesOfPerson {
+  courses: coursesOfPerson {
     id
     name
     groups {
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
index 0891fab875b4e1ea2117cd1b246e4aad7565824e..07918bc166c5b0717b30f11a8d4cf8e252ebadcb 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue
@@ -6,10 +6,7 @@
       class="full-width d-flex flex-column align-stretch"
       :class="{ 'flex-md-row': 'compact' in $attrs }"
     >
-      <lesson-information
-        class="flex-grow-1"
-        :documentation="documentation"
-      />
+      <lesson-information class="flex-grow-1" :documentation="documentation" />
       <lesson-summary
         class="flex-grow-1"
         ref="summary"
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue
index e67555bb1463f5fc375533801d5a3004d05df803..f5cb72e1b7195f4e14943599e76df5ab0365f332 100644
--- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue
@@ -1,19 +1,29 @@
 <template>
-  <v-card-text class="d-flex align-center justify-space-between justify-md-end flex-wrap gap">
+  <v-card-text
+    class="d-flex align-center justify-space-between justify-md-end flex-wrap gap"
+  >
     <v-chip dense color="success">
-      <v-chip small dense class="mr-2" color="green darken-3 white--text">26</v-chip>
+      <v-chip small dense class="mr-2" color="green darken-3 white--text"
+        >26</v-chip
+      >
       von 30 anwesend
     </v-chip>
     <v-chip dense color="warning">
-      <v-chip small dense class="mr-2" color="orange darken-3 white--text">3</v-chip>
+      <v-chip small dense class="mr-2" color="orange darken-3 white--text"
+        >3</v-chip
+      >
       entschuldigt
     </v-chip>
     <v-chip dense color="error">
-      <v-chip small dense class="mr-2" color="red darken-3 white--text">1</v-chip>
+      <v-chip small dense class="mr-2" color="red darken-3 white--text"
+        >1</v-chip
+      >
       unentschuldigt
     </v-chip>
     <v-chip dense color="grey lighten-1">
-      <v-chip small dense class="mr-2" color="grey darken-1 white--text">4</v-chip>
+      <v-chip small dense class="mr-2" color="grey darken-1 white--text"
+        >4</v-chip
+      >
       Hausaufgaben vergessen
     </v-chip>
   </v-card-text>
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 803efbfb8227c8f66597049f099c82b52411cb18..7356d23dad2949092bb31ab292c081c06fdb1e2b 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -1,7 +1,6 @@
 from datetime import date, datetime, timezone
 from typing import Optional, Union
 from urllib.parse import urlparse
-import json
 
 from django.db import models
 from django.db.models import QuerySet
@@ -536,10 +535,12 @@ class Documentation(CalendarEvent):
             "not_amended": True,
         }
         if obj_type is not None and obj_id is not None:
-            event_params.update({
-                "type": obj_type,
-                "id": obj_id,
-            })
+            event_params.update(
+                {
+                    "type": obj_type,
+                    "id": obj_id,
+                }
+            )
 
         events = LessonEvent.get_single_events(
             date_start,
diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py
index 9e39fb2760320ede8f98f9096326846ebec2c7d1..e3d9e43a6ac713428ce6882dc000c2c56a235ee5 100644
--- a/aleksis/apps/alsijil/rules.py
+++ b/aleksis/apps/alsijil/rules.py
@@ -16,6 +16,8 @@ from .util.predicates import (
     has_lesson_group_object_perm,
     has_person_group_object_perm,
     has_personal_note_group_perm,
+    is_course_group_owner,
+    is_course_member,
     is_course_teacher,
     is_group_member,
     is_group_owner,
@@ -363,12 +365,18 @@ view_documentation_predicate = has_person & (
 add_perm("alsijil.view_documentation_rule", view_documentation_predicate)
 
 view_documentations_for_course_predicate = has_person & (
-    has_global_perm("alsijil.view_documentation") | is_course_teacher
+    has_global_perm("alsijil.view_documentation")
+    | is_course_teacher
+    | is_course_member
+    | is_course_group_owner
 )
 add_perm("alsijil.view_documentations_for_course_rule", view_documentations_for_course_predicate)
 
 view_documentations_for_group_predicate = has_person & (
-    has_global_perm("alsijil.view_documentation") | is_group_owner
+    has_global_perm("alsijil.view_documentation")
+    | is_group_owner
+    | is_group_member
+    | is_parent_group_owner
 )
 add_perm("alsijil.view_documentations_for_group_rule", view_documentations_for_group_predicate)
 
diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py
index 1cfc2ca6526dc921a6d349c39c950fc0fc0cc664..037bf93c3bbf5e9e081d95accb3f56469d2ededf 100644
--- a/aleksis/apps/alsijil/schema/__init__.py
+++ b/aleksis/apps/alsijil/schema/__init__.py
@@ -1,7 +1,8 @@
-from django.db.models.query_utils import Q
+from datetime import datetime
+
 from django.core.exceptions import PermissionDenied
+from django.db.models.query_utils import Q
 
-from datetime import datetime
 import graphene
 
 from aleksis.apps.cursus.models import Course
@@ -11,9 +12,9 @@ from aleksis.core.schema.base import FilterOrderList
 from ..models import Documentation
 from .documentation import (
     DocumentationBatchCreateMutation,
+    DocumentationBatchCreateOrUpdateMutation,
     DocumentationBatchPatchMutation,
     DocumentationCreateMutation,
-    DocumentationBatchCreateOrUpdateMutation,
     DocumentationDeleteMutation,
     DocumentationType,
 )
diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py
index b72a7f50ffd3fd08fe5e013e7f3ac4687089e652..a2027c54d68deef8b765f3d1d381ba5fbc7f1858 100644
--- a/aleksis/apps/alsijil/schema/documentation.py
+++ b/aleksis/apps/alsijil/schema/documentation.py
@@ -1,4 +1,7 @@
 from datetime import datetime, timezone
+
+from django.core.exceptions import PermissionDenied
+
 import graphene
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
@@ -8,8 +11,6 @@ from graphene_django_cud.mutations import (
 )
 from guardian.shortcuts import get_objects_for_user
 
-from django.core.exceptions import PermissionDenied
-
 from aleksis.apps.chronos.models import LessonEvent
 from aleksis.core.schema.base import (
     DeleteMutation,
@@ -17,6 +18,7 @@ from aleksis.core.schema.base import (
     PermissionBatchPatchMixin,
     PermissionsTypeMixin,
 )
+
 from ..models import Documentation
 
 
@@ -137,11 +139,12 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
 
     @classmethod
     def create_or_update(cls, info, doc):
-        id = doc.id
+        _id = doc.id
 
-        # Sadly, we can't use the update_or_create method since create_defaults is only introduced in Django 5.0
-        if id.startswith("DUMMY"):
-            dummy, lesson_event_id, datetime_start, datetime_end = id.split(";")
+        # Sadly, we can't use the update_or_create method since create_defaults
+        # is only introduced in Django 5.0
+        if _id.startswith("DUMMY"):
+            dummy, lesson_event_id, datetime_start, datetime_end = _id.split(";")
             lesson_event = LessonEvent.objects.get(id=lesson_event_id)
 
             if not info.context.user.has_perm(
@@ -149,7 +152,8 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
             ):
                 raise PermissionDenied()
 
-            # Timezone removal is necessary due to ISO style offsets are no valid timezones. Instead, we take the timezone from the lesson_event and save it in a dedicated field.
+            # Timezone removal is necessary due to ISO style offsets are no valid timezones.
+            # Instead, we take the timezone from the lesson_event and save it in a dedicated field.
             return Documentation.objects.create(
                 datetime_start=datetime.fromisoformat(datetime_start).replace(tzinfo=timezone.utc),
                 datetime_end=datetime.fromisoformat(datetime_end).replace(tzinfo=timezone.utc),
@@ -162,7 +166,7 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
                 group_note=doc.group_note or "",
             )
         else:
-            obj = Documentation.objects.get(id=id)
+            obj = Documentation.objects.get(id=_id)
 
             if not info.context.user.has_perm("alsijil.edit_documentation_rule", obj):
                 raise PermissionDenied()
@@ -178,7 +182,7 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
             return obj
 
     @classmethod
-    def mutate(cls, root, info, input):
+    def mutate(cls, root, info, input):  # noqa
         objs = [cls.create_or_update(info, doc) for doc in input]
 
         return DocumentationBatchCreateOrUpdateMutation(documentations=objs)
diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py
index d984fafc043be23dfebb13692209a50a4883d690..615142a890c1b0abcd360637e26a58917a3c2970 100644
--- a/aleksis/apps/alsijil/util/predicates.py
+++ b/aleksis/apps/alsijil/util/predicates.py
@@ -312,7 +312,7 @@ def is_lesson_event_teacher(user: User, obj: LessonEvent):
     or a teacher of the course, if the lesson event has one.
     """
     if obj:
-        return obj.course and is_course_teacher(user, obj) or user.person in obj.all_teachers
+        return obj.course and is_course_teacher(user, obj.course) or user.person in obj.all_teachers
     return False
 
 
@@ -329,19 +329,57 @@ def is_course_member(user: User, obj: Course):
     return False
 
 
+@predicate
+def is_course_group_owner(user: User, obj: Course):
+    """Predicate for group owners of a course.
+
+    Checks whether the person linked to the user is a owner of any group
+    (or their respective parent groups) linked to the course.
+    """
+    if obj:
+        for g in obj.groups.all():
+            if user.person in g.owners.all():
+                return True
+            for pg in g.parent_groups.all():
+                if user.person in pg.owners.all():
+                    return True
+    return False
+
+
 @predicate
 def is_lesson_event_member(user: User, obj: LessonEvent):
     """Predicate for members of a lesson event.
 
-    Checks whether the person linked to the user is a members in the lesson event,
+    Checks whether the person linked to the user is a member in the lesson event,
     or a members of the course, if the lesson event has one.
     """
     if obj:
-        if obj.course and is_course_member(user, obj):
+        if obj.course and is_course_member(user, obj.course):
             return True
         for g in obj.groups.all():
             if user.person in g.members.all():
                 return True
+
+    return False
+
+
+@predicate
+def is_lesson_event_group_owner(user: User, obj: LessonEvent):
+    """Predicate for group owners of a lesson event.
+
+    Checks whether the person linked to the user is a owner of any group
+    (or their respective parent groups) linked to the lesson event,
+    or a owner of any group linked to the course, if the lesson event has one.
+    """
+    if obj:
+        if obj.course and is_course_group_owner(user, obj.course):
+            return True
+        for g in obj.groups.all():
+            if user.person in g.owners.all():
+                return True
+            for pg in g.parent_groups.all():
+                if user.person in pg.owners.all():
+                    return True
     return False
 
 
@@ -350,10 +388,16 @@ def can_view_documentation(user: User, obj: Documentation):
     """Predicate which checks if the user is allowed to view a documentation."""
     if obj:
         if obj.course:
-            return is_course_teacher(user, obj.course) | is_course_member(user, obj.course)
+            return (
+                is_course_teacher(user, obj.course)
+                | is_course_member(user, obj.course)
+                | is_course_group_owner(user, obj.course)
+            )
         if obj.lesson_event:
-            return is_lesson_event_teacher(user, obj.course) | is_lesson_event_member(
-                user, obj.course
+            return (
+                is_lesson_event_teacher(user, obj.lesson_event)
+                | is_lesson_event_member(user, obj.lesson_event)
+                | is_lesson_event_group_owner(user, obj.lesson_event)
             )
     return False
 
@@ -363,7 +407,9 @@ def can_edit_documentation(user: User, obj: Documentation):
     """Predicate which checks if the user is allowed to edit or delete a documentation."""
     if obj:
         if obj.course:
-            return is_course_teacher(user, obj.course)
+            return is_course_teacher(user, obj.course) | is_course_group_owner(user, obj.course)
         if obj.lesson_event:
-            return is_lesson_event_teacher(user, obj.course)
+            return is_lesson_event_teacher(user, obj.lesson_event) | is_lesson_event_group_owner(
+                user, obj.lesson_event
+            )
     return False