From 211c63bf498b24bec74f9fc562452dddab7290d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 3 Jun 2021 15:32:32 +0200
Subject: [PATCH 1/2] api: datetimes & past events (#48397)

---
 chrono/agendas/models.py    |  39 ++-
 chrono/api/views.py         |  33 ++-
 tests/api/test_datetimes.py | 508 ++++++++++++++++++++++++++++++++++++
 3 files changed, 570 insertions(+), 10 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 0b2a525..6a180e6 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -655,7 +655,7 @@ class Agenda(models.Model):
         if max_start:
             entries = self.add_event_recurrences(
                 entries,
-                min_start or localtime(now()),
+                max(min_start or localtime(now()), localtime(now())),
                 max_start,
                 include_full=include_full,
                 prefetched_queryset=prefetched_queryset,
@@ -663,6 +663,43 @@ class Agenda(models.Model):
 
         return entries
 
+    def get_past_events(
+        self,
+        annotate_queryset=False,
+        min_start=None,
+        max_start=None,
+        excluded_user_external_id=None,
+    ):
+        assert self.kind == 'events'
+
+        # recurring events are never opened
+        entries = self.event_set.filter(recurrence_rule__isnull=True)
+        # exclude canceled events except for event recurrences
+        entries = entries.filter(Q(cancelled=False) | Q(primary_event__isnull=False))
+        # we want only past events
+        entries = entries.filter(start_datetime__lt=localtime(now()))
+
+        if min_start:
+            entries = entries.filter(start_datetime__gte=min_start)
+
+        if max_start:
+            entries = entries.filter(start_datetime__lt=max_start)
+
+        if excluded_user_external_id:
+            entries = Event.annotate_queryset_for_user(entries, excluded_user_external_id)
+
+        if annotate_queryset:
+            entries = Event.annotate_queryset(entries)
+
+        if min_start:
+            entries = self.add_event_recurrences(
+                entries,
+                min_start,
+                min(max_start or localtime(now()), localtime(now())),
+            )
+
+        return entries
+
     def add_event_recurrences(
         self,
         events,
diff --git a/chrono/api/views.py b/chrono/api/views.py
index 0de03eb..d03a4cb 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -406,10 +406,13 @@ def get_event_places(event):
 
 
 def is_event_disabled(event, min_places=1):
-    if event.remaining_places < min_places and event.remaining_waiting_list_places < min_places:
-        return True
     if getattr(event, 'user_places_count', 0) > 0:
         return True
+    if event.start_datetime < now():
+        # event is past, it's alway ok
+        return False
+    if event.remaining_places < min_places and event.remaining_waiting_list_places < min_places:
+        return True
     return False
 
 
@@ -682,13 +685,25 @@ class Datetimes(APIView):
 
         start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
         user_external_id = request.GET.get('exclude_user_external_id') or None
-
-        entries = agenda.get_open_events(
-            annotate_queryset=True,
-            min_start=start_datetime,
-            max_start=end_datetime,
-            excluded_user_external_id=user_external_id,
-        )
+        show_events = request.GET.get('events') or 'future'
+        show_past = show_events in ['all', 'past']
+        show_future = show_events in ['all', 'future']
+
+        entries = []
+        if show_past:
+            entries += agenda.get_past_events(
+                annotate_queryset=True,
+                min_start=start_datetime,
+                max_start=end_datetime,
+                excluded_user_external_id=user_external_id,
+            )
+        if show_future:
+            entries += agenda.get_open_events(
+                annotate_queryset=True,
+                min_start=start_datetime,
+                max_start=end_datetime,
+                excluded_user_external_id=user_external_id,
+            )
 
         if request.GET.get('hide_disabled'):
             entries = [e for e in entries if not is_event_disabled(e, min_places)]
diff --git a/tests/api/test_datetimes.py b/tests/api/test_datetimes.py
index 047dc8d..409e842 100644
--- a/tests/api/test_datetimes.py
+++ b/tests/api/test_datetimes.py
@@ -627,3 +627,511 @@ def test_recurring_events_api_exceptions(app, user, freezer):
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.post(fillslot_url, status=400)
     assert resp.json['err'] == 1
+
+
+def test_past_datetimes(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    event1 = Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    event2 = Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'all'})
+    data = resp.json['data']
+    assert len(data) == 2
+    assert data[0]['id'] == 'today-before-now'
+    assert data[1]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+
+    # check canceled
+    event1.cancel()
+    event2.cancel()
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'all'})
+    data = resp.json['data']
+    assert len(data) == 0
+
+
+def test_past_datetimes_meta(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future'})
+    assert len(resp.json['data']) == 1
+    assert resp.json['meta'] == {
+        'no_bookable_datetimes': False,
+        'bookable_datetimes_number_total': 1,
+        'bookable_datetimes_number_available': 1,
+        'first_bookable_slot': resp.json['data'][0],
+    }
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past'})
+    assert len(resp.json['data']) == 1
+    assert resp.json['data'][0]['id'] == 'today-before-now'
+    assert resp.json['meta'] == {
+        'no_bookable_datetimes': False,
+        'bookable_datetimes_number_total': 1,
+        'bookable_datetimes_number_available': 1,
+        'first_bookable_slot': resp.json['data'][0],
+    }
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'all'})
+    assert len(resp.json['data']) == 2
+    assert resp.json['data'][0]['id'] == 'today-before-now'
+    assert resp.json['data'][1]['id'] == 'today-after-now'
+    assert resp.json['meta'] == {
+        'no_bookable_datetimes': False,
+        'bookable_datetimes_number_total': 2,
+        'bookable_datetimes_number_available': 2,
+        'first_bookable_slot': resp.json['data'][0],
+    }
+
+
+def test_past_datetimes_date_range(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    Event.objects.create(
+        label='Yesterday 18H',
+        start_datetime=localtime(now() - datetime.timedelta(days=1)).replace(hour=18, minute=0),
+        places=5,
+        agenda=agenda,
+    )
+    Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    Event.objects.create(
+        label='Tomorrow 16H',
+        start_datetime=localtime(now() + datetime.timedelta(days=1)).replace(hour=16, minute=0),
+        places=5,
+        agenda=agenda,
+    )
+
+    # check date_start
+    params = {'date_start': localtime(now() - datetime.timedelta(hours=2)), 'events': 'future'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 2
+    assert data[0]['id'] == 'today-after-now'
+    assert data[1]['id'] == 'tomorrow-16h'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+
+    params = {'date_start': localtime(now() - datetime.timedelta(hours=2)), 'events': 'past'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False
+
+    params = {'date_start': localtime(now() - datetime.timedelta(hours=2)), 'events': 'all'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 3
+    assert data[0]['id'] == 'today-before-now'
+    assert data[1]['id'] == 'today-after-now'
+    assert data[2]['id'] == 'tomorrow-16h'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+
+    params = {'date_start': localtime(now() + datetime.timedelta(hours=2)), 'events': 'future'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'tomorrow-16h'
+    assert data[0]['disabled'] is False
+
+    params = {'date_start': localtime(now() + datetime.timedelta(hours=2)), 'events': 'past'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 0
+
+    params = {'date_start': localtime(now() + datetime.timedelta(hours=2)), 'events': 'all'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'tomorrow-16h'
+    assert data[0]['disabled'] is False
+
+    # check date_end
+    params = {'date_end': localtime(now() + datetime.timedelta(hours=2)), 'events': 'future'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+
+    params = {'date_end': localtime(now() + datetime.timedelta(hours=2)), 'events': 'past'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 2
+    assert data[0]['id'] == 'yesterday-18h'
+    assert data[1]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+
+    params = {'date_end': localtime(now() + datetime.timedelta(hours=2)), 'events': 'all'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 3
+    assert data[0]['id'] == 'yesterday-18h'
+    assert data[1]['id'] == 'today-before-now'
+    assert data[2]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+
+    params = {'date_end': localtime(now() - datetime.timedelta(hours=2)), 'events': 'future'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 0
+
+    params = {'date_end': localtime(now() - datetime.timedelta(hours=2)), 'events': 'past'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'yesterday-18h'
+    assert data[0]['disabled'] is False
+
+    params = {'date_end': localtime(now() - datetime.timedelta(hours=2)), 'events': 'all'}
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'yesterday-18h'
+    assert data[0]['disabled'] is False
+
+    # mix
+    params = {
+        'date_start': localtime(now() - datetime.timedelta(hours=2)),
+        'date_end': localtime(now() + datetime.timedelta(hours=2)),
+        'events': 'future',
+    }
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+
+    params = {
+        'date_start': localtime(now() - datetime.timedelta(hours=2)),
+        'date_end': localtime(now() + datetime.timedelta(hours=2)),
+        'events': 'past',
+    }
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False
+
+    params = {
+        'date_start': localtime(now() - datetime.timedelta(hours=2)),
+        'date_end': localtime(now() + datetime.timedelta(hours=2)),
+        'events': 'all',
+    }
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
+    data = resp.json['data']
+    assert len(data) == 2
+    assert data[0]['id'] == 'today-before-now'
+    assert data[1]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+
+
+def test_past_datetimes_places(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    event1 = Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=1,
+        agenda=agenda,
+    )
+    event2 = Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=1,
+        agenda=agenda,
+    )
+    Booking.objects.create(event=event1)
+    Booking.objects.create(event=event2)
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert resp.json['data'][0]['places']['full'] is True
+    assert data[0]['disabled'] is True
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert resp.json['data'][0]['places']['full'] is True
+    assert data[0]['disabled'] is False  # always available if past
+    assert resp.json['meta']['first_bookable_slot']['id'] == 'today-before-now'
+
+    event1.waiting_list_places = 1
+    event1.save()
+    event2.waiting_list_places = 1
+    event2.save()
+    Booking.objects.create(event=event1, in_waiting_list=True)
+    Booking.objects.create(event=event2, in_waiting_list=True)
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert resp.json['data'][0]['places']['full'] is True
+    assert data[0]['disabled'] is True
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past'})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert resp.json['data'][0]['places']['full'] is True
+    assert data[0]['disabled'] is False  # always available if past
+    assert resp.json['meta']['first_bookable_slot']['id'] == 'today-before-now'
+
+
+def test_past_datetimes_min_places(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=1,
+        agenda=agenda,
+    )
+    Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=1,
+        agenda=agenda,
+    )
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future', 'min_places': 2})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is True
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past', 'min_places': 2})
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False  # always available if past
+    assert resp.json['meta']['first_bookable_slot']['id'] == 'today-before-now'
+
+
+def test_past_datetimes_exclude_slots(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    event1 = Event.objects.create(
+        label='Today before now',
+        start_datetime=localtime(now() - datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    event2 = Event.objects.create(
+        label='Today after now',
+        start_datetime=localtime(now() + datetime.timedelta(hours=1)),
+        places=5,
+        agenda=agenda,
+    )
+    Booking.objects.create(event=event1, user_external_id='42')
+    Booking.objects.create(event=event2, user_external_id='42')
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={'events': 'future', 'exclude_user_external_id': '35'},
+    )
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is False
+    assert resp.json['meta']['first_bookable_slot']['id'] == 'today-after-now'
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past', 'exclude_user_external_id': '35'}
+    )
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is False
+    assert resp.json['meta']['first_bookable_slot']['id'] == 'today-before-now'
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={'events': 'future', 'exclude_user_external_id': '42'},
+    )
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-after-now'
+    assert data[0]['disabled'] is True
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past', 'exclude_user_external_id': '42'}
+    )
+    data = resp.json['data']
+    assert len(data) == 1
+    assert data[0]['id'] == 'today-before-now'
+    assert data[0]['disabled'] is True
+
+
+def test_past_datetimes_recurring_event(app, user):
+    agenda = Agenda.objects.create(
+        label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
+    )
+    event = Event.objects.create(
+        label='Recurring',
+        start_datetime=localtime(now() - datetime.timedelta(days=3 * 7)),
+        repeat='weekly',
+        places=5,
+        agenda=agenda,
+    )
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'future'})
+    data = resp.json['data']
+    assert len(data) == 4
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'past'})
+    data = resp.json['data']
+    assert len(data) == 0  # no date_start, not possible to compute recurring events in past
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={'events': 'past', 'date_start': localtime(now() - datetime.timedelta(days=6 * 7))},
+    )
+    data = resp.json['data']
+    assert len(data) == 4
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+
+    resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'events': 'all'})
+    data = resp.json['data']
+    assert len(data) == 4  # no date_start, not possible to compute recurring events in past
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={'events': 'all', 'date_start': localtime(now() - datetime.timedelta(days=6 * 7))},
+    )
+    data = resp.json['data']
+    assert len(data) == 8
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+    assert data[4]['disabled'] is False
+    assert data[5]['disabled'] is False
+    assert data[6]['disabled'] is False
+    assert data[7]['disabled'] is False
+
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={
+            'events': 'all',
+            'date_start': localtime(now() - datetime.timedelta(days=6 * 7)),
+            'date_end': localtime(now() + datetime.timedelta(days=6 * 7)),
+        },
+    )
+    data = resp.json['data']
+    assert len(data) == 8
+    assert data[0]['disabled'] is False
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+    assert data[4]['disabled'] is False
+    assert data[5]['disabled'] is False
+    assert data[6]['disabled'] is False
+    assert data[7]['disabled'] is False
+
+    # check exclude_user_external_id
+    first_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
+    Booking.objects.create(event=first_recurrence, user_external_id='42')
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={
+            'events': 'past',
+            'exclude_user_external_id': '42',
+            'date_start': localtime(now() - datetime.timedelta(days=6 * 7)),
+        },
+    )
+    data = resp.json['data']
+    assert len(data) == 4
+    assert data[0]['disabled'] is True
+    assert data[1]['disabled'] is False
+    assert data[2]['disabled'] is False
+    assert data[3]['disabled'] is False
+
+    # check canceled
+    first_recurrence.cancel()
+    resp = app.get(
+        '/api/agenda/%s/datetimes/' % agenda.slug,
+        params={'events': 'all', 'date_start': localtime(now() - datetime.timedelta(days=6 * 7))},
+    )
+    data = resp.json['data']
+    assert len(data) == 7
-- 
2.30.2

