api: add a meta dict on datetimes endpoints (#50278)

This commit is contained in:
Nicolas Roche 2021-01-18 18:00:50 +01:00 committed by Frédéric Péters
parent 1ec10c4e46
commit 446ca922be
3 changed files with 275 additions and 21 deletions

View File

@ -749,8 +749,8 @@ class SharedTimePeriod(object):
When it crosses end_datetime it stops.
Generated start_datetime MUST be in the local timezone as the API
needs it to generate stable ids.
Generated start_datetime MUST be in the local timezone, and the local
timezone must not change, as the API needs it to generate stable ids.
"""
meeting_duration = datetime.timedelta(minutes=meeting_duration)
duration = datetime.timedelta(minutes=base_duration)

View File

@ -398,6 +398,24 @@ def get_event_detail(request, event, agenda=None):
}
def get_events_meta_detail(events):
bookable_datetimes_number_total = 0
bookable_datetimes_number_available = 0
bookable_datetimes_first = None
for event in events:
bookable_datetimes_number_total += 1
if not bool(event.full):
bookable_datetimes_number_available += 1
if not bookable_datetimes_first:
bookable_datetimes_first = format_response_datetime(event.start_datetime)
return {
'no_bookable_datetimes': bool(bookable_datetimes_number_available == 0),
'bookable_datetimes_number_total': bookable_datetimes_number_total,
'bookable_datetimes_number_available': bookable_datetimes_number_available,
'bookable_datetimes_first': bookable_datetimes_first,
}
def get_resources_from_request(request, agenda):
if agenda.kind != 'meetings' or 'resources' not in request.GET:
return []
@ -502,7 +520,10 @@ class Datetimes(APIView):
)
)
response = {'data': [get_event_detail(request, x, agenda=agenda) for x in entries]}
response = {
'data': [get_event_detail(request, x, agenda=agenda) for x in entries],
'meta': get_events_meta_detail(entries),
}
return Response(response)
@ -603,21 +624,23 @@ class MeetingDatetimes(APIView):
if resources:
fillslot_url += '?resources=%s' % ','.join(r.slug for r in resources)
def make_id(start_datetime, meeting_type):
"""Make virtual id for a slot, combining meeting_type.id and
iso-format of date and time.
!!! The datetime must always be in the local timezone and the local
timezone must not change if we want the id to be stable.
It MUST be a garanty of SharedTimePeriod.get_time_slots(),
!!!
"""
return '%s:%s' % (
meeting_type.slug,
start_datetime.strftime('%Y-%m-%d-%H%M'),
)
bookable_datetimes_number_total = 0
bookable_datetimes_number_available = 0
bookable_datetimes_first = None
data = []
for slot in generator_of_unique_slots:
bookable_datetimes_number_total += 1
if not bool(slot.full):
bookable_datetimes_number_available += 1
if not bookable_datetimes_first:
bookable_datetimes_first = format_response_datetime(slot.start_datetime)
response = {
'data': [
# Make virtual id for a slot, combining meeting_type.id and
# iso-format of date and time.
# (SharedTimePeriod.get_time_slots() generate datetime in fixed local timezone,
# in order to make slot_id stable.)
slot_id = '%s:%s' % (meeting_type.slug, slot.start_datetime.strftime('%Y-%m-%d-%H%M'))
data.append(
{
'id': slot_id,
'datetime': format_response_datetime(slot.start_datetime),
@ -625,10 +648,16 @@ class MeetingDatetimes(APIView):
'disabled': bool(slot.full),
'api': {'fillslot_url': fillslot_url.replace(fake_event_identifier, slot_id)},
}
for slot in generator_of_unique_slots
# we do not have the := operator, so we do that
for slot_id in [make_id(slot.start_datetime, meeting_type)]
]
)
response = {
'data': data,
'meta': {
'no_bookable_datetimes': bool(bookable_datetimes_number_available == 0),
'bookable_datetimes_number_total': bookable_datetimes_number_total,
'bookable_datetimes_number_available': bookable_datetimes_number_available,
'bookable_datetimes_first': bookable_datetimes_first,
},
}
return Response(response)

View File

@ -4981,3 +4981,228 @@ def test_meetings_and_virtual_datetimes_date_filter(app):
virtual_agenda.save()
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 16
def test_datetimes_api_meta(app, freezer):
# 2017-05-20 -> saturday
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
agenda = Agenda.objects.create(label=u'Foo bar')
first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0)
first_date += datetime.timedelta(days=1)
for i in range(3):
event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda)
event.save()
# a date in the past
event = Event(start_datetime=first_date - datetime.timedelta(days=10), places=10, agenda=agenda)
event.save()
events = Event.objects.filter(agenda_id=agenda.id).exclude(start_datetime__lt=now())
assert len(events) == 3
api_url = '/api/agenda/%s/datetimes/' % agenda.slug
resp = app.get(api_url)
assert len(resp.json['data']) == 3
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 3,
'bookable_datetimes_number_available': 3,
'bookable_datetimes_first': '2017-05-21 17:00:00',
}
def simulate_booking(event, nb_places):
for i in range(nb_places):
Booking(event=event).save()
simulate_booking(events[0], 10)
resp = app.get(api_url)
assert len(resp.json['data']) == 3
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 3,
'bookable_datetimes_number_available': 3,
'bookable_datetimes_first': '2017-05-21 17:00:00',
}
simulate_booking(events[0], 10)
resp = app.get(api_url)
assert len(resp.json['data']) == 3
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 3,
'bookable_datetimes_number_available': 2,
'bookable_datetimes_first': '2017-05-22 17:00:00',
}
simulate_booking(events[1], 20)
simulate_booking(events[2], 20)
resp = app.get(api_url)
assert len(resp.json['data']) == 3
assert resp.json['meta'] == {
'no_bookable_datetimes': True,
'bookable_datetimes_number_total': 3,
'bookable_datetimes_number_available': 0,
'bookable_datetimes_first': None,
}
def test_datetimes_api_meetings_agenda_meta(app, freezer):
# 2017-05-20 -> saturday
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
meetings_agenda = Agenda.objects.create(
label=u'Foo bar Meeting', kind='meetings', maximal_booking_delay=3
)
meeting_type = MeetingType(agenda=meetings_agenda, label='Blah', duration=30)
meeting_type.save()
desk1 = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
desk2 = Desk.objects.create(agenda=meetings_agenda, label='Desk 2')
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
for desk in desk1, desk2:
TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
).save()
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][2]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'bookable_datetimes_first': '2017-05-22 10:00:00',
}
def simulate_booking(slot, desk):
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=meetings_agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=desk,
)
ev.save()
booking = Booking(event=ev)
booking.save()
simulate_booking(resp.json['data'][0], desk1)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'bookable_datetimes_first': '2017-05-22 10:00:00',
}
simulate_booking(resp.json['data'][0], desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'bookable_datetimes_first': '2017-05-22 10:30:00',
}
for idx in range(1, 4):
simulate_booking(resp.json['data'][idx], desk1)
simulate_booking(resp.json['data'][idx], desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['meta'] == {
'no_bookable_datetimes': True,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 0,
'bookable_datetimes_first': None,
}
def test_datetimes_api_virtual_meetings_agenda_meta(app, freezer):
# 2017-05-20 -> saturday
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
meetings_agenda1 = Agenda.objects.create(label=u'Foo Meeting', kind='meetings', maximal_booking_delay=3)
meetings_agenda2 = Agenda.objects.create(label=u'Bar Meeting', kind='meetings', maximal_booking_delay=3)
virtual_agenda = Agenda.objects.create(label=u'Agenda Virtual', kind='virtual', maximal_booking_delay=3)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda1)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda2)
desk1 = Desk.objects.create(agenda=meetings_agenda1, label='Desk 1')
desk2 = Desk.objects.create(agenda=meetings_agenda2, label='Desk 2')
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
for agenda, desk in zip((meetings_agenda1, meetings_agenda2), (desk1, desk2)):
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
).save()
virt_meeting_type = virtual_agenda.iter_meetingtypes()[0]
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][2]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'bookable_datetimes_first': '2017-05-22 10:00:00',
}
def simulate_booking(slot, agenda, desk):
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=desk,
)
ev.save()
booking = Booking(event=ev)
booking.save()
simulate_booking(resp.json['data'][0], meetings_agenda1, desk1)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'bookable_datetimes_first': '2017-05-22 10:00:00',
}
simulate_booking(resp.json['data'][0], meetings_agenda2, desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'bookable_datetimes_first': '2017-05-22 10:30:00',
}
for idx in range(1, 4):
simulate_booking(resp.json['data'][idx], meetings_agenda1, desk1)
simulate_booking(resp.json['data'][idx], meetings_agenda2, desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['meta'] == {
'no_bookable_datetimes': True,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 0,
'bookable_datetimes_first': None,
}