diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py index d52c750d5575910fa50299628ab6735ddb7d142b..f02daf466b083e10b7af26dc77df8a0f3042b33c 100644 --- a/aleksis/apps/chronos/managers.py +++ b/aleksis/apps/chronos/managers.py @@ -25,65 +25,92 @@ class TimetableType(Enum): class LessonEventQuerySet(RecurrencePolymorphicQuerySet): """Queryset with special query methods for lesson events.""" - def for_teacher(self, teacher: Union[int, Person]) -> "LessonEventQuerySet": + @staticmethod + def for_teacher_q(teacher: Union[int, Person]) -> Q: """Get all lesson events for a certain person as teacher (including amends).""" from .models import LessonEvent amended = LessonEvent.objects.filter( amended_by__isnull=False, teachers=teacher ).values_list("amended_by__pk", flat=True) - return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct() + return Q(teachers=teacher) | Q(pk__in=amended) - def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet": + def for_teacher(self, teacher: Union[int, Person]) -> "LessonEventQuerySet": + """Get all lesson events for a certain person as teacher (including amends).""" + return self.filter(self.for_teacher_q(teacher)).distinct() + + @staticmethod + def for_participant_q(person: Union[int, Person]) -> Q: """Get all lesson events the person participates in (including amends).""" from .models import LessonEvent amended = LessonEvent.objects.filter( amended_by__isnull=False, groups__members=person ).values_list("amended_by__pk", flat=True) - return self.filter(Q(groups__members=person) | Q(pk__in=amended)).distinct() + return Q(groups__members=person) | Q(pk__in=amended) - def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet": + def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet": + """Get all lesson events the person participates in (including amends).""" + return self.filter(self.for_participant_q(person)).distinct() + + @staticmethod + def for_group_q(group: Union[int, Group]) -> Q: """Get all lesson events for a certain group (including amends/as parent group).""" from .models import LessonEvent amended = LessonEvent.objects.filter( Q(amended_by__isnull=False) & (Q(groups=group) | Q(groups__parent_groups=group)) ).values_list("amended_by__pk", flat=True) - return self.filter( - Q(groups=group) | Q(groups__parent_groups=group) | Q(pk__in=amended) - ).distinct() + return Q(groups=group) | Q(groups__parent_groups=group) | Q(pk__in=amended) - def for_room(self, room: Union[int, Room]) -> "LessonEventQuerySet": + def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet": + """Get all lesson events for a certain group (including amends/as parent group).""" + return self.filter(self.for_group_q(group)).distinct() + + @staticmethod + def for_room_q(room: Union[int, Room]) -> Q: """Get all lesson events for a certain room (including amends).""" from .models import LessonEvent amended = LessonEvent.objects.filter( Q(amended_by__isnull=False) & (Q(rooms=room)) ).values_list("amended_by__pk", flat=True) - return self.filter(Q(rooms=room) | Q(pk__in=amended)).distinct() + return Q(rooms=room) | Q(pk__in=amended) - def for_course(self, course: Union[int, Course]) -> "LessonEventQuerySet": + def for_room(self, room: Union[int, Room]) -> "LessonEventQuerySet": + """Get all lesson events for a certain room (including amends).""" + return self.filter(self.for_room_q(room)).distinct() + + @staticmethod + def for_course_q(course: Union[int, Course]) -> Q: """Get all lesson events for a certain course (including amends).""" from .models import LessonEvent amended = LessonEvent.objects.filter(amended_by__isnull=False, course=course).values_list( "amended_by__pk", flat=True ) - return self.filter(Q(course=course) | Q(pk__in=amended)).distinct() + return Q(course=course) | Q(pk__in=amended) - def for_person(self, person: Union[int, Person]) -> "LessonEventQuerySet": + def for_course(self, course: Union[int, Course]) -> "LessonEventQuerySet": + """Get all lesson events for a certain course (including amends).""" + return self.filter(self.for_course_q(course)).distinct() + + @staticmethod + def for_person_q(person: Union[int, Person]) -> Q: """Get all lesson events for a certain person (as teacher/participant, including amends).""" from .models import LessonEvent amended = LessonEvent.objects.filter( Q(amended_by__isnull=False) & (Q(teachers=person) | Q(groups__members=person)) ).values_list("amended_by__pk", flat=True) - return self.filter( - Q(teachers=person) | Q(groups__members=person) | Q(pk__in=amended) - ).distinct() + return Q(teachers=person) | Q(groups__members=person) | Q(pk__in=amended) - def related_to_person(self, person: Union[int, Person]) -> "LessonEventQuerySet": + def for_person(self, person: Union[int, Person]) -> "LessonEventQuerySet": + """Get all lesson events for a certain person (as teacher/participant, including amends).""" + return self.filter(self.for_person_q(person)).distinct() + + @staticmethod + def related_to_person_q(person: Union[int, Person]) -> Q: """Get all lesson events a certain person is allowed to see. This includes all lesson events the person is assigned to as @@ -101,25 +128,49 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet): | Q(groups__parent_groups__owners=person) ) ).values_list("amended_by__pk", flat=True) - return self.filter( + return ( Q(teachers=person) | Q(groups__members=person) | Q(groups__owners=person) | Q(groups__parent_groups__owners=person) | Q(pk__in=amended) - ).distinct() + ) + + def related_to_person(self, person: Union[int, Person]) -> "LessonEventQuerySet": + """Get all lesson events a certain person is allowed to see. + + This includes all lesson events the person is assigned to as + teacher/participant/group owner/parent group owner, + including those amended. + """ + return self.filter(self.related_to_person_q(person)).distinct() + + @staticmethod + def not_amended_q() -> Q: + """Get all lesson events that are not amended.""" + return Q(amended_by__isnull=True) def not_amended(self) -> "LessonEventQuerySet": """Get all lesson events that are not amended.""" - return self.filter(amended_by__isnull=True) + return self.filter(self.not_amended_q()) + + @staticmethod + def not_amending_q() -> Q: + """Get all lesson events that are not amending other events.""" + return Q(amends__isnull=True) def not_amending(self) -> "LessonEventQuerySet": """Get all lesson events that are not amending other events.""" - return self.filter(amends__isnull=True) + return self.filter(self.not_amending_q()) + + @staticmethod + def amending_q() -> Q: + """Get all lesson events that are amending other events.""" + return Q(amends__isnull=False) def amending(self) -> "LessonEventQuerySet": """Get all lesson events that are amending other events.""" - return self.filter(amends__isnull=False) + return self.filter(self.amending_q()) class SupervisionEventQuerySet(LessonEventQuerySet): diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py index d2ca3cdd4c77e26f716c3a51af883fe1eb16cc75..43b6c9b57924a4c9f9c5f4001ab34fa31e5791aa 100644 --- a/aleksis/apps/chronos/models.py +++ b/aleksis/apps/chronos/models.py @@ -7,10 +7,9 @@ from datetime import date from typing import Any from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import PermissionDenied from django.core.validators import MinValueValidator from django.db import models -from django.db.models import QuerySet +from django.db.models import Q, QuerySet from django.dispatch import receiver from django.http import HttpRequest from django.template.loader import render_to_string @@ -411,19 +410,18 @@ class LessonEvent(CalendarEvent): """Return all objects that should be included in the calendar.""" if no_effect: return super().get_objects(request, params, **kwargs) - objs = ( - super() - .get_objects(request, params, **kwargs) - .not_instance_of(SupervisionEvent) - .select_related("subject", "course") - .prefetch_related("groups", "teachers", "rooms", "groups__members") - ) if request and not has_person(request.user): - raise PermissionDenied() + return [] + + q = Q() if params: - obj_id = int(params.get("id", 0)) + try: + obj_id = int(params.get("id", 0)) + except ValueError: + obj_id = None + type_ = params.get("type", None) not_amended = params.get("not_amended", False) not_amending = params.get("not_amending", False) @@ -431,36 +429,43 @@ class LessonEvent(CalendarEvent): own = params.get("own", False) if not_amended: - objs = objs.not_amended() + q = q & LessonEventQuerySet.not_amended_q() if not_amending: - objs = objs.not_amending() + q = q & LessonEventQuerySet.not_amending_q() if amending: - objs = objs.amending() + q = q & LessonEventQuerySet.amending_q() if request and "own" in params: if own: - objs = objs.for_person(request.user.person) + q = q & LessonEventQuerySet.for_person_q(request.user.person) else: - objs = objs.related_to_person(request.user.person) + q = q & LessonEventQuerySet.related_to_person_q(request.user.person) if type_ and obj_id: if type_ == "TEACHER": - return objs.for_teacher(obj_id) + q = q & LessonEventQuerySet.for_teacher_q(obj_id) elif type_ == "PARTICIPANT": - return objs.for_participant(obj_id) + q = q & LessonEventQuerySet.for_participant_q(obj_id) elif type_ == "GROUP": - return objs.for_group(obj_id) + q = q & LessonEventQuerySet.for_group_q(obj_id) elif type_ == "ROOM": - return objs.for_room(obj_id) + q = q & LessonEventQuerySet.for_room_q(obj_id) elif type_ == "COURSE": - return objs.for_course(obj_id) + q = q & LessonEventQuerySet.for_course_q(obj_id) - if "own" in params: - return objs - if request: - return objs.for_person(request.user.person) + elif request: + q = q & LessonEventQuerySet.for_person_q(request.user.person) + + objs = ( + super() + .get_objects(request, params, additional_filter=q, **kwargs) + .not_instance_of(SupervisionEvent) + .filter(q) + .select_related("subject", "course") + .prefetch_related("groups", "teachers", "rooms", "groups__members") + ) return objs class Meta: