From e663bbac49044b3b45d2d1a488b25550628d0df4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 12 Jun 2020 16:12:58 +0200
Subject: [PATCH] api: return opened_events_available in events agenda detail
 payload (#43937)

---
 chrono/agendas/models.py | 24 ++++++++++++++++++
 chrono/api/views.py      | 30 ++++------------------
 tests/test_api.py        | 55 ++++++++++++++++++++++++++++++++++++++--
 3 files changed, 82 insertions(+), 27 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 90d680a..a5134bb 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -363,6 +363,30 @@ class Agenda(models.Model):
                 for weektime_interval in IntervalSet.simple(*time_period_interval) - closed_hours_by_days:
                     yield SharedTimePeriod.from_weektime_interval(weektime_interval, desks=desks)
 
+    def get_opened_events(self):
+        assert self.kind == 'events'
+
+        entries = self.event_set.all()
+        # we never want to allow booking for past events.
+        entries = entries.filter(start_datetime__gte=localtime(now()))
+        # exclude non published events
+        entries = entries.filter(
+            Q(publication_date__isnull=True) | Q(publication_date__lte=localtime(now()).date())
+        )
+        if self.minimal_booking_delay:
+            entries = entries.filter(
+                start_datetime__gte=localtime(
+                    now() + datetime.timedelta(days=self.minimal_booking_delay)
+                ).replace(hour=0, minute=0)
+            )
+        if self.maximal_booking_delay:
+            entries = entries.filter(
+                start_datetime__lt=localtime(
+                    now() + datetime.timedelta(days=self.maximal_booking_delay)
+                ).replace(hour=0, minute=0)
+            )
+        return entries
+
 
 class VirtualMember(models.Model):
     '''Trough model to link virtual agendas to their real agendas.
diff --git a/chrono/api/views.py b/chrono/api/views.py
index 9a46127..13b5662 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -273,7 +273,7 @@ def get_all_slots(base_agenda, meeting_type, resources=None, unique=False):
                         break
 
 
-def get_agenda_detail(request, agenda):
+def get_agenda_detail(request, agenda, check_events=False):
     agenda_detail = {
         'id': agenda.slug,
         'slug': agenda.slug,  # kept for compatibility
@@ -293,6 +293,8 @@ def get_agenda_detail(request, agenda):
                 reverse('api-agenda-datetimes', kwargs={'agenda_identifier': agenda.slug})
             )
         }
+        if check_events:
+            agenda_detail['opened_events_available'] = agenda.get_opened_events().filter(full=False).exists()
     elif agenda.accept_meetings():
         agenda_detail['api'] = {
             'meetings_url': request.build_absolute_uri(
@@ -370,7 +372,7 @@ class AgendaDetail(APIView):
 
     def get(self, request, agenda_identifier):
         agenda = get_object_or_404(Agenda, slug=agenda_identifier)
-        return Response({'data': get_agenda_detail(request, agenda)})
+        return Response({'data': get_agenda_detail(request, agenda, check_events=True)})
 
 
 agenda_detail = AgendaDetail.as_view()
@@ -391,29 +393,7 @@ class Datetimes(APIView):
         if agenda.kind != 'events':
             raise Http404('agenda found, but it was not an events agenda')
 
-        entries = Event.objects.filter(agenda=agenda)
-
-        # we never want to allow booking for past events.
-        entries = entries.filter(start_datetime__gte=localtime(now()))
-
-        # exclude non published events
-        entries = entries.filter(
-            Q(publication_date__isnull=True) | Q(publication_date__lte=localtime(now()).date())
-        )
-
-        if agenda.minimal_booking_delay:
-            entries = entries.filter(
-                start_datetime__gte=localtime(
-                    now() + datetime.timedelta(days=agenda.minimal_booking_delay)
-                ).replace(hour=0, minute=0)
-            )
-
-        if agenda.maximal_booking_delay:
-            entries = entries.filter(
-                start_datetime__lt=localtime(
-                    now() + datetime.timedelta(days=agenda.maximal_booking_delay)
-                ).replace(hour=0, minute=0)
-            )
+        entries = agenda.get_opened_events()
 
         if 'date_start' in request.GET:
             entries = entries.filter(
diff --git a/tests/test_api.py b/tests/test_api.py
index 1ea99a8..f22a767 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -2311,16 +2311,67 @@ def test_multiple_booking_move_booking(app, user):
         assert Event.objects.get(id=event.id).booked_places == 1
 
 
-def test_agenda_detail_api(app, some_data):
-    agenda = Agenda.objects.get(slug='foo-bar')
+def test_agenda_detail_api(app):
+    agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
+    first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0)
+    first_date += datetime.timedelta(days=1)
+    event1 = Event.objects.create(
+        start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
+        places=20,
+        agenda=agenda,
+    )
+    event2 = Event.objects.create(
+        start_datetime=(now() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
+        places=20,
+        agenda=agenda,
+    )
+    event3 = Event.objects.create(
+        start_datetime=(now() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
+        places=20,
+        agenda=agenda,
+    )
     resp = app.get('/api/agenda/%s/' % agenda.slug)
     data = resp.json['data']
     assert data['id'] == 'foo-bar'
     assert data['slug'] == 'foo-bar'
     assert data['text'] == 'Foo bar'
     assert data['kind'] == 'events'
+    assert data['opened_events_available'] is True
     assert data['api']['datetimes_url'] == 'http://testserver/api/agenda/foo-bar/datetimes/'
 
+    # one event is full
+    Event.objects.filter(pk=event1.pk).update(full=True)
+    resp = app.get('/api/agenda/%s/' % agenda.slug)
+    assert resp.json['data']['opened_events_available'] is True
+
+    # all events are full
+    Event.objects.update(full=True)
+    resp = app.get('/api/agenda/%s/' % agenda.slug)
+    assert resp.json['data']['opened_events_available'] is False
+
+    # event1 is not full but too soon
+    Event.objects.filter(pk=event1.pk).update(full=False)
+    agenda.minimal_booking_delay = 10
+    agenda.save()
+    resp = app.get('/api/agenda/%s/' % agenda.slug)
+    assert list(agenda.get_opened_events()) == [event2, event3]
+    assert resp.json['data']['opened_events_available'] is False
+
+    # event3 is not full but too late
+    Event.objects.filter(pk=event3.pk).update(full=False)
+    agenda.maximal_booking_delay = 12
+    agenda.save()
+    resp = app.get('/api/agenda/%s/' % agenda.slug)
+    assert list(agenda.get_opened_events()) == [event2]
+    assert resp.json['data']['opened_events_available'] is False
+
+    # events are not full but not published
+    Event.objects.update(full=False)
+    agenda.event_set.update(publication_date=now().date() + datetime.timedelta(days=20))
+    resp = app.get('/api/agenda/%s/' % agenda.slug)
+    assert list(agenda.get_opened_events()) == []
+    assert resp.json['data']['opened_events_available'] is False
+
     # unknown
     app.get('/api/agenda/whatever/', status=404)
 
-- 
2.20.1

