From 627ae74e16218e3f6c228edca652d97998c5bcc7 Mon Sep 17 00:00:00 2001
From: Valentin Deniaud <vdeniaud@entrouvert.com>
Date: Mon, 13 Dec 2021 11:54:49 +0100
Subject: [PATCH 1/2] fix order for subscriptions in multiple agendas datetimes

---
 chrono/agendas/models.py    |  1 +
 chrono/api/views.py         | 16 ++++++++++-
 tests/api/test_datetimes.py | 53 ++++++++++++++++++++++++++++++++++---
 3 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 915b3a1e..73eb76cb 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -945,6 +945,7 @@ class Agenda(models.Model):
             ),
         )
         qs = Agenda.prefetch_recurring_events(qs)
+        qs = qs.select_related('category')
         agendas_exceptions = TimePeriodException.objects.filter(
             Q(desk__slug='_exceptions_holder', desk__agenda__in=qs)
             | Q(
diff --git a/chrono/api/views.py b/chrono/api/views.py
index e6c7c7c8..d79bd4dd 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -905,7 +905,21 @@ class MultipleAgendasDatetimes(APIView):
                 agenda_slug: i for i, agenda_slug in enumerate(payload['agenda_slugs'])
             }
             entries.sort(
-                key=lambda event: (event.start_datetime, agenda_querystring_indexes[event.agenda.slug])
+                key=lambda event: (
+                    event.start_datetime,
+                    agenda_querystring_indexes[event.agenda.slug],
+                    event.slug,
+                )
+            )
+        elif 'subscribed' in request.query_params:
+            category_querystring_indexes = {category: i for i, category in enumerate(payload['subscribed'])}
+            entries.sort(
+                key=lambda event: (
+                    event.start_datetime,
+                    category_querystring_indexes.get(event.agenda.category.slug),
+                    event.agenda.slug,
+                    event.slug,
+                )
             )
 
         response = {
diff --git a/tests/api/test_datetimes.py b/tests/api/test_datetimes.py
index 27d91b81..f0b2cfa2 100644
--- a/tests/api/test_datetimes.py
+++ b/tests/api/test_datetimes.py
@@ -1792,17 +1792,26 @@ def test_datetimes_multiple_agendas(app):
 
 @pytest.mark.freeze_time('2021-05-06 14:00')
 def test_datetimes_multiple_agendas_sort(app):
-    first_agenda = Agenda.objects.create(label='First agenda', kind='events')
+    category = Category.objects.create(label='Category A')
+    first_agenda = Agenda.objects.create(label='First agenda', kind='events', category=category)
     Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder')
     Event.objects.create(label='10-05', start_datetime=now().replace(day=10), places=5, agenda=first_agenda)
-    second_agenda = Agenda.objects.create(label='Second agenda', kind='events')
+    second_agenda = Agenda.objects.create(label='Second agenda', kind='events', category=category)
     Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder')
     Event.objects.create(label='09-05', start_datetime=now().replace(day=9), places=5, agenda=second_agenda)
     Event.objects.create(label='04-05', start_datetime=now().replace(day=4), places=5, agenda=second_agenda)
-    third_agenda = Agenda.objects.create(label='Third agenda', kind='events')
+    category = Category.objects.create(label='Category B')
+    third_agenda = Agenda.objects.create(label='Third agenda', kind='events', category=category)
     Desk.objects.create(agenda=third_agenda, slug='_exceptions_holder')
     Event.objects.create(label='09-05', start_datetime=now().replace(day=9), places=5, agenda=third_agenda)
     Event.objects.create(label='04-05', start_datetime=now().replace(day=4), places=5, agenda=third_agenda)
+    for agenda in Agenda.objects.all():
+        Subscription.objects.create(
+            agenda=agenda,
+            user_external_id='xxx',
+            date_start=now(),
+            date_end=now() + datetime.timedelta(days=30),
+        )
 
     # check events are ordered by start_datetime and then by agenda order in querystring
     agenda_slugs = ','.join((first_agenda.slug, third_agenda.slug, second_agenda.slug))
@@ -1820,11 +1829,39 @@ def test_datetimes_multiple_agendas_sort(app):
     assert resp.json['data'][3]['id'] == 'second-agenda@09-05'
     assert resp.json['data'][4]['id'] == 'first-agenda@10-05'
 
+    # check subscribed events are ordered by start_datetime
+    resp = app.get('/api/agendas/datetimes/', params={'subscribed': 'all', 'user_external_id': 'xxx'})
+    assert len(resp.json['data']) == 3
+    assert resp.json['data'][0]['id'] == 'second-agenda@09-05'
+    assert resp.json['data'][1]['id'] == 'third-agenda@09-05'
+    assert resp.json['data'][2]['id'] == 'first-agenda@10-05'
+
+    # and by category slug if specified in querystring
+    resp = app.get(
+        '/api/agendas/datetimes/', params={'subscribed': 'category-b,category-a', 'user_external_id': 'xxx'}
+    )
+    assert len(resp.json['data']) == 3
+    assert resp.json['data'][0]['id'] == 'third-agenda@09-05'
+    assert resp.json['data'][1]['id'] == 'second-agenda@09-05'
+    assert resp.json['data'][2]['id'] == 'first-agenda@10-05'
+
+    # order is stable if same date events are in the same category
+    Event.objects.create(label='09-05', start_datetime=now().replace(day=9), places=5, agenda=first_agenda)
+    resp = app.get(
+        '/api/agendas/datetimes/', params={'subscribed': 'category-b,category-a', 'user_external_id': 'xxx'}
+    )
+    assert len(resp.json['data']) == 4
+    assert resp.json['data'][0]['id'] == 'third-agenda@09-05'
+    assert resp.json['data'][1]['id'] == 'first-agenda@09-05'
+    assert resp.json['data'][2]['id'] == 'second-agenda@09-05'
+    assert resp.json['data'][3]['id'] == 'first-agenda@10-05'
+
 
 @pytest.mark.freeze_time('2021-05-06 14:00')
 def test_datetimes_multiple_agendas_queries(app):
+    category = Category.objects.create(label='Category A')
     for i in range(10):
-        agenda = Agenda.objects.create(label=str(i), kind='events')
+        agenda = Agenda.objects.create(label=str(i), kind='events', category=category)
         Subscription.objects.create(
             agenda=agenda,
             user_external_id='xxx',
@@ -1852,6 +1889,14 @@ def test_datetimes_multiple_agendas_queries(app):
         assert len(resp.json['data']) == 30
         assert len(ctx.captured_queries) == 7
 
+    with CaptureQueriesContext(connection) as ctx:
+        resp = app.get(
+            '/api/agendas/datetimes/',
+            params={'subscribed': 'category-a', 'user_external_id': 'xxx', 'show_past_events': True},
+        )
+        assert len(resp.json['data']) == 30
+        assert len(ctx.captured_queries) == 7
+
 
 @pytest.mark.freeze_time('2021-05-06 14:00')
 def test_datetimes_multiple_agendas_subscribed(app):
-- 
2.30.2

