diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py index d52c750d5575910fa50299628ab6735ddb7d142b..07695bb22e7361f3cbfa02b8d5553d2ad6ed5b5d 100644 --- a/aleksis/apps/chronos/managers.py +++ b/aleksis/apps/chronos/managers.py @@ -25,63 +25,154 @@ class TimetableType(Enum): class LessonEventQuerySet(RecurrencePolymorphicQuerySet): """Queryset with special query methods for lesson events.""" + @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) + .union(LessonEvent.objects.filter(teachers=teacher).values_list("pk", flat=True)) + ) + return Q(pk__in=amended) + 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, teachers=teacher - ).values_list("amended_by__pk", flat=True) - return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct() + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, groups__members=person) + .values_list("amended_by__pk", flat=True) + .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True)) + ) + return Q(pk__in=amended) 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( - 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() + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, groups=group) + .values_list("amended_by__pk", flat=True) + .union( + LessonEvent.objects.filter( + amended_by__isnull=False, groups__parent_groups=group + ).values_list("amended_by__pk", flat=True) + ) + .union(LessonEvent.objects.filter(groups=group).values_list("pk", flat=True)) + .union( + LessonEvent.objects.filter(groups__parent_groups=group).values_list("pk", flat=True) + ) + ) + return Q(pk__in=amended) 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(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() + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, rooms=room) + .values_list("amended_by__pk", flat=True) + .union(LessonEvent.objects.filter(rooms=room).values_list("pk", flat=True)) + ) + return Q(pk__in=amended) 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( - 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() + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, course=course) + .values_list("amended_by__pk", flat=True) + .union(LessonEvent.objects.filter(course=course).values_list("pk", flat=True)) + ) + return Q(pk__in=amended) 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(amended_by__isnull=False, course=course).values_list( - "amended_by__pk", flat=True + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, teachers=person) + .values_list("amended_by__pk", flat=True) + .union( + LessonEvent.objects.filter( + amended_by__isnull=False, groups__members=person + ).values_list("amended_by__pk", flat=True) + ) + .union(LessonEvent.objects.filter(teachers=person).values_list("pk", flat=True)) + .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True)) ) - return self.filter(Q(course=course) | Q(pk__in=amended)).distinct() + return Q(pk__in=amended) 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 + teacher/participant/group owner/parent group owner, + including those amended. + """ 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() + amended = ( + LessonEvent.objects.filter(amended_by__isnull=False, teachers=person) + .values_list("amended_by__pk", flat=True) + .union( + LessonEvent.objects.filter( + amended_by__isnull=False, groups__members=person + ).values_list("amended_by__pk", flat=True) + ) + .union( + LessonEvent.objects.filter( + amended_by__isnull=False, groups__owners=person + ).values_list("amended_by__pk", flat=True) + ) + .union( + LessonEvent.objects.filter( + amended_by__isnull=False, groups__parent_groups__owners=person + ).values_list("amended_by__pk", flat=True) + ) + .union(LessonEvent.objects.filter(teachers=person).values_list("pk", flat=True)) + .union(LessonEvent.objects.filter(groups__members=person).values_list("pk", flat=True)) + .union(LessonEvent.objects.filter(groups__owners=person).values_list("pk", flat=True)) + .union( + LessonEvent.objects.filter(groups__parent_groups__owners=person).values_list( + "pk", flat=True + ) + ) + ) + return Q(pk__in=amended) def related_to_person(self, person: Union[int, Person]) -> "LessonEventQuerySet": """Get all lesson events a certain person is allowed to see. @@ -90,36 +181,34 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet): teacher/participant/group owner/parent group owner, including those amended. """ - from .models import LessonEvent + return self.filter(self.related_to_person_q(person)).distinct() - amended = LessonEvent.objects.filter( - Q(amended_by__isnull=False) - & ( - Q(teachers=person) - | Q(groups__members=person) - | Q(groups__owners=person) - | Q(groups__parent_groups__owners=person) - ) - ).values_list("amended_by__pk", flat=True) - return self.filter( - Q(teachers=person) - | Q(groups__members=person) - | Q(groups__owners=person) - | Q(groups__parent_groups__owners=person) - | Q(pk__in=amended) - ).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..8f3a04d809c6eca20bba019516c0f9a22cafe9cb 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 @@ -407,23 +406,22 @@ class LessonEvent(CalendarEvent): params: dict[str, any] | None = None, no_effect: bool = False, **kwargs, - ) -> Iterable: + ) -> QuerySet: """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 cls.objects.none() + + 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,44 @@ 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) - - if "own" in params: - return objs - if request: - return objs.for_person(request.user.person) + q = q & LessonEventQuerySet.for_course_q(obj_id) + + elif request: + q = q & LessonEventQuerySet.for_person_q(request.user.person) + + objs = super().get_objects( + request, + params, + start_qs=cls.objects.not_instance_of(SupervisionEvent), + additional_filter=q, + select_related=["subject", "course"], + prefetch_related=["groups", "teachers", "rooms", "groups__members"], + **kwargs, + ) return objs class Meta: