api: datetimes & past events (#48397)

This commit is contained in:
Lauréline Guérin 2021-06-03 15:32:32 +02:00
parent 4d9c0330ad
commit 302c2c0285
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 570 additions and 9 deletions

View File

@ -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_days__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,

View File

@ -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 => not disabled (always ok to book a past event)
return False
if event.remaining_places < min_places and event.remaining_waiting_list_places < min_places:
return True
return False
@ -683,13 +686,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
show_events = request.GET.get('events') or 'future'
show_past = show_events in ['all', 'past']
show_future = show_events in ['all', 'future']
entries = agenda.get_open_events(
annotate_queryset=True,
min_start=start_datetime,
max_start=end_datetime,
excluded_user_external_id=user_external_id,
)
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)]

View File

@ -650,3 +650,512 @@ 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
)
start_datetime = localtime(now() - datetime.timedelta(days=3 * 7))
event = Event.objects.create(
label='Recurring',
start_datetime=start_datetime,
recurrence_days=[start_datetime.weekday()],
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