From 6e33863546708df8c756396cfbd00bdbf3fb6ea4 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sun, 14 Jun 2020 12:59:39 +0200 Subject: [PATCH] Add rules and permissions for some views --- aleksis/apps/alsijil/models.py | 9 +++ aleksis/apps/alsijil/rules.py | 46 ++++++++++++ aleksis/apps/alsijil/util/alsijil_helpers.py | 73 ++++++++++++++++++++ aleksis/apps/alsijil/util/predicates.py | 33 +++++++++ aleksis/apps/alsijil/views.py | 62 +++++------------ 5 files changed, 179 insertions(+), 44 deletions(-) create mode 100644 aleksis/apps/alsijil/rules.py create mode 100644 aleksis/apps/alsijil/util/alsijil_helpers.py create mode 100644 aleksis/apps/alsijil/util/predicates.py diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 3867f6c5a..1a32039e1 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -81,3 +81,12 @@ class PersonalNoteFilter(ExtensibleModel): verbose_name = _("Personal note filter") verbose_name_plural = _("Personal note filters") ordering = ["identifier"] + +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/rules.py b/aleksis/apps/alsijil/rules.py new file mode 100644 index 000000000..c6c6baf84 --- /dev/null +++ b/aleksis/apps/alsijil/rules.py @@ -0,0 +1,46 @@ +from rules import add_perm + +from aleksis.core.util.predicates import ( + has_any_object, + has_global_perm, + has_object_perm, + has_person, +) + +from .util.predicates import has_lesson_perm, has_week_perm + +# View lesson +view_lesson_predicate = has_person & ( + has_global_perm("chronos.view_lesson_period") | has_lesson_perm("chronos.view_lesson_period") +) +add_perm("alsijil.view_lesson", view_lesson_predicate) + +# View week overview +view_week_predicate = has_person & ( + has_global_perm("alsijil.view_week") | has_week_perm("alsijil") +) +add_perm("alsijil.view_week", view_week_predicate) + +# Register absence +register_absence_predicate = has_person & ( + has_global_perm("alsijil.register_absence") +) +add_perm("alsijil.register_absence", register_absence_predicate) + +# List all personal note filters +list_personal_note_filters_predicate = has_person & has_global_perm("alsijil.list_personal_note_filters") +add_perm("alsijil.list_personal_note_filters", list_personal_note_filters_predicate) + +# Edit personal note filter +edit_personal_note_filter_predicate = has_person & ( + has_global_perm("alsijil.change_personal_note_filter") + | has_object_perm("alsijil.change_personal_note_filter") +) +add_perm("alsijil.edit_personal_note_filter", edit_personal_note_filter_predicate) + +# Delete personal note filter +delete_personal_note_filter_predicate = has_person & ( + has_global_perm("alsijil.delete_personal_note_filter") + | has_object_perm("alsijil.delete_personal_note_filter") +) +add_perm("alsijil.delete_personal_note_filter", delete_personal_note_filter_predicate) diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py new file mode 100644 index 000000000..ec27bcb8b --- /dev/null +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -0,0 +1,73 @@ +from typing import Optional + +from django.db.models import Count, Exists, OuterRef, Q, Sum +from django.http import HttpRequest, HttpResponseNotFound +from django.shortcuts import get_object_or_404 + +from calendarweek import CalendarWeek + +from aleksis.apps.chronos.managers import TimetableType +from aleksis.apps.chronos.models import LessonPeriod +from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk +from ..models import LessonDocumentation + + +def get_lesson_period_by_pk( + request: HttpRequest, + year: Optional[int] = None, + week: Optional[int] = None, + period_id: Optional[int] = None, +): + if period_id: + lesson_period = LessonPeriod.objects.get(pk=period_id) + wanted_week = CalendarWeek(year=year, week=week) + 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() + wanted_week = CalendarWeek() + else: + lesson_period = wanted_week = None + return lesson_period, wanted_week + + +def get_lesson_periods_by_pk( + request: HttpRequest, + year: Optional[int] = None, + week: Optional[int] = None, + type_: Optional[str] = None, + id_: Optional[int] = None, +): + if year and week: + wanted_week = CalendarWeek(year=year, week=week) + else: + wanted_week = CalendarWeek() + + lesson_periods = LessonPeriod.objects.annotate( + has_documentation=Exists( + LessonDocumentation.objects.filter( + ~Q(topic__exact=""), lesson_period=OuterRef("pk"), week=wanted_week.week + ) + ) + ).in_week(wanted_week) + + if type_ and id_: + instance = get_el_by_pk(request, type_, id_) + + if isinstance(instance, HttpResponseNotFound): + return HttpResponseNotFound() + + type_ = TimetableType.from_string(type_) + + 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 + else: + lesson_periods = lesson_periods.filter_participant(request.user.person) + else: + lesson_periods = None + return lesson_periods, wanted_week, type_, instance \ No newline at end of file diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py new file mode 100644 index 000000000..94c69e0fe --- /dev/null +++ b/aleksis/apps/alsijil/util/predicates.py @@ -0,0 +1,33 @@ +from django.contrib.auth.models import User + +from rules import predicate + +from aleksis.core.models import Group, Person +from aleksis.core.util.predicates import check_object_permission + + +def has_lesson_perm(perm: str): + """Build predicate which checks whether the user is allowed to access the requested lesson notes.""" + name = f"has_lesson_perm:{perm}" + + @predicate(name) + def fn(user: User, obj: tuple) -> bool: + if (user.person in obj[0].lesson.teachers) or (set(user.person.member_of).intersection(set(obj[0].lesson.groups))): + return True + return check_object_permission(user, perm, obj) + + return fn + + +@predicate +def has_week_perm(perm: str): + """Build predicate which checks whether the user is allowed to access the week overview.""" + name = f"has_week_perm:{perm}" + + @predicate(name) + def fn(user: User, obj: tuple) -> bool: + if (user.person in obj[0].lesson.teachers) or (set(user.person.member_of).intersection(set(obj[0].lesson.groups))): + return True + return check_object_permission(user, perm, obj) + + return fn diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 7b594f25b..da8d97642 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -12,11 +12,12 @@ from django.utils.translation import ugettext as _ from calendarweek import CalendarWeek from django_tables2 import RequestConfig +from rules.contrib.views import permission_required from aleksis.apps.chronos.models import LessonPeriod from aleksis.core.models import Group, Person, SchoolYear from aleksis.core.util import messages - +from aleksis.core.util.core_helpers import objectgetter_optional from .forms import ( LessonDocumentationForm, PersonalNoteFilterForm, @@ -26,8 +27,10 @@ from .forms import ( ) from .models import LessonDocumentation, PersonalNoteFilter from .tables import PersonalNoteFilterTable +from .util.alsijil_helpers import get_lesson_period_by_pk, get_lesson_periods_by_pk +@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk) def lesson( request: HttpRequest, year: Optional[int] = None, @@ -36,15 +39,9 @@ def lesson( ) -> HttpResponse: context = {} - if year and week and period_id: - # Get a specific lesson period if provided in URL - lesson_period = LessonPeriod.objects.get(pk=period_id) - wanted_week = CalendarWeek(year=year, week=week) - else: - # Determine current lesson by current date and time - lesson_period = LessonPeriod.objects.at_time().filter_teacher(request.user.person).first() - wanted_week = CalendarWeek() + lesson_period, wanted_week = get_lesson_period_by_pk(request, year, week, period_id) + if not (year and week and period_id): if lesson_period: return redirect( "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk, @@ -108,46 +105,13 @@ def lesson( return render(request, "alsijil/lesson.html", context) +@permission_required("alsijil.view_week", fn=get_lesson_periods_by_pk) def week_view( request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None, type_: Optional[str] = None, id_: Optional[int] = None ) -> HttpResponse: context = {} - if year and week: - wanted_week = CalendarWeek(year=year, week=week) - else: - wanted_week = CalendarWeek() - - lesson_periods = LessonPeriod.objects.annotate( - has_documentation=Exists( - LessonDocumentation.objects.filter( - ~Q(topic__exact=""), lesson_period=OuterRef("pk"), week=wanted_week.week - ) - ) - ).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 - else: - lesson_periods = lesson_periods.filter_participant(request.user.person) - else: - lesson_periods = None + lesson_periods, wanted_week, type_, instance = get_lesson_periods_by_pk(request, year, week, type_, id_) # Add a form to filter the view if type_: @@ -164,6 +128,11 @@ def week_view( return redirect("week_view_by_week", wanted_week.year, wanted_week.week, select_form.cleaned_data["type_"].value, 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 = lesson_periods.values_list("pk", flat=True) @@ -226,6 +195,7 @@ def week_view( return render(request, "alsijil/week_view.html", context) +@permission_required("alsijil.full_register_group", fn=objectgetter_optional(Group, None, False)) def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: context = {} @@ -293,6 +263,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: return render(request, "alsijil/print/full_register.html", context) +@permission_required("alsijil.register_absence") def register_absence(request: HttpRequest) -> HttpResponse: context = {} @@ -324,6 +295,7 @@ def register_absence(request: HttpRequest) -> HttpResponse: return render(request, "alsijil/register_absence.html", context) +@permission_required("alsijil.list_personal_note_filters") def list_personal_note_filters(request: HttpRequest) -> HttpResponse: context = {} @@ -338,6 +310,7 @@ def list_personal_note_filters(request: HttpRequest) -> HttpResponse: return render(request, "alsijil/personal_note_filters.html", context) +@permission_required("alsijil.edit_personal_note_filter", fn=objectgetter_optional(PersonalNoteFilter, None, False)) def edit_personal_note_filter(request: HttpRequest, id: Optional["int"] = None) -> HttpResponse: context = {} @@ -362,6 +335,7 @@ def edit_personal_note_filter(request: HttpRequest, id: Optional["int"] = None) return render(request, "alsijil/manage_personal_note_filter.html", context) +@permission_required("alsijil.delete_personal_note_filter", fn=objectgetter_optional(PersonalNoteFilter, None, False)) def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse: context = {} -- GitLab