From f9e158feb36bbba9cb4f3829863a8ca6185e64b4 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 1 Dec 2021 11:42:29 +0100 Subject: [PATCH 4/4] api: add support for subscriptions in recurring fillslots (#58446) --- chrono/api/views.py | 42 ++++++++++------- tests/api/test_fillslot.py | 93 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/chrono/api/views.py b/chrono/api/views.py index 87f9dacb..404ff2a1 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -674,14 +674,6 @@ def get_start_and_end_datetime_from_request(request): return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end') -def get_agendas_from_request(request): - serializer = serializers.AgendaSlugsSerializer(data=request.query_params) - if not serializer.is_valid(): - raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) - - return serializer.validated_data.get('agendas') - - def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_list=False, color=None): return Booking( event_id=event.pk, @@ -1558,25 +1550,41 @@ class RecurringFillslots(APIView): if not start_datetime or start_datetime < now(): start_datetime = now() - agenda_slugs = get_agendas_from_request(request) - agendas = get_objects_from_slugs(agenda_slugs, qs=Agenda.objects.filter(kind='events')) + serializer = serializers.AgendaOrSubscribedSlugsSerializer( + data=request.query_params, context={'user_external_id': request.data.get('user_external_id')} + ) + if not serializer.is_valid(): + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) + data = serializer.validated_data - context = {'allowed_agenda_slugs': agenda_slugs, 'agendas': Agenda.prefetch_recurring_events(agendas)} + context = { + 'allowed_agenda_slugs': data['agenda_slugs'], + 'agendas': Agenda.prefetch_recurring_events(data['agendas']), + } serializer = self.serializer_class(data=request.data, partial=True, context=context) if not serializer.is_valid(): raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data user_external_id = payload['user_external_id'] - agendas = Agenda.prefetch_events_and_exceptions(agendas, user_external_id=user_external_id) + agendas = Agenda.prefetch_events_and_exceptions(data['agendas'], user_external_id=user_external_id) event_filter = Q() for agenda_slug, days_by_event in payload['slots'].items(): for event_slug, days in days_by_event.items(): - event_filter |= Q( - agenda__slug=agenda_slug, - primary_event__slug=event_slug, - start_datetime__week_day__in=days, - ) + lookups = { + 'agenda__slug': agenda_slug, + 'primary_event__slug': event_slug, + 'start_datetime__week_day__in': days, + } + if 'subscribed' in request.query_params: + lookups.update( + { + 'agenda__subscriptions__user_external_id': user_external_id, + 'agenda__subscriptions__date_start__lt': F('start_datetime'), + 'agenda__subscriptions__date_end__gt': F('start_datetime'), + } + ) + event_filter |= Q(**lookups) events_to_book = Event.objects.filter(event_filter) if event_filter else Event.objects.none() events_to_book = events_to_book.filter(start_datetime__gte=start_datetime, cancelled=False) diff --git a/tests/api/test_fillslot.py b/tests/api/test_fillslot.py index ed248452..74b57fc4 100644 --- a/tests/api/test_fillslot.py +++ b/tests/api/test_fillslot.py @@ -2360,6 +2360,99 @@ def test_recurring_events_api_fillslots_change_bookings(app, user, freezer): assert Booking.objects.filter(user_external_id='user_id', event=normal_event).count() == 1 +@pytest.mark.freeze_time('2021-09-06 12:00') +def test_recurring_events_api_fillslots_subscribed(app, user): + category = Category.objects.create(label='Category A') + first_agenda = Agenda.objects.create(label='First agenda', kind='events', category=category) + Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder') + category = Category.objects.create(label='Category B') + second_agenda = Agenda.objects.create(label='Second agenda', kind='events', category=category) + Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder') + event = Event.objects.create( + slug='event', + start_datetime=now(), + recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday + places=2, + waiting_list_places=1, + agenda=first_agenda, + recurrence_end_date=now() + datetime.timedelta(days=364), + ) + event.create_all_recurrences() + sunday_event = Event.objects.create( + slug='sunday-event', + start_datetime=now(), + recurrence_days=[6], + places=2, + waiting_list_places=1, + agenda=second_agenda, + recurrence_end_date=now() + datetime.timedelta(days=364), + ) + sunday_event.create_all_recurrences() + + Subscription.objects.create( + agenda=first_agenda, + user_external_id='xxx', + date_start=now() + datetime.timedelta(days=16), # Wednesday 22/09 + date_end=now() + datetime.timedelta(days=44), # Wednesday 20/10 + ) + + app.authorization = ('Basic', ('john.doe', 'password')) + params = {'user_external_id': 'xxx'} + # book Monday and Thursday of first event, in subscription range + params['slots'] = 'first-agenda@event:0,first-agenda@event:3' + resp = app.post_json('/api/agendas/recurring-events/fillslots/?subscribed=category-a', params=params) + assert resp.json['booking_count'] == 8 + assert Booking.objects.count() == 8 + assert Booking.objects.filter(event__primary_event=event).count() == 8 + assert Booking.objects.first().event.start_datetime.strftime('%d/%m') == '23/09' + assert Booking.objects.last().event.start_datetime.strftime('%d/%m') == '18/10' + + # wrong category + resp = app.post_json( + '/api/agendas/recurring-events/fillslots/?subscribed=category-b', params=params, status=400 + ) + + # not subscribed category + params['slots'] = 'second-agenda@sunday-event:6' + resp = app.post_json( + '/api/agendas/recurring-events/fillslots/?subscribed=category-b', params=params, status=400 + ) + + # update bookings + Subscription.objects.create( + agenda=second_agenda, + user_external_id='xxx', + date_start=now() + datetime.timedelta(days=100), # Wednesday 15/12 + date_end=now() + datetime.timedelta(days=150), # Thursday 03/02 + ) + params['slots'] = 'first-agenda@event:1,second-agenda@sunday-event:6' + resp = app.post_json('/api/agendas/recurring-events/fillslots/?subscribed=all', params=params) + assert resp.json['booking_count'] == 11 + assert resp.json['cancelled_booking_count'] == 8 + assert Booking.objects.count() == 11 + booked_events_first_agenda = Event.objects.filter(primary_event=event, booking__isnull=False) + assert [ + x.strftime('%d/%m/%Y') for x in booked_events_first_agenda.values_list('start_datetime', flat=True) + ] == ['28/09/2021', '05/10/2021', '12/10/2021', '19/10/2021'] + booked_events_second_agenda = Event.objects.filter(primary_event=sunday_event, booking__isnull=False) + assert [ + x.strftime('%d/%m/%Y') for x in booked_events_second_agenda.values_list('start_datetime', flat=True) + ] == ['19/12/2021', '26/12/2021', '02/01/2022', '09/01/2022', '16/01/2022', '23/01/2022', '30/01/2022'] + + # other user + Subscription.objects.create( + agenda=second_agenda, + user_external_id='yyy', + date_start=now(), + date_end=now() + datetime.timedelta(days=10), + ) + params = {'user_external_id': 'yyy', 'slots': 'second-agenda@sunday-event:6'} + resp = app.post_json('/api/agendas/recurring-events/fillslots/?subscribed=category-b', params=params) + assert resp.json['booking_count'] == 1 + assert Booking.objects.count() == 12 + assert Booking.objects.filter(user_external_id='yyy').count() == 1 + + @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_multiple_agendas(app, user): agenda = Agenda.objects.create(label='First Agenda', kind='events') -- 2.30.2