From c99bcf92e718dcb5a5b29c39c20888f9bfb20a55 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sun, 20 Mar 2022 21:34:46 +0100 Subject: [PATCH] Allow the owner of a parent group to be able to perform the same operations on the related object as the owners of all other groups the object is a member of --- CHANGELOG.rst | 6 +++ aleksis/apps/alsijil/forms.py | 44 +++++++++++++--- aleksis/apps/alsijil/preferences.py | 11 ++++ aleksis/apps/alsijil/rules.py | 53 +++++++++++++++++++- aleksis/apps/alsijil/util/alsijil_helpers.py | 24 +++++++-- aleksis/apps/alsijil/util/predicates.py | 26 ++++++++++ aleksis/apps/alsijil/views.py | 27 ++++++++-- 7 files changed, 178 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e1b242414..62b0fc17c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,12 @@ and this project adheres to `Semantic Versioning`_. Unreleased ---------- +Changed +~~~~~~~ + +* Owners of one of the parent groups of a object can now have the same rights on it +as a group owner (can be toggled with a preference). + `2.0.1`_ - 2022-02-12 --------------------- diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 3549c6088..39ddbebf1 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -111,13 +111,21 @@ class SelectForm(forms.Form): if not check_global_permission(self.request.user, "alsijil.view_week"): # 1) All groups the user is allowed to see the week view by object permissions # 2) All groups the user is a member of an owner of + # 3) If the corresponding preference is turned on: + # All groups that have a parent group the user is an owner of group_qs = ( group_qs.filter( pk__in=get_objects_for_user( self.request.user, "core.view_week_class_register_group", Group ).values_list("pk", flat=True) ) - ).union(group_qs.filter(Q(members=person) | Q(owners=person))) + ).union( + group_qs.filter( + Q(members=person) | Q(owners=person) | Q(parent_groups__owners=person) + if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"] + else Q(members=person) | Q(owners=person) + ) + ) # Flatten query by filtering groups by pk self.fields["group"].queryset = Group.objects.filter( @@ -130,9 +138,18 @@ class SelectForm(forms.Form): # Filter selectable teachers by permissions if not check_global_permission(self.request.user, "alsijil.view_week"): - # If the user hasn't the global permission, - # the user is only allowed to see his own person - teacher_qs = teacher_qs.filter(pk=person.pk) + # If the user hasn't got the global permission and the inherit privileges preference is + # turned off, the user is only allowed to see his own person. Otherwise, the user + # is allowed to see all persons that teach lessons that the given groups attend. + if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"]: + teacher_pks = [] + for group in group_qs: + for lesson in group.lessons.all(): + for teacher in lesson.teachers.all(): + teacher_pks.append(teacher.pk) + teacher_qs = teacher_qs.filter(pk__in=teacher_pks) + else: + teacher_qs = teacher_qs.filter(pk=person.pk) self.fields["teacher"].queryset = teacher_qs @@ -261,16 +278,31 @@ class AssignGroupRoleForm(forms.ModelForm): if get_site_preferences()["alsijil__group_owners_can_assign_roles_to_parents"]: persons = persons.filter( Q(member_of__owners=self.request.user.person) + | Q(member_of__parent_groups__owners=self.request.user.person) + | Q(children__member_of__owners=self.request.user.person) + | Q(children__member_of__parent_groups__owners=self.request.user.person) + if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"] + else Q(member_of__owners=self.request.user.person) | Q(children__member_of__owners=self.request.user.person) ) else: - persons = persons.filter(member_of__owners=self.request.user.person) + persons = persons.filter( + Q(member_of__owners=self.request.user.person) + | Q(member_of__parent_groups__owners=self.request.user.person) + if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"] + else Q(member_of__owners=self.request.user.person) + ) self.fields["person"].queryset = persons.distinct() if "groups" not in initial: groups = ( Group.objects.for_current_school_term_or_all() - .filter(owners=self.request.user.person) + .filter( + Q(owners=self.request.user.person) + | Q(parent_groups__owners=self.request.user.person) + if get_site_preferences()["alsijil__inherit_privileges_from_parent_group"] + else Q(owners=self.request.user.person) + ) .distinct() ) self.fields["groups"].queryset = groups diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index c56332115..5e1a29131 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -35,6 +35,17 @@ class RegisterAbsenceAsPrimaryGroupOwner(BooleanPreference): ) +@site_preferences_registry.register +class InheritPrivilegesFromParentGroup(BooleanPreference): + section = alsijil + name = "inherit_privileges_from_parent_group" + default = True + verbose_name = _( + "Grant the owner of a parent group the same privileges " + "as the owners of the respective child groups" + ) + + @site_preferences_registry.register class EditLessonDocumentationAsOriginalTeacher(BooleanPreference): section = alsijil diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index 23686bf17..40856e27e 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -24,7 +24,9 @@ from .util.predicates import ( is_none, is_own_personal_note, is_owner_of_any_group, + is_parent_group_owner, is_person_group_owner, + is_person_parent_group_owner, is_person_primary_group_owner, is_personal_note_lesson_original_teacher, is_personal_note_lesson_parent_group_owner, @@ -52,6 +54,10 @@ view_lesson_personal_notes_predicate = view_register_object_predicate & ( ~is_lesson_participant | is_lesson_teacher | is_lesson_original_teacher + | ( + is_lesson_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_personalnote") | has_lesson_group_object_perm("core.view_personalnote_group") ) @@ -64,6 +70,10 @@ edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & ( is_lesson_original_teacher & is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher") ) + | ( + is_lesson_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.change_personalnote") | has_lesson_group_object_perm("core.edit_personalnote_group") ) @@ -87,6 +97,10 @@ edit_personal_note_predicate = view_personal_note_predicate & ( is_personal_note_lesson_original_teacher | ~is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher") ) + | ( + is_personal_note_lesson_parent_group_owner + | is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_personalnote") | has_personal_note_group_perm("core.edit_personalnote_group") ) @@ -103,6 +117,10 @@ edit_lesson_documentation_predicate = view_register_object_predicate & ( is_lesson_original_teacher & is_site_preference_set("alsijil", "edit_lesson_documentation_as_original_teacher") ) + | ( + is_lesson_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.change_lessondocumentation") | has_lesson_group_object_perm("core.edit_lessondocumentation_group") ) @@ -113,6 +131,10 @@ view_week_predicate = has_person & ( is_current_person | is_group_member | is_group_owner + | ( + is_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_week") | has_object_perm("core.view_week_class_register_group") ) @@ -125,6 +147,10 @@ add_perm("alsijil.view_week_menu_rule", has_person) view_week_personal_notes_predicate = has_person & ( (is_current_person & is_teacher) | is_group_owner + | ( + is_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_personalnote") | has_object_perm("core.view_personalnote_group") ) @@ -145,6 +171,10 @@ add_perm("alsijil.register_absence_rule", register_absence_predicate) # View full register for group view_full_register_predicate = has_person & ( is_group_owner + | ( + is_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_full_register") | has_object_perm("core.view_full_register_group") ) @@ -161,6 +191,10 @@ add_perm("alsijil.view_my_groups_rule", view_my_groups_predicate) # View students list view_students_list_predicate = view_my_groups_predicate & ( is_group_owner + | ( + is_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_personalnote") | has_object_perm("core.view_personalnote_group") ) @@ -170,6 +204,10 @@ add_perm("alsijil.view_students_list_rule", view_students_list_predicate) view_person_overview_predicate = has_person & ( (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) | is_person_group_owner + | ( + is_person_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) ) add_perm("alsijil.view_person_overview_rule", view_person_overview_predicate) @@ -181,6 +219,10 @@ add_perm("alsijil.view_person_overview_menu_rule", view_person_overview_menu_pre view_person_overview_personal_notes_predicate = view_person_overview_predicate & ( (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) | is_person_primary_group_owner + | ( + is_person_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.view_personalnote") | has_person_group_object_perm("core.view_personalnote_group") ) @@ -263,6 +305,10 @@ add_perm("alsijil.delete_grouprole_rule", delete_group_role_predicate) view_assigned_group_roles_predicate = has_person & ( is_group_owner + | ( + is_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) | has_global_perm("alsijil.assign_grouprole") | has_object_perm("core.assign_grouprole") ) @@ -280,7 +326,12 @@ add_perm( ) assign_group_role_person_predicate = has_person & ( - is_person_group_owner | has_global_perm("alsijil.assign_grouprole") + is_person_group_owner + | ( + is_person_parent_group_owner + & is_site_preference_set("alsijil", "inherit_privileges_from_parent_group") + ) + | has_global_perm("alsijil.assign_grouprole") ) add_perm("alsijil.assign_grouprole_to_person_rule", assign_group_role_person_predicate) diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index 640431a90..0274100f0 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -15,6 +15,8 @@ from aleksis.apps.alsijil.forms import FilterRegisterObjectForm from aleksis.apps.alsijil.models import LessonDocumentation from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk +from aleksis.core.models import Group +from aleksis.core.util.core_helpers import get_site_preferences def get_register_object_by_pk( @@ -187,7 +189,19 @@ def _generate_dicts_for_lesson_periods( weeks = CalendarWeek.weeks_within(date_start, date_end) register_objects = [] + inherit_privileges_preference = get_site_preferences()[ + "alsijil__inherit_privileges_from_parent_group" + ] for lesson_period in lesson_periods: + parent_group_owned_by_person = ( + filter_dict.get("person") + .owner_of.intersection( + Group.objects.filter( + child_groups__in=Group.objects.filter(lessons__lesson_periods=lesson_period) + ) + ) + .exists() + ) for week in weeks: day = week[lesson_period.period.weekday] @@ -205,10 +219,14 @@ def _generate_dicts_for_lesson_periods( ): sub = lesson_period.get_substitution() - # Skip lesson period if the person isn't a teacher - # or substitution teacher of this lesson period + # Skip lesson period if the person isn't a teacher, + # substitution teacher or, when the corresponding + # preference is switched on, owner of a parent group + # of this lesson period if filter_dict.get("person") and ( - filter_dict.get("person") not in lesson_period.lesson.teachers.all() and not sub + filter_dict.get("person") not in lesson_period.lesson.teachers.all() + and not sub + and not (inherit_privileges_preference and parent_group_owned_by_person) ): continue diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index 1759a5446..6f8cab752 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -125,6 +125,22 @@ def is_person_primary_group_owner(user: User, obj: Person) -> bool: return False +@predicate +def is_person_parent_group_owner(user: User, obj: Person) -> bool: + """ + Predicate for parent group owners of a person. + + Checks whether the person linked to the user is the owner of + any parent groups of any groups of the given person. + """ + if obj: + for group in use_prefetched(obj, "member_of"): + for parent_group in use_prefetched(group, "parent_groups"): + if user.person in use_prefetched(parent_group, "owners"): + return True + return False + + def has_person_group_object_perm(perm: str): """Predicate builder for permissions on a set of member groups. @@ -206,6 +222,16 @@ def is_own_personal_note(user: User, obj: PersonalNote) -> bool: return False +@predicate +def is_parent_group_owner(user: User, obj: Group) -> bool: + """Predicate which checks whether the user is the owner of any parent group of the group.""" + if hasattr(obj, "parent_groups"): + for parent_group in obj.parent_groups.all(): + if user.person in list(parent_group.owners.all()): + return True + return False + + @predicate def is_personal_note_lesson_teacher(user: User, obj: PersonalNote) -> bool: """Predicate for teachers of a register object linked to a personal note. diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 2d8968207..a4ba938d2 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -350,9 +350,30 @@ def week_view( elif hasattr(request, "user") and hasattr(request.user, "person"): if request.user.person.lessons_as_teacher.exists(): - lesson_periods = lesson_periods.filter_teacher(request.user.person) - events = events.filter_teacher(request.user.person) - extra_lessons = extra_lessons.filter_teacher(request.user.person) + inherit_privileges_preference = get_site_preferences()[ + "alsijil__inherit_privileges_from_parent_group" + ] + lesson_periods = ( + lesson_periods.filter_teacher(request.user.person).union( + lesson_periods.filter_groups(request.user.person.owner_of.all()) + ) + if inherit_privileges_preference + else lesson_periods.filter_teacher(request.user.person) + ) + events = ( + events.filter_teacher(request.user.person).union( + events.filter_groups(request.user.person.owner_of.all()) + ) + if inherit_privileges_preference + else events.filter_teacher(request.user.person) + ) + extra_lessons = ( + extra_lessons.filter_teacher(request.user.person).union( + extra_lessons.filter_groups(request.user.person.owner_of.all()) + ) + if inherit_privileges_preference + else extra_lessons.filter_teacher(request.user.person) + ) type_ = TimetableType.TEACHER else: -- GitLab