api: add endpoint to book multiple events independently (#55367)

This commit is contained in:
Valentin Deniaud 2021-07-29 11:20:38 +02:00
parent 3936f9450a
commit a26183e5fa
3 changed files with 143 additions and 2 deletions

View File

@ -33,6 +33,11 @@ urlpatterns = [
name='api-fillslot',
),
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'),
url(
r'^agenda/(?P<agenda_identifier>[\w-]+)/events/fillslots/$',
views.events_fillslots,
name='api-agenda-events-fillslots',
),
url(
r'^agenda/(?P<agenda_identifier>[\w-]+)/recurring_fillslots/$',
views.recurring_fillslots,

View File

@ -1163,7 +1163,7 @@ class SlotsSerializer(SlotSerializer):
return attrs
class EventSlotsSerializer(SlotsSerializer):
class EventsSlotsSerializer(SlotsSerializer):
def validate(self, attrs):
super().validate(attrs)
if not attrs.get('user_external_id'):
@ -1551,7 +1551,7 @@ fillslot = Fillslot.as_view()
class RecurringFillslots(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = EventSlotsSerializer
serializer_class = EventsSlotsSerializer
def post(self, request, agenda_identifier):
if not settings.ENABLE_RECURRING_EVENT_BOOKING:
@ -1640,6 +1640,61 @@ class RecurringFillslots(APIView):
recurring_fillslots = RecurringFillslots.as_view()
class EventsFillslots(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = EventsSlotsSerializer
def post(self, request, agenda_identifier):
agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
serializer = self.serializer_class(data=request.data, partial=True)
if not serializer.is_valid():
raise APIError(
_('invalid payload'),
err_class='invalid payload',
errors=serializer.errors,
http_status=status.HTTP_400_BAD_REQUEST,
)
payload = serializer.validated_data
events = get_events_from_slots(payload['slots'], request, agenda, payload)
events = Event.annotate_queryset(events)
full_events = [str(event) for event in events.filter(full=True)]
if full_events:
raise APIError(
_('some events are full: %s') % ', '.join(full_events), err_class='some events are full'
)
events = events.annotate(
in_waiting_list=ExpressionWrapper(
Q(booked_places_count__gte=F('places')) | Q(waiting_list_count__gt=0),
output_field=BooleanField(),
)
)
waiting_list_events = [event for event in events if event.in_waiting_list]
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]
with transaction.atomic():
Booking.objects.bulk_create(bookings)
events.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')),
)
response = {
'err': 0,
'booking_count': len(bookings),
'waiting_list_events': [get_event_detail(request, x, agenda=agenda) for x in waiting_list_events],
}
return Response(response)
events_fillslots = EventsFillslots.as_view()
class BookingSerializer(serializers.ModelSerializer):
user_absence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True)

View File

@ -2224,3 +2224,84 @@ def test_recurring_events_api_fillslots_waiting_list(app, user, freezer):
resp = app.post_json('/api/agenda/%s/recurring_fillslots/' % agenda.slug, params=params)
assert resp.json['booking_count'] == 5
assert events.filter(waiting_list_count=2).count() == 5
@pytest.mark.freeze_time('2021-09-06 12:00')
def test_api_events_fillslots(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=1),
places=2,
waiting_list_places=1,
agenda=agenda,
)
second_event = Event.objects.create(
label='Event 2', start_datetime=now() + datetime.timedelta(days=2), places=2, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
fillslots_url = '/api/agenda/%s/events/fillslots/' % agenda.slug
params = {'user_external_id': 'user_id', 'slots': 'event,event-2'}
resp = app.post_json(fillslots_url, params=params)
assert resp.json['booking_count'] == 2
assert len(resp.json['waiting_list_events']) == 0
events = Event.annotate_queryset(Event.objects.all())
assert events.filter(booked_places_count=1).count() == 2
resp = app.post_json(fillslots_url, params=params)
assert resp.json['booking_count'] == 2
assert len(resp.json['waiting_list_events']) == 0
resp = app.post_json(fillslots_url, params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'some events are full: Event 2'
params['slots'] = 'event'
resp = app.post_json(fillslots_url, params=params)
assert resp.json['booking_count'] == 1
assert resp.json['waiting_list_events'][0]['slug'] == event.slug
assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 1
resp = app.post('/api/agenda/foobar/events/fillslots/', status=404)
resp = app.post('/api/agenda/0/events/fillslots/', status=404)
@pytest.mark.freeze_time('2021-09-06 12:00')
def test_api_events_fillslots_past_event(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
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,
)
params = {'user_external_id': 'user_id', 'slots': ','.join((event1.slug, event2.slug))}
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event not bookable'
params['events'] = 'future'
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event not bookable'
params['events'] = 'past'
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event not bookable'
params['events'] = 'all'
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
assert resp.json['err'] == 0