0003-api-add-endpoint-to-book-multiple-events-independent.patch
chrono/api/urls.py | ||
---|---|---|
33 | 33 |
name='api-fillslot', |
34 | 34 |
), |
35 | 35 |
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'), |
36 |
url( |
|
37 |
r'^agenda/(?P<agenda_identifier>[\w-]+)/event_fillslots/$', |
|
38 |
views.event_fillslots, |
|
39 |
name='api-agenda-event-fillslots', |
|
40 |
), |
|
36 | 41 |
url( |
37 | 42 |
r'^agenda/(?P<agenda_identifier>[\w-]+)/recurring_fillslots/$', |
38 | 43 |
views.recurring_fillslots, |
chrono/api/views.py | ||
---|---|---|
1640 | 1640 |
recurring_fillslots = RecurringFillslots.as_view() |
1641 | 1641 | |
1642 | 1642 | |
1643 |
class EventFillslots(APIView): |
|
1644 |
permission_classes = (permissions.IsAuthenticated,) |
|
1645 |
serializer_class = EventSlotsSerializer |
|
1646 | ||
1647 |
def post(self, request, agenda_identifier): |
|
1648 |
agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events') |
|
1649 |
serializer = self.serializer_class(data=request.data, partial=True) |
|
1650 |
if not serializer.is_valid(): |
|
1651 |
raise APIError( |
|
1652 |
_('invalid payload'), |
|
1653 |
err_class='invalid payload', |
|
1654 |
errors=serializer.errors, |
|
1655 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
1656 |
) |
|
1657 |
payload = serializer.validated_data |
|
1658 | ||
1659 |
events = get_events_from_slots(payload['slots'], request, agenda, payload) |
|
1660 |
events = Event.annotate_queryset(events) |
|
1661 | ||
1662 |
full_events = [str(event) for event in events.filter(full=True)] |
|
1663 |
if full_events: |
|
1664 |
raise APIError( |
|
1665 |
_('some events are full: %s') % ', '.join(full_events), err_class='some events are full' |
|
1666 |
) |
|
1667 | ||
1668 |
events = events.annotate( |
|
1669 |
in_waiting_list=ExpressionWrapper( |
|
1670 |
Q(booked_places_count__gte=F('places')) | Q(waiting_list_count__gt=0), |
|
1671 |
output_field=BooleanField(), |
|
1672 |
) |
|
1673 |
) |
|
1674 |
waiting_list_events = [event for event in events if event.in_waiting_list] |
|
1675 | ||
1676 |
extra_data = {k: v for k, v in request.data.items() if k not in payload} |
|
1677 |
bookings = [make_booking(event, payload, extra_data) for event in events] |
|
1678 | ||
1679 |
with transaction.atomic(): |
|
1680 |
Booking.objects.bulk_create(bookings) |
|
1681 |
events.update( |
|
1682 |
full=Q(booked_places_count__gte=F('places'), waiting_list_places=0) |
|
1683 |
| Q(waiting_list_places__gt=0, waiting_list_count__gte=F('waiting_list_places')), |
|
1684 |
almost_full=Q(booked_places_count__gte=0.9 * F('places')), |
|
1685 |
) |
|
1686 | ||
1687 |
response = { |
|
1688 |
'err': 0, |
|
1689 |
'booking_count': len(bookings), |
|
1690 |
'waiting_list_events': [get_event_detail(request, x, agenda=agenda) for x in waiting_list_events], |
|
1691 |
} |
|
1692 |
return Response(response) |
|
1693 | ||
1694 | ||
1695 |
event_fillslots = EventFillslots.as_view() |
|
1696 | ||
1697 | ||
1643 | 1698 |
class BookingSerializer(serializers.ModelSerializer): |
1644 | 1699 |
user_absence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True) |
1645 | 1700 |
tests/api/test_fillslot.py | ||
---|---|---|
2224 | 2224 |
resp = app.post_json('/api/agenda/%s/recurring_fillslots/' % agenda.slug, params=params) |
2225 | 2225 |
assert resp.json['booking_count'] == 5 |
2226 | 2226 |
assert events.filter(waiting_list_count=2).count() == 5 |
2227 | ||
2228 | ||
2229 |
def test_api_event_fillslots(app, user, freezer): |
|
2230 |
freezer.move_to('2021-09-06 12:00') |
|
2231 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
2232 |
event = Event.objects.create( |
|
2233 |
label='Event', |
|
2234 |
start_datetime=now() + datetime.timedelta(days=1), |
|
2235 |
places=2, |
|
2236 |
waiting_list_places=1, |
|
2237 |
agenda=agenda, |
|
2238 |
) |
|
2239 |
second_event = Event.objects.create( |
|
2240 |
label='Event 2', start_datetime=now() + datetime.timedelta(days=2), places=2, agenda=agenda |
|
2241 |
) |
|
2242 | ||
2243 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
2244 |
fillslots_url = '/api/agenda/%s/event_fillslots/' % agenda.slug |
|
2245 |
params = {'user_external_id': 'user_id', 'slots': 'event,event-2'} |
|
2246 |
resp = app.post_json(fillslots_url, params=params) |
|
2247 |
assert resp.json['booking_count'] == 2 |
|
2248 |
assert len(resp.json['waiting_list_events']) == 0 |
|
2249 | ||
2250 |
events = Event.annotate_queryset(Event.objects.all()) |
|
2251 |
assert events.filter(booked_places_count=1).count() == 2 |
|
2252 | ||
2253 |
resp = app.post_json(fillslots_url, params=params) |
|
2254 |
assert resp.json['booking_count'] == 2 |
|
2255 |
assert len(resp.json['waiting_list_events']) == 0 |
|
2256 | ||
2257 |
resp = app.post_json(fillslots_url, params=params) |
|
2258 |
assert resp.json['err'] == 1 |
|
2259 |
assert resp.json['err_desc'] == 'some events are full: Event 2' |
|
2260 | ||
2261 |
params['slots'] = 'event' |
|
2262 |
resp = app.post_json(fillslots_url, params=params) |
|
2263 |
assert resp.json['booking_count'] == 1 |
|
2264 |
assert resp.json['waiting_list_events'][0]['slug'] == event.slug |
|
2265 |
assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 1 |
|
2227 |
- |