From 4717d721213b7ad2a5aaac9a5ae230238737a5b7 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sun, 23 Jun 2024 17:37:31 +0200
Subject: [PATCH] Optimize documentation queries

---
 aleksis/apps/alsijil/models.py               | 26 ++++++++++++++------
 aleksis/apps/alsijil/schema/__init__.py      |  8 +++++-
 aleksis/apps/alsijil/schema/documentation.py |  3 +--
 3 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index 8dd46a5a8..6be1734a1 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -530,6 +530,8 @@ class Documentation(CalendarEvent):
     @classmethod
     def get_documentations_for_events(
         cls,
+        datetime_start: datetime,
+        datetime_end: datetime,
         events: list,
         incomplete: Optional[bool] = False,
     ) -> tuple:
@@ -539,18 +541,28 @@ class Documentation(CalendarEvent):
         """
         docs = []
         dummies = []
+
+        # Prefetch existing documentations to speed things up
+        existing_documentations = Documentation.objects.filter(
+            datetime_start__lte=datetime_end,
+            datetime_end__gte=datetime_start,
+            amends__in=[e["REFERENCE_OBJECT"] for e in events],
+        ).prefetch_related("participations")
+
         for event in events:
             if incomplete and event["STATUS"] == "CANCELLED":
                 continue
 
             event_reference_obj = event["REFERENCE_OBJECT"]
-            existing_documentations = event_reference_obj.amended_by.filter(
-                datetime_start=event["DTSTART"].dt,
-                datetime_end=event["DTEND"].dt,
+            existing_documentations_event = filter(
+                lambda d: (
+                    d.datetime_start == event["DTSTART"].dt and d.datetime_end == event["DTEND"].dt
+                ),
+                existing_documentations,
             )
 
-            if existing_documentations.exists():
-                doc = existing_documentations.first()
+            doc = next(existing_documentations_event, None)
+            if doc:
                 if incomplete and doc.topic:
                     continue
                 docs.append(doc)
@@ -579,7 +591,7 @@ class Documentation(CalendarEvent):
                     )
                 )
 
-        return (docs, dummies)
+        return docs, dummies
 
     @classmethod
     def get_documentations_for_person(
@@ -606,7 +618,7 @@ class Documentation(CalendarEvent):
             with_reference_object=True,
         )
 
-        return Documentation.get_documentations_for_events(events, incomplete)
+        return Documentation.get_documentations_for_events(start, end, events, incomplete)
 
     @classmethod
     def parse_dummy(
diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py
index 2634a297d..ec8a9b025 100644
--- a/aleksis/apps/alsijil/schema/__init__.py
+++ b/aleksis/apps/alsijil/schema/__init__.py
@@ -11,6 +11,7 @@ from aleksis.apps.cursus.schema import CourseType
 from aleksis.core.models import Group, Person
 from aleksis.core.schema.base import FilterOrderList
 from aleksis.core.schema.group import GroupType
+from aleksis.core.schema.person import PersonType
 from aleksis.core.util.core_helpers import has_person
 
 from ..models import Documentation
@@ -112,7 +113,12 @@ class Query(graphene.ObjectType):
         )
 
         # Lookup or create documentations and return them all.
-        docs, dummies = Documentation.get_documentations_for_events(events, incomplete)
+        docs, dummies = Documentation.get_documentations_for_events(
+            datetime.combine(date_start, datetime.min.time()),
+            datetime.combine(date_end, datetime.max.time()),
+            events,
+            incomplete,
+        )
         return docs + dummies
 
     @staticmethod
diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py
index 63aecfdfc..39eed04a4 100644
--- a/aleksis/apps/alsijil/schema/documentation.py
+++ b/aleksis/apps/alsijil/schema/documentation.py
@@ -34,7 +34,6 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp
             "date_start",
             "date_end",
             "teachers",
-            "participations",
         )
         filter_fields = {
             "id": ["exact", "lte", "gte"],
@@ -78,7 +77,7 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp
         # A dummy documentation will not have any participations
         if str(root.pk).startswith("DUMMY") or not hasattr(root, "participations"):
             return []
-        return root.participations.select_related("absence_reason", "base_absence").all()
+        return root.participations.all()
 
 
 class DocumentationInputType(graphene.InputObjectType):
-- 
GitLab