From 1618796ee609418180db7f336b478fb6d08caa06 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Fri, 14 May 2021 23:16:08 +0200 Subject: [PATCH] Use custom queries to speed up permission filtering of timetables in overview --- aleksis/apps/chronos/rules.py | 9 +-- aleksis/apps/chronos/util/chronos_helpers.py | 84 ++++++++++++++++++++ aleksis/apps/chronos/util/predicates.py | 8 ++ aleksis/apps/chronos/views.py | 47 +++-------- 4 files changed, 108 insertions(+), 40 deletions(-) diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py index a31b3ca1..aec25bf3 100644 --- a/aleksis/apps/chronos/rules.py +++ b/aleksis/apps/chronos/rules.py @@ -1,6 +1,5 @@ from rules import add_perm -from aleksis.core.models import Group, Person from aleksis.core.util.predicates import ( has_any_object, has_global_perm, @@ -8,14 +7,12 @@ from aleksis.core.util.predicates import ( has_person, ) -from .models import LessonSubstitution, Room -from .util.predicates import has_timetable_perm +from .models import LessonSubstitution +from .util.predicates import has_any_timetable_object, has_timetable_perm # View timetable overview view_timetable_overview_predicate = has_person & ( - has_any_object("chronos.view_timetable", Person) - | has_any_object("chronos.view_timetable", Group) - | has_any_object("chronos.view_timetable", Room) + has_any_timetable_object | has_global_perm("chronos.view_timetable_overview") ) add_perm("chronos.view_timetable_overview", view_timetable_overview_predicate) diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py index 7f929ca8..0f229be4 100644 --- a/aleksis/apps/chronos/util/chronos_helpers.py +++ b/aleksis/apps/chronos/util/chronos_helpers.py @@ -1,9 +1,14 @@ from typing import Optional +from django.contrib.auth.models import User +from django.db.models import Count, Q from django.http import HttpRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404 +from guardian.core import ObjectPermissionChecker + from aleksis.core.models import Group, Person +from aleksis.core.util.predicates import check_global_permission from ..managers import TimetableType from ..models import LessonPeriod, LessonSubstitution, Room @@ -37,3 +42,82 @@ def get_substitution_by_id(request: HttpRequest, id_: int, week: int): return LessonSubstitution.objects.filter( week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period ).first() + + +def get_teachers(user: User): + checker = ObjectPermissionChecker(user) + + teachers = ( + Person.objects.annotate(lessons_count=Count("lessons_as_teacher")) + .filter(lessons_count__gt=0) + .order_by("short_name", "last_name") + ) + + if not check_global_permission(user, "chronos.view_all_person_timetables"): + checker.prefetch_perms(teachers) + + wanted_teachers = set() + + for teacher in teachers: + if checker.has_perm("core.view_person_timetable", teacher): + wanted_teachers.add(teacher.pk) + + teachers = teachers.filter(Q(pk=user.person.pk) | Q(pk__in=wanted_teachers)) + + return teachers + + +def get_classes(user: User): + checker = ObjectPermissionChecker(user) + + classes = ( + Group.objects.for_current_school_term_or_all() + .annotate( + lessons_count=Count("lessons"), child_lessons_count=Count("child_groups__lessons"), + ) + .filter( + Q(lessons_count__gt=0, parent_groups=None) + | Q(child_lessons_count__gt=0, parent_groups=None) + ) + .order_by("short_name", "name") + ) + + if not check_global_permission(user, "chronos.view_all_group_timetables"): + checker.prefetch_perms(classes) + + wanted_classes = set() + + for _class in classes: + if checker.has_perm("core.view_group_timetable", _class): + wanted_classes.add(_class.pk) + + classes = classes.filter( + Q(pk__in=wanted_classes) | Q(members=user.person) | Q(pk=user.person.primary_group.pk) + if user.person.primary_group + else Q() | Q(owners=user.person) + ) + + return classes + + +def get_rooms(user: User): + checker = ObjectPermissionChecker(user) + + rooms = ( + Room.objects.annotate(lessons_count=Count("lesson_periods")) + .filter(lessons_count__gt=0) + .order_by("short_name", "name") + ) + + if not check_global_permission(user, "chronos.view_all_room_timetables"): + checker.prefetch_perms(rooms) + + wanted_rooms = set() + + for room in rooms: + if checker.has_perm("chronos.view_room_timetable", room): + wanted_rooms.add(room.pk) + + rooms = rooms.filter(Q(pk__in=wanted_rooms)) + + return rooms diff --git a/aleksis/apps/chronos/util/predicates.py b/aleksis/apps/chronos/util/predicates.py index e7b3a31d..c07047dd 100644 --- a/aleksis/apps/chronos/util/predicates.py +++ b/aleksis/apps/chronos/util/predicates.py @@ -7,6 +7,8 @@ from aleksis.apps.chronos.models import Room from aleksis.core.models import Group, Person from aleksis.core.util.predicates import has_global_perm, has_object_perm +from .chronos_helpers import get_classes, get_rooms, get_teachers + @predicate def has_timetable_perm(user: User, obj: Model) -> bool: @@ -31,3 +33,9 @@ def has_timetable_perm(user: User, obj: Model) -> bool: )(user, obj) else: return False + + +@predicate +def has_any_timetable_object(user: User) -> bool: + """Predicate which checks whether there exists a timetable that the user is allowed to access.""" + return get_classes(user).exists() or get_rooms(user).exists() or get_teachers(user).exists() diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py index 31b0a8f6..42841d13 100644 --- a/aleksis/apps/chronos/views.py +++ b/aleksis/apps/chronos/views.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from typing import Optional -from django.db.models import Count, Q +from django.db.models import Q from django.http import HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse @@ -12,16 +12,22 @@ from django.views.decorators.cache import never_cache from django_tables2 import RequestConfig from rules.contrib.views import permission_required -from aleksis.core.models import Announcement, Group, Person +from aleksis.core.models import Announcement, Group from aleksis.core.util import messages -from aleksis.core.util.core_helpers import get_site_preferences, has_person, queryset_rules_filter +from aleksis.core.util.core_helpers import get_site_preferences, has_person from .forms import LessonSubstitutionForm from .managers import TimetableType -from .models import Absence, Holiday, LessonPeriod, LessonSubstitution, Room, TimePeriod +from .models import Absence, Holiday, LessonPeriod, LessonSubstitution, TimePeriod from .tables import LessonsTable from .util.build import build_substitutions_list, build_timetable, build_weekdays -from .util.chronos_helpers import get_el_by_pk, get_substitution_by_id +from .util.chronos_helpers import ( + get_classes, + get_el_by_pk, + get_rooms, + get_substitution_by_id, + get_teachers, +) from .util.date import CalendarWeek, get_weeks_for_year from .util.js import date_unix @@ -31,35 +37,8 @@ def all_timetables(request: HttpRequest) -> HttpResponse: """View all timetables for persons, groups and rooms.""" context = {} - teachers = queryset_rules_filter( - request, - ( - Person.objects.annotate(lessons_count=Count("lessons_as_teacher")) - .filter(lessons_count__gt=0) - .order_by("short_name", "last_name") - ), - "chronos.view_timetable", - ) - groups = Group.objects.for_current_school_term_or_all().annotate( - lessons_count=Count("lessons"), child_lessons_count=Count("child_groups__lessons"), - ) - classes = queryset_rules_filter( - request, - groups.filter(lessons_count__gt=0, parent_groups=None) - | groups.filter(child_lessons_count__gt=0, parent_groups=None).order_by( - "short_name", "name" - ), - "chronos.view_timetable", - ) - rooms = queryset_rules_filter( - request, - ( - Room.objects.annotate(lessons_count=Count("lesson_periods")) - .filter(lessons_count__gt=0) - .order_by("short_name", "name") - ), - "chronos.view_timetable", - ) + user = request.user + teachers, classes, rooms = get_teachers(user), get_classes(user), get_rooms(user) context["teachers"] = teachers context["classes"] = classes -- GitLab