From 9a62bb1112c91e7013e8a209d357d8229a23921d Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 29 Jul 2021 14:42:14 +0200 Subject: [PATCH] api: allow changing multiple event bookings (#55368) --- chrono/api/views.py | 21 ++++++++++++++++++++- tests/api/test_fillslot.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/chrono/api/views.py b/chrono/api/views.py index 58f18ee..1987539 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -1646,6 +1646,8 @@ class EventFillslots(APIView): def post(self, request, agenda_identifier): agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events') + start_datetime, end_datetime = get_start_and_end_datetime_from_request(request) + serializer = self.serializer_class(data=request.data, partial=True) if not serializer.is_valid(): raise APIError( @@ -1655,10 +1657,20 @@ class EventFillslots(APIView): http_status=status.HTTP_400_BAD_REQUEST, ) payload = serializer.validated_data + user_external_id = payload['user_external_id'] events = get_events_from_slots(payload['slots'], request, agenda, payload) events = Event.annotate_queryset(events) + already_booked_events = agenda.event_set.filter(booking__user_external_id=user_external_id) + if start_datetime: + already_booked_events = already_booked_events.filter(start_datetime__gte=start_datetime) + if end_datetime: + already_booked_events = already_booked_events.filter(start_datetime__lt=end_datetime) + + events_to_unbook = list(already_booked_events.exclude(pk__in=events).values_list('pk', flat=True)) + events = events.exclude(booking__user_external_id=user_external_id) + full_events = [str(event) for event in events.filter(full=True)] if full_events: raise APIError( @@ -1675,9 +1687,15 @@ class EventFillslots(APIView): extra_data = {k: v for k, v in request.data.items() if k not in payload} 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]) + ) with transaction.atomic(): + deleted_count = Booking.objects.filter( + user_external_id=user_external_id, event__in=events_to_unbook + ).delete()[0] Booking.objects.bulk_create(bookings) - events.update( + events_to_update.update( full=Q(booked_places_count__gte=F('places'), waiting_list_places=0) | Q(waiting_list_places__gt=0, waiting_list_count__gte=F('waiting_list_places')), almost_full=Q(booked_places_count__gte=0.9 * F('places')), @@ -1686,6 +1704,7 @@ class EventFillslots(APIView): response = { 'err': 0, 'booking_count': len(bookings), + 'cancelled_booking_count': deleted_count, } return Response(response) diff --git a/tests/api/test_fillslot.py b/tests/api/test_fillslot.py index a189214..60b34b2 100644 --- a/tests/api/test_fillslot.py +++ b/tests/api/test_fillslot.py @@ -2237,6 +2237,9 @@ def test_api_event_fillslots(app, user, freezer): second_event = Event.objects.create( label='Event 2', start_datetime=now() + datetime.timedelta(days=2), places=2, agenda=agenda ) + third_event = Event.objects.create( + label='Event 3', start_datetime=now() + datetime.timedelta(days=3), places=2, agenda=agenda + ) app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agenda/%s/event_fillslots/' % agenda.slug @@ -2247,9 +2250,11 @@ def test_api_event_fillslots(app, user, freezer): events = Event.annotate_queryset(Event.objects.all()) assert events.filter(booked_places_count=1).count() == 2 + params['user_external_id'] = 'user_id_2' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 + params['user_external_id'] = 'user_id_3' resp = app.post_json(fillslots_url, params=params) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'some events are full: Event 2' @@ -2258,3 +2263,34 @@ def test_api_event_fillslots(app, user, freezer): resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 1 assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 1 + + # change bookings + params = {'user_external_id': 'user_id', 'slots': 'event-2,event-3'} + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 1 + assert resp.json['cancelled_booking_count'] == 1 + + user_bookings = Booking.objects.filter(user_external_id='user_id') + assert {b.event.slug for b in user_bookings} == {'event-2', 'event-3'} + assert event.booking_set.count() == 2 + assert second_event.booking_set.count() == 2 + assert third_event.booking_set.count() == 1 + + # increase waiting_list_places to make "Event" bookable again + event.waiting_list_places = 2 + event.save() + + # specify time range so that "Event 3" is not cancelled + params['slots'] = 'event,event-2' + resp = app.post_json(fillslots_url + '?date_start=2021-09-06&date_end=2021-09-09', params=params) + assert resp.json['booking_count'] == 1 + assert resp.json['cancelled_booking_count'] == 0 + + user_bookings = Booking.objects.filter(user_external_id='user_id') + assert {b.event.slug for b in user_bookings} == {'event', 'event-2', 'event-3'} + assert event.booking_set.count() == 3 + assert second_event.booking_set.count() == 2 + assert third_event.booking_set.count() == 1 + + # new event booking went in waiting list despite free slots on main list + assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 2 -- 2.20.1