api: add endpoint to book multiple events accross several agendas (#56104)
This commit is contained in:
parent
0cf2c62199
commit
67f1f68fd6
|
@ -21,6 +21,11 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'^agenda/$', views.agendas),
|
||||
url(r'^agendas/datetimes/$', views.agendas_datetimes, name='api-agendas-datetimes'),
|
||||
url(
|
||||
r'^agendas/events/fillslots/$',
|
||||
views.agendas_events_fillslots,
|
||||
name='api-agendas-events-fillslots',
|
||||
),
|
||||
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/$', views.agenda_detail),
|
||||
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'),
|
||||
url(
|
||||
|
|
|
@ -1605,7 +1605,10 @@ class EventsFillslots(APIView):
|
|||
serializer_class = serializers.EventsSlotsSerializer
|
||||
|
||||
def post(self, request, agenda_identifier):
|
||||
agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
|
||||
self.agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
|
||||
return self.fillslots(request)
|
||||
|
||||
def fillslots(self, request):
|
||||
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
|
||||
|
||||
serializer = self.serializer_class(data=request.data, partial=True)
|
||||
|
@ -1619,10 +1622,11 @@ class EventsFillslots(APIView):
|
|||
payload = serializer.validated_data
|
||||
user_external_id = payload['user_external_id']
|
||||
|
||||
events = get_events_from_slots(payload['slots'], request, agenda, payload)
|
||||
events = self.get_events(request, payload)
|
||||
events = events.select_related('agenda')
|
||||
events = Event.annotate_queryset(events)
|
||||
|
||||
already_booked_events = agenda.event_set.filter(booking__user_external_id=user_external_id)
|
||||
already_booked_events = self.get_already_booked_events(user_external_id)
|
||||
if start_datetime:
|
||||
already_booked_events = already_booked_events.filter(start_datetime__gte=start_datetime)
|
||||
if end_datetime:
|
||||
|
@ -1649,7 +1653,7 @@ class EventsFillslots(APIView):
|
|||
bookings = [make_booking(event, payload, extra_data) for event in events]
|
||||
|
||||
events_to_update = Event.annotate_queryset(
|
||||
agenda.event_set.filter(pk__in=events_to_unbook + [event.pk for event in events])
|
||||
Event.objects.filter(pk__in=events_to_unbook + [event.pk for event in events])
|
||||
)
|
||||
with transaction.atomic():
|
||||
deleted_count = Booking.objects.filter(
|
||||
|
@ -1665,15 +1669,66 @@ class EventsFillslots(APIView):
|
|||
response = {
|
||||
'err': 0,
|
||||
'booking_count': len(bookings),
|
||||
'waiting_list_events': [get_event_detail(request, x, agenda=agenda) for x in waiting_list_events],
|
||||
'waiting_list_events': [get_event_detail(request, x) for x in waiting_list_events],
|
||||
'cancelled_booking_count': deleted_count,
|
||||
}
|
||||
return Response(response)
|
||||
|
||||
def get_events(self, request, payload):
|
||||
return get_events_from_slots(payload['slots'], request, self.agenda, payload)
|
||||
|
||||
def get_already_booked_events(self, user_external_id):
|
||||
return self.agenda.event_set.filter(booking__user_external_id=user_external_id)
|
||||
|
||||
|
||||
events_fillslots = EventsFillslots.as_view()
|
||||
|
||||
|
||||
class MultipleAgendasEventsFillslots(EventsFillslots):
|
||||
def post(self, request):
|
||||
self.agendas = None
|
||||
if 'agendas' not in request.GET:
|
||||
raise APIError(
|
||||
_('Missing agendas list in querystring'),
|
||||
err_class='Missing agendas list in querystring',
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
self.agenda_slugs = [s for s in request.GET.get('agendas', '').split(',') if s]
|
||||
self.agendas = get_objects_from_slugs(self.agenda_slugs, qs=Agenda.objects.filter(kind='events'))
|
||||
return self.fillslots(request)
|
||||
|
||||
def get_events(self, request, payload):
|
||||
events_by_agenda = collections.defaultdict(list)
|
||||
for slot in payload['slots']:
|
||||
agenda, event = slot.split('@')
|
||||
events_by_agenda[agenda].append(event)
|
||||
|
||||
agendas = get_objects_from_slugs(events_by_agenda.keys(), qs=Agenda.objects.filter(kind='events'))
|
||||
agendas_by_slug = {agenda.slug: agenda for agenda in agendas}
|
||||
|
||||
if not set(agendas_by_slug).issubset(self.agenda_slugs):
|
||||
extra_agendas = set(agendas_by_slug) - set(self.agenda_slugs)
|
||||
extra_agendas = ','.join(extra_agendas)
|
||||
raise APIError(
|
||||
_('Some events belong to agendas that are not present in querystring: %s' % extra_agendas),
|
||||
err_class='Some events belong to agendas that are not present in querystring: %s'
|
||||
% extra_agendas,
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
events = Event.objects.none()
|
||||
for agenda_slug, event_slugs in events_by_agenda.items():
|
||||
events |= get_events_from_slots(event_slugs, request, agendas_by_slug[agenda_slug], payload)
|
||||
|
||||
return events
|
||||
|
||||
def get_already_booked_events(self, user_external_id):
|
||||
return Event.objects.filter(agenda__in=self.agendas, booking__user_external_id=user_external_id)
|
||||
|
||||
|
||||
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view()
|
||||
|
||||
|
||||
class BookingFilter(filters.FilterSet):
|
||||
agenda = filters.CharFilter(field_name='event__agenda__slug', lookup_expr='exact')
|
||||
category = filters.CharFilter(field_name='event__agenda__category__slug', lookup_expr='exact')
|
||||
|
|
|
@ -2432,3 +2432,78 @@ def test_api_events_fillslots_past_event(app, user):
|
|||
params['events'] = 'all'
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
|
||||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_api_events_fillslots_multiple_agendas(app, user):
|
||||
first_agenda = Agenda.objects.create(label='First agenda', kind='events')
|
||||
Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder')
|
||||
first_event = Event.objects.create(
|
||||
label='Event',
|
||||
start_datetime=now() + datetime.timedelta(days=5),
|
||||
places=2,
|
||||
agenda=first_agenda,
|
||||
)
|
||||
second_agenda = Agenda.objects.create(label='Second agenda', kind='events')
|
||||
Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder')
|
||||
second_event = Event.objects.create(
|
||||
label='Event',
|
||||
start_datetime=now() + datetime.timedelta(days=6),
|
||||
places=2,
|
||||
agenda=second_agenda,
|
||||
)
|
||||
|
||||
agenda_slugs = '%s,%s' % (first_agenda.slug, second_agenda.slug)
|
||||
resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs})
|
||||
event_slugs = ','.join((resp.json['data'][0]['id'], resp.json['data'][1]['id']))
|
||||
assert event_slugs == 'first-agenda@event,second-agenda@event'
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
params = {'user_external_id': 'user_id', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert first_event.booking_set.count() == 1
|
||||
assert second_event.booking_set.count() == 1
|
||||
|
||||
# booking modification
|
||||
params = {'user_external_id': 'user_id', 'slots': 'first-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
assert resp.json['booking_count'] == 0
|
||||
assert resp.json['cancelled_booking_count'] == 1
|
||||
assert first_event.booking_set.count() == 1
|
||||
assert second_event.booking_set.count() == 0
|
||||
|
||||
params = {'user_external_id': 'user_id_2', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert first_event.booking_set.count() == 2
|
||||
assert second_event.booking_set.count() == 1
|
||||
|
||||
params = {'user_external_id': 'user_id_3', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'some events are full: Event'
|
||||
|
||||
# invalid agenda slugs
|
||||
resp = app.post_json(
|
||||
'/api/agendas/events/fillslots/?agendas=first-agenda,xxx,yyy', params=params, status=400
|
||||
)
|
||||
assert resp.json['err_desc'] == 'invalid slugs: xxx, yyy'
|
||||
|
||||
params = {'user_external_id': 'user_id_3', 'slots': 'first-agenda@event,xxx@event,yyy@event'}
|
||||
resp = app.post_json(
|
||||
'/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400
|
||||
)
|
||||
assert resp.json['err_desc'] == 'invalid slugs: xxx, yyy'
|
||||
|
||||
# missing agendas parameter
|
||||
resp = app.post_json('/api/agendas/events/fillslots/', params=params, status=400)
|
||||
assert resp.json['err_desc'] == 'Missing agendas list in querystring'
|
||||
|
||||
# valid agendas parameter and event slugs, but mismatch between the two
|
||||
params = {'user_external_id': 'user_id_3', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400)
|
||||
assert (
|
||||
resp.json['err_desc']
|
||||
== 'Some events belong to agendas that are not present in querystring: second-agenda'
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue