From f8b6bc78c4c0fe6ab9b02e54c80d9095c3f01475 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 19 Nov 2020 17:21:00 +0100
Subject: [PATCH 2/2] manager: reduce querysets on agenda settings page
 (#48624)

---
 chrono/agendas/models.py | 13 +++++--------
 chrono/manager/views.py  | 18 +++++++++++++++++-
 tests/test_manager.py    |  2 +-
 3 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 911892d..4821ffc 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -1210,12 +1210,11 @@ class Desk(models.Model):
         return new_desk
 
     def get_exceptions_within_two_weeks(self):
-        # FIXME : optimize DB queries, on the settings view this is called once per desk
+        # prefetched_exceptions contains desks exceptions + unavailability_calendars exceptions
         # default ordering: start_datetime
         in_two_weeks = make_aware(datetime.datetime.today() + datetime.timedelta(days=14))
         exceptions = []
-        queryset = TimePeriodException.objects.filter(Q(desk=self) | Q(unavailability_calendar__desks=self))
-        for exception in queryset.all():
+        for exception in self.prefetched_exceptions:
             if exception.end_datetime < now():
                 # exception ends in the past, skip it
                 continue
@@ -1228,7 +1227,7 @@ class Desk(models.Model):
         if exceptions:
             return exceptions
         # if none found within the 2 coming weeks, return the next one
-        for exception in queryset.all():
+        for exception in self.prefetched_exceptions:
             if exception.start_datetime < now():
                 # exception starts in the past, skip it
                 continue
@@ -1238,9 +1237,7 @@ class Desk(models.Model):
 
     def are_all_exceptions_displayed(self):
         in_two_weeks = self.get_exceptions_within_two_weeks()
-        return TimePeriodException.objects.filter(
-            Q(desk=self) | Q(unavailability_calendar__desks=self)
-        ).count() == len(in_two_weeks)
+        return len(self.prefetched_exceptions) == len(in_two_weeks)
 
     def import_timeperiod_exceptions_from_remote_ics(self, ics_url, source=None):
         try:
@@ -1621,7 +1618,7 @@ class TimePeriodException(models.Model):
 
     @property
     def read_only(self):
-        return self.source and self.source.settings_slug or self.unavailability_calendar
+        return self.source and self.source.settings_slug or self.unavailability_calendar_id
 
     class Meta:
         ordering = ['start_datetime']
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 259c1eb..b0c8b3b 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -1325,11 +1325,27 @@ class ManagedTimePeriodExceptionMixin(object):
 class AgendaSettings(ManagedAgendaMixin, DetailView):
     model = Agenda
 
+    def set_agenda(self, **kwargs):
+        self.agenda = get_object_or_404(
+            Agenda.objects.select_related('edit_role', 'view_role'), pk=kwargs.get('pk')
+        )
+
     def get_object(self, *args, **kwargs):
         if self.agenda.kind == 'meetings':
             self.agenda.prefetched_desks = self.agenda.desk_set.all().prefetch_related(
-                'timeperiod_set', 'timeperiodexception_set',
+                'timeperiod_set', 'unavailability_calendars',
             )
+            all_desks_exceptions = TimePeriodException.objects.filter(
+                Q(desk__in=self.agenda.prefetched_desks)
+                | Q(unavailability_calendar__desks__in=self.agenda.prefetched_desks)
+            ).select_related('source')
+            for desk in self.agenda.prefetched_desks:
+                uc_ids = [uc.pk for uc in desk.unavailability_calendars.all()]
+                desk.prefetched_exceptions = [
+                    e
+                    for e in all_desks_exceptions
+                    if e.desk_id == desk.pk or e.unavailability_calendar_id in uc_ids
+                ]
         return self.agenda
 
     def get_context_data(self, **kwargs):
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 0be527a..d1739b2 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -1042,7 +1042,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
     app = login(app)
     with CaptureQueriesContext(connection) as ctx:
         app.get('/manage/agendas/%s/settings' % agenda.pk)
-        assert len(ctx.captured_queries) == 63
+        assert len(ctx.captured_queries) == 12
 
 
 def test_agenda_resources(app, admin_user):
-- 
2.28.0

