api: add endpoint to book multiple events independently (#55367)
This commit is contained in:
parent
3936f9450a
commit
a26183e5fa
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue