diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 33f43b5153e7ac36a93093ecbadb2da1def157c6..3152b47e395bc7d60f622d9e02b04f21f71552af 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -88,7 +88,10 @@ class SelectForm(forms.Form): group_qs = Group.get_groups_with_lessons() + # Filter selectable groups by permissions if not check_global_permission(self.request.user, "alsijil.view_week"): + # 1) All groups the user is allowed to see the week view by object permissions + # 2) All groups the user is a member of an owner of group_qs = ( group_qs.filter( pk__in=get_objects_for_user( @@ -96,6 +99,8 @@ class SelectForm(forms.Form): ).values_list("pk", flat=True) ) ).union(group_qs.filter(Q(members=person) | Q(owners=person))) + + # Flatten query by filtering groups by pk self.fields["group"].queryset = Group.objects.filter( pk__in=list(group_qs.values_list("pk", flat=True)) ) @@ -103,7 +108,10 @@ class SelectForm(forms.Form): teacher_qs = Person.objects.annotate( lessons_count=Count("lessons_as_teacher") ).filter(lessons_count__gt=0) + + # Filter selectable teachers by permissions if not check_global_permission(self.request.user, "alsijil.view_week"): + # If the user hasn't the global permission, the user is only allowed to see his own person teacher_qs = teacher_qs.filter(pk=person.pk) self.fields["teacher"].queryset = teacher_qs @@ -138,12 +146,18 @@ class RegisterAbsenceForm(forms.Form): remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False) def __init__(self, *args, **kwargs): - self.request = kwargs.pop("request") + self.request = get_request() super().__init__(*args, **kwargs) period_choices = TimePeriod.period_choices + + # Filter selectable persons by permissions if check_global_permission(self.request.user, "alsijil.register_absence"): + # Global permission, user can register absences for all persons self.fields["person"].queryset = Person.objects.all() else: + # 1) All persons the user is allowed to register an absence for by object permissions + # 2) All persons the user is the primary group owner + # 3) All persons the user is allowed to register an absence for by object permissions of the person's group persons_qs = ( get_objects_for_user( self.request.user, "core.register_absence_person", Person @@ -161,6 +175,8 @@ class RegisterAbsenceForm(forms.Form): ) ) ) + + # Flatten query by getting all pks and filter persons self.fields["person"].queryset = Person.objects.filter( pk__in=list(persons_qs.values_list("pk", flat=True)) ) diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index 9958d72d831b8978178301c8fa7123d0222e47e4..d5552c5e03a37d530d1355c99ef56706616e5380 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -24,6 +24,16 @@ class ViewOwnPersonalNotes(BooleanPreference): verbose_name = _("Allow users to view their own personal notes") +@site_preferences_registry.register +class RegisterAbsenceAsPrimaryGroupOwner(BooleanPreference): + section = alsijil + name = "register_absence_as_primary_group_owner" + default = True + verbose_name = _( + "Allow primary group owners to register future absences for students in their groups" + ) + + @site_preferences_registry.register class CarryOverDataToNextPeriods(BooleanPreference): section = alsijil diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index c2db683987b29f7321ae6bd8f7788923f67d4e62..cb2bda3333471b171a0e76010c574fb22843fdbc 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -5,6 +5,7 @@ from aleksis.core.util.predicates import ( has_object_perm, has_person, is_current_person, + is_site_preference_set, ) from .util.predicates import ( @@ -17,6 +18,7 @@ from .util.predicates import ( is_lesson_parent_group_owner, is_lesson_participant, is_lesson_teacher, + is_none, is_own_personal_note, is_person_group_owner, is_person_primary_group_owner, @@ -27,7 +29,8 @@ from .util.predicates import ( # View lesson view_lesson_predicate = has_person & ( - is_lesson_teacher + is_none # View is opened as "Current lesson" + | is_lesson_teacher | is_lesson_participant | is_lesson_parent_group_owner | has_global_perm("alsijil.view_lesson") @@ -39,26 +42,27 @@ add_perm("alsijil.view_lesson", view_lesson_predicate) add_perm("alsijil.view_lesson_menu", has_person) # View lesson personal notes -view_lesson_personal_notes_predicate = has_person & ( - is_lesson_teacher - | is_lesson_parent_group_owner +view_lesson_personal_notes_predicate = view_lesson_predicate & ( + ~is_lesson_participant | has_global_perm("alsijil.view_personalnote") | has_lesson_group_object_perm("core.view_personalnote_group") ) add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate) # Edit personal note -edit_lesson_personal_note_predicate = has_person & ( - is_lesson_teacher +edit_lesson_personal_note_predicate = view_lesson_personal_notes_predicate & ( + ~is_lesson_parent_group_owner | has_global_perm("alsijil.change_personalnote") | has_lesson_group_object_perm("core.edit_personalnote_group") ) add_perm("alsijil.edit_lesson_personalnote", edit_lesson_personal_note_predicate) - # View personal note view_personal_note_predicate = has_person & ( - is_own_personal_note + ( + is_own_personal_note + & is_site_preference_set("alsijil", "view_own_personal_notes") + ) | is_personal_note_lesson_teacher | is_personal_note_lesson_parent_group_owner | has_global_perm("alsijil.view_personalnote") @@ -67,26 +71,19 @@ view_personal_note_predicate = has_person & ( add_perm("alsijil.view_personalnote", view_personal_note_predicate) # Edit personal note -edit_personal_note_predicate = has_person & ( - is_personal_note_lesson_teacher - | is_personal_note_lesson_parent_group_owner +edit_personal_note_predicate = view_personal_note_predicate & ( + ~is_own_personal_note | has_global_perm("alsijil.view_personalnote") | has_personal_note_group_perm("core.edit_personalnote_group") ) add_perm("alsijil.edit_personalnote", edit_personal_note_predicate) # View lesson documentation -view_lesson_documentation_predicate = has_person & ( - is_lesson_teacher - | is_lesson_participant - | is_lesson_parent_group_owner - | has_global_perm("alsijil.view_lessondocumentation") - | has_lesson_group_object_perm("core.view_lessondocumentation_group") -) +view_lesson_documentation_predicate = view_lesson_predicate add_perm("alsijil.view_lessondocumentation", view_lesson_documentation_predicate) # Edit lesson documentation -edit_lesson_documentation_predicate = has_person & ( +edit_lesson_documentation_predicate = view_lesson_predicate & ( is_lesson_teacher | has_global_perm("alsijil.change_lessondocumentation") | has_lesson_group_object_perm("core.edit_lessondocumentation_group") @@ -123,7 +120,10 @@ add_perm("alsijil.view_register_absence", view_register_absence_predicate) # Register absence register_absence_predicate = has_person & ( - is_person_primary_group_owner + ( + is_person_primary_group_owner + & is_site_preference_set("alsijil", "register_absence_as_primary_group_owner") + ) | has_global_perm("alsijil.register_absence") | has_object_perm("core.register_absence_person") | has_person_group_object_perm("core.register_absence_group") @@ -148,7 +148,8 @@ add_perm("alsijil.view_my_groups", view_my_groups_predicate) # View person overview view_person_overview_predicate = has_person & ( - is_current_person | is_person_group_owner + (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) + | is_person_group_owner ) add_perm("alsijil.view_person_overview", view_person_overview_predicate) @@ -157,8 +158,8 @@ 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 & ( - is_current_person +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 | has_global_perm("alsijil.view_personalnote") | has_person_group_object_perm("core.view_personalnote_group") @@ -169,10 +170,13 @@ add_perm( ) # Edit person overview personal notes -edit_person_overview_personal_notes_predicate = has_person & ( - is_person_primary_group_owner - | has_global_perm("alsijil.edit_personalnote") - | has_person_group_object_perm("core.edit_personalnote_group") +edit_person_overview_personal_notes_predicate = ( + view_person_overview_personal_notes_predicate + & ( + ~is_current_person + | has_global_perm("alsijil.edit_personalnote") + | has_person_group_object_perm("core.edit_personalnote_group") + ) ) add_perm( "alsijil.edit_person_overview_personalnote", @@ -180,11 +184,8 @@ add_perm( ) # View person statistics on personal notes -view_person_statistics_personal_notes_predicate = has_person & ( - is_current_person - | is_person_primary_group_owner - | has_global_perm("alsijil.view_personalnote") - | has_person_group_object_perm("core.view_personalnote_group") +view_person_statistics_personal_notes_predicate = ( + view_person_overview_personal_notes_predicate ) add_perm( "alsijil.view_person_statistics_personalnote", @@ -196,15 +197,21 @@ view_excusetypes_predicate = has_person & has_global_perm("alsijil.view_excusety add_perm("alsijil.view_excusetypes", view_excusetypes_predicate) # Add excuse type -add_excusetype_predicate = has_person & has_global_perm("alsijil.add_excusetype") +add_excusetype_predicate = view_excusetypes_predicate & has_global_perm( + "alsijil.add_excusetype" +) add_perm("alsijil.add_excusetype", add_excusetype_predicate) # Edit excuse type -edit_excusetype_predicate = has_person & has_global_perm("alsijil.change_excusetype") +edit_excusetype_predicate = view_excusetypes_predicate & has_global_perm( + "alsijil.change_excusetype" +) add_perm("alsijil.edit_excusetype", edit_excusetype_predicate) # Delete excuse type -delete_excusetype_predicate = has_person & has_global_perm("alsijil.delete_excusetype") +delete_excusetype_predicate = view_excusetypes_predicate & has_global_perm( + "alsijil.delete_excusetype" +) add_perm("alsijil.delete_excusetype", delete_excusetype_predicate) # View extra mark list @@ -212,13 +219,19 @@ 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_extramark_predicate = view_extramarks_predicate & has_global_perm( + "alsijil.add_extramark" +) add_perm("alsijil.add_extramark", add_extramark_predicate) # Edit extra mark -edit_extramark_predicate = has_person & has_global_perm("alsijil.change_extramark") +edit_extramark_predicate = view_extramarks_predicate & has_global_perm( + "alsijil.change_extramark" +) add_perm("alsijil.edit_extramark", edit_extramark_predicate) # Delete extra mark -delete_extramark_predicate = has_person & has_global_perm("alsijil.delete_extramark") +delete_extramark_predicate = view_extramarks_predicate & has_global_perm( + "alsijil.delete_extramark" +) add_perm("alsijil.delete_extramark", delete_extramark_predicate) diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index 51279dc62615a8d5432a3b654095d8d10c895eaa..10bf5b4e7e4c67b2f998209849e7fa60f5659945 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -38,14 +38,14 @@ def get_lesson_period_by_pk( return lesson_period -def get_instance_by_pk( +def get_timetable_instance_by_pk( request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None, type_: Optional[str] = None, id_: Optional[int] = None, ): - """Get Instance object by given type and id or the current person.""" + """Get timetable object (teacher, room or group) by given type and id or the current person.""" if type_ and id_: return get_el_by_pk(request, type_, id_) elif hasattr(request, "user") and hasattr(request.user, "person"): diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index a018dbbb762b7d9a150058ec8729fbc777f3f9d6..620f7ef761235083a6fcacf9498fd118bc8c7cef 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Any, Union from django.contrib.auth.models import Permission, User @@ -12,10 +12,17 @@ from aleksis.core.util.core_helpers import ( get_content_type_by_perm, get_site_preferences, ) +from aleksis.core.util.predicates import check_object_permission from ..models import PersonalNote +@predicate +def is_none(user: User, obj: Any) -> bool: + """Predicate that checks if the provided object is None-like.""" + return bool(obj) + + @predicate def is_lesson_teacher(user: User, obj: LessonPeriod) -> bool: """Predicate for teachers of a lesson. @@ -28,7 +35,7 @@ def is_lesson_teacher(user: User, obj: LessonPeriod) -> bool: if sub and sub in user.person.lesson_substitutions.all(): return True return user.person in obj.lesson.teachers.all() - return True + return False @predicate @@ -42,8 +49,7 @@ def is_lesson_participant(user: User, obj: LessonPeriod) -> bool: for group in obj.lesson.groups.all(): if user.person in list(group.members.all()): return True - return False - return True + return False @predicate @@ -59,8 +65,7 @@ def is_lesson_parent_group_owner(user: User, obj: LessonPeriod) -> bool: for parent_group in group.parent_groups.all(): if user.person in list(parent_group.owners.all()): return True - return False - return True + return False @predicate @@ -201,15 +206,10 @@ 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 + if hasattr(obj, "person") and obj.person is user.person: + return True + return False @predicate diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 4f34328162c1cfc51f21bebb2ffe91b39c2fd9db..d72c5856334a31b78c2cfca6630fef2416f13027 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -34,7 +34,7 @@ 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 +from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk @permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk) @@ -55,7 +55,7 @@ def lesson( else: wanted_week = None - if not (year and week and period_id): + if not all((year, week, period_id)): if lesson_period: return redirect( "lesson_by_week_and_period", @@ -109,9 +109,11 @@ def lesson( ) # Create a formset that holds all personal notes for all persons in this lesson - 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 = Person.objects.filter(pk=request.user.person.pk) + else: + persons = Person.objects.all() + persons_qs = lesson_period.get_personal_notes(persons, wanted_week) personal_note_formset = PersonalNoteFormSet( request.POST or None, queryset=persons_qs, prefix="personal_notes" @@ -162,7 +164,7 @@ def lesson( return render(request, "alsijil/class_register/lesson.html", context) -@permission_required("alsijil.view_week", fn=get_instance_by_pk) +@permission_required("alsijil.view_week", fn=get_timetable_instance_by_pk) def week_view( request: HttpRequest, year: Optional[int] = None, @@ -177,7 +179,7 @@ def week_view( else: wanted_week = CalendarWeek() - instance = get_instance_by_pk(request, year, week, type_, id_) + instance = get_timetable_instance_by_pk(request, year, week, type_, id_) lesson_periods = LessonPeriod.objects.in_week(wanted_week).prefetch_related( "lesson__groups__members", @@ -282,7 +284,7 @@ def week_view( lesson_period__in=lesson_periods_pk, ), ), - "member_of__owners" + "member_of__owners", ) .annotate( absences_count=Count( @@ -625,20 +627,19 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp person.refresh_from_db() - allowed_personal_notes = person.personal_notes.all().prefetch_related( + person_personal_notes = person.personal_notes.all().prefetch_related( "lesson_period__lesson__groups", "lesson_period__lesson__teachers", "lesson_period__substitutions", ) - if not request.user.has_perm("alsijil.view_person_overview_personalnote", person): - print("has") - allowed_personal_notes = allowed_personal_notes.filter( + if request.user.has_perm("alsijil.view_person_overview_personalnote", person): + allowed_personal_notes = person_personal_notes.all() + else: + allowed_personal_notes = person_personal_notes.filter( lesson_period__lesson__groups__owners=request.user.person ) - print(allowed_personal_notes) - unexcused_absences = allowed_personal_notes.filter(absent=True, excused=False) context["unexcused_absences"] = unexcused_absences @@ -711,7 +712,7 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp def register_absence(request: HttpRequest) -> HttpResponse: context = {} - register_absence_form = RegisterAbsenceForm(request.POST or None, request=request) + register_absence_form = RegisterAbsenceForm(request.POST or None) if request.method == "POST": if register_absence_form.is_valid() and request.user.has_perm(