0002-api-endpoint-to-resize-a-booking-40039.patch
chrono/agendas/models.py | ||
---|---|---|
507 | 507 |
ics.add(vevent) |
508 | 508 |
return ics.serialize() |
509 | 509 | |
510 |
def clone(self, in_waiting_list=False, primary_booking=None, save=True): |
|
511 |
new_booking = Booking( |
|
512 |
primary_booking=primary_booking, |
|
513 |
event_id=self.event_id, |
|
514 |
in_waiting_list=in_waiting_list, |
|
515 |
label=self.label, |
|
516 |
user_name=self.user_name, |
|
517 |
backoffice_url=self.backoffice_url, |
|
518 |
user_display_label=self.user_display_label, |
|
519 |
extra_data=self.extra_data, |
|
520 |
) |
|
521 |
if save: |
|
522 |
new_booking.save() |
|
523 |
return new_booking |
|
524 | ||
510 | 525 | |
511 | 526 |
class Desk(models.Model): |
512 | 527 |
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE) |
chrono/api/urls.py | ||
---|---|---|
49 | 49 |
url(r'^booking/(?P<booking_pk>\w+)/cancel/$', views.cancel_booking, name='api-cancel-booking'), |
50 | 50 |
url(r'^booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking, name='api-accept-booking'), |
51 | 51 |
url(r'^booking/(?P<booking_pk>\w+)/suspend/$', views.suspend_booking, name='api-suspend-booking'), |
52 |
url(r'^booking/(?P<booking_pk>\w+)/resize/$', views.resize_booking, name='api-resize-booking'), |
|
52 | 53 |
url(r'^booking/(?P<booking_pk>\w+)/ics/$', views.booking_ics, name='api-booking-ics'), |
53 | 54 |
] |
chrono/api/views.py | ||
---|---|---|
845 | 845 |
suspend_booking = SuspendBooking.as_view() |
846 | 846 | |
847 | 847 | |
848 |
class ResizeSerializer(serializers.Serializer): |
|
849 |
count = serializers.IntegerField(min_value=1) |
|
850 | ||
851 | ||
852 |
class ResizeBooking(APIView): |
|
853 |
''' |
|
854 |
Resize a booking. |
|
855 | ||
856 |
It will return error codes if the booking was cancelled before (code 1) |
|
857 |
if the booking is not primary (code 2) |
|
858 |
if the event is sold out (code 3) or |
|
859 |
if the booking is on multi events (code 4). |
|
860 |
''' |
|
861 | ||
862 |
permission_classes = (permissions.IsAuthenticated,) |
|
863 |
serializer_class = ResizeSerializer |
|
864 | ||
865 |
def post(self, request, booking_pk=None, format=None): |
|
866 |
serializer = self.serializer_class(data=request.data) |
|
867 |
if not serializer.is_valid(): |
|
868 |
return Response( |
|
869 |
{ |
|
870 |
'err': 1, |
|
871 |
'err_class': 'invalid payload', |
|
872 |
'err_desc': _('invalid payload'), |
|
873 |
'errors': serializer.errors, |
|
874 |
}, |
|
875 |
status=status.HTTP_400_BAD_REQUEST, |
|
876 |
) |
|
877 |
payload = serializer.validated_data |
|
878 | ||
879 |
booking = get_object_or_404(Booking, pk=booking_pk) |
|
880 |
event = booking.event |
|
881 |
if booking.cancellation_datetime: |
|
882 |
response = { |
|
883 |
'err': 1, |
|
884 |
'err_class': 'booking is cancelled', |
|
885 |
'err_desc': _('booking is cancelled'), |
|
886 |
} |
|
887 |
return Response(response) |
|
888 |
if booking.primary_booking is not None: |
|
889 |
response = { |
|
890 |
'err': 2, |
|
891 |
'err_class': 'secondary booking', |
|
892 |
'err_desc': _('secondary booking'), |
|
893 |
} |
|
894 |
return Response(response) |
|
895 |
event_ids = set([event.pk]) |
|
896 |
secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime') |
|
897 |
for secondary in secondary_bookings: |
|
898 |
event_ids.add(secondary.event_id) |
|
899 |
if len(event_ids) > 1: |
|
900 |
response = { |
|
901 |
'err': 4, |
|
902 |
'err_class': 'can not resize multi event booking', |
|
903 |
'err_desc': _('can not resize multi event booking'), |
|
904 |
} |
|
905 |
return Response(response) |
|
906 | ||
907 |
count = payload['count'] |
|
908 |
booked_places = event.waiting_list_places and event.waiting_list or event.booked_places |
|
909 |
if booked_places < count: |
|
910 |
if ( |
|
911 |
event.waiting_list |
|
912 |
and count > event.waiting_list_places |
|
913 |
or not event.waiting_list |
|
914 |
and count > event.places |
|
915 |
): |
|
916 |
response = { |
|
917 |
'err': 3, |
|
918 |
'err_class': 'sold out', |
|
919 |
'err_desc': _('sold out'), |
|
920 |
} |
|
921 |
return Response(response) |
|
922 | ||
923 |
with transaction.atomic(): |
|
924 |
if booked_places > count: |
|
925 |
# decrease places |
|
926 |
for secondary in secondary_bookings[: booked_places - count]: |
|
927 |
secondary.delete() |
|
928 |
elif booked_places < count: |
|
929 |
# increase places |
|
930 |
bulk_bookings = [] |
|
931 |
for i in range(0, count - booked_places): |
|
932 |
bulk_bookings.append( |
|
933 |
booking.clone( |
|
934 |
in_waiting_list=bool(event.waiting_list_places and event.waiting_list), |
|
935 |
primary_booking=booking, |
|
936 |
save=False, |
|
937 |
) |
|
938 |
) |
|
939 |
Booking.objects.bulk_create(bulk_bookings) |
|
940 | ||
941 |
response = {'err': 0, 'booking_id': booking.pk} |
|
942 |
return Response(response) |
|
943 | ||
944 | ||
945 |
resize_booking = ResizeBooking.as_view() |
|
946 | ||
947 | ||
848 | 948 |
class SlotStatus(APIView): |
849 | 949 |
permission_classes = (permissions.IsAuthenticated,) |
850 | 950 |
tests/test_api.py | ||
---|---|---|
1419 | 1419 |
assert primary.in_waiting_list is False |
1420 | 1420 | |
1421 | 1421 | |
1422 |
def test_resize_booking(app, some_data, user): |
|
1423 |
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id |
|
1424 |
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0] |
|
1425 |
event.places = 5 |
|
1426 |
event.waiting_list_places = 5 |
|
1427 |
event.save() |
|
1428 |
event2 = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[1] |
|
1429 | ||
1430 |
# create a booking not on the waiting list |
|
1431 |
primary = Booking.objects.create(event=event, in_waiting_list=False) |
|
1432 |
secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary) |
|
1433 | ||
1434 |
assert Booking.objects.filter(in_waiting_list=False).count() == 2 |
|
1435 | ||
1436 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
1437 | ||
1438 |
resp = app.post('/api/booking/%s/resize/' % secondary.pk, params={'count': 42}) |
|
1439 |
assert resp.json['err'] == 2 |
|
1440 |
assert resp.json['reason'] == 'secondary booking' # legacy |
|
1441 |
assert resp.json['err_class'] == 'secondary booking' |
|
1442 |
assert resp.json['err_desc'] == 'secondary booking' |
|
1443 | ||
1444 |
# resize a booking that doesn't exist |
|
1445 |
resp = app.post('/api/booking/0/resize/', params={'count': 42}, status=404) |
|
1446 | ||
1447 |
# decrease a booking to 0 |
|
1448 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 0}, status=400) |
|
1449 |
assert resp.json['err'] == 1 |
|
1450 | ||
1451 |
# decrease a booking not in waiting list |
|
1452 |
assert Booking.objects.filter(in_waiting_list=False).count() == 2 |
|
1453 |
assert Booking.objects.filter().count() == 2 |
|
1454 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1}) |
|
1455 |
assert resp.json['err'] == 0 |
|
1456 |
assert Booking.objects.filter(in_waiting_list=False).count() == 1 |
|
1457 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1458 |
assert Booking.objects.filter().count() == 1 |
|
1459 | ||
1460 |
# no changes |
|
1461 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1}) |
|
1462 |
assert resp.json['err'] == 0 |
|
1463 |
assert Booking.objects.filter(in_waiting_list=False).count() == 1 |
|
1464 |
assert Booking.objects.filter().count() == 1 |
|
1465 | ||
1466 |
# increase a booking not in waiting list |
|
1467 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2}) |
|
1468 |
assert resp.json['err'] == 0 |
|
1469 |
assert Booking.objects.filter(in_waiting_list=False).count() == 2 |
|
1470 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1471 |
assert Booking.objects.filter().count() == 2 |
|
1472 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6}) |
|
1473 |
assert resp.json['err'] == 3 |
|
1474 |
assert resp.json['reason'] == 'sold out' # legacy |
|
1475 |
assert resp.json['err_class'] == 'sold out' |
|
1476 |
assert resp.json['err_desc'] == 'sold out' |
|
1477 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5}) |
|
1478 |
assert resp.json['err'] == 0 |
|
1479 |
assert Booking.objects.filter(in_waiting_list=False).count() == 5 |
|
1480 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1481 |
assert Booking.objects.filter().count() == 5 |
|
1482 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6}) |
|
1483 |
assert resp.json['err'] == 3 |
|
1484 |
assert resp.json['reason'] == 'sold out' # legacy |
|
1485 |
assert resp.json['err_class'] == 'sold out' |
|
1486 |
assert resp.json['err_desc'] == 'sold out' |
|
1487 | ||
1488 |
# decrease a booking in waiting list |
|
1489 |
primary.suspend() |
|
1490 |
assert Booking.objects.filter(in_waiting_list=True).count() == 5 |
|
1491 |
assert Booking.objects.filter().count() == 5 |
|
1492 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1}) |
|
1493 |
assert resp.json['err'] == 0 |
|
1494 |
assert Booking.objects.filter(in_waiting_list=True).count() == 1 |
|
1495 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1496 |
assert Booking.objects.filter().count() == 1 |
|
1497 | ||
1498 |
# no changes |
|
1499 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1}) |
|
1500 |
assert resp.json['err'] == 0 |
|
1501 |
assert Booking.objects.filter(in_waiting_list=True).count() == 1 |
|
1502 |
assert Booking.objects.filter().count() == 1 |
|
1503 | ||
1504 |
# increase a booking in waiting list |
|
1505 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2}) |
|
1506 |
assert resp.json['err'] == 0 |
|
1507 |
assert Booking.objects.filter(in_waiting_list=True).count() == 2 |
|
1508 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1509 |
assert Booking.objects.filter().count() == 2 |
|
1510 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6}) |
|
1511 |
assert resp.json['err'] == 3 |
|
1512 |
assert resp.json['reason'] == 'sold out' # legacy |
|
1513 |
assert resp.json['err_class'] == 'sold out' |
|
1514 |
assert resp.json['err_desc'] == 'sold out' |
|
1515 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5}) |
|
1516 |
assert resp.json['err'] == 0 |
|
1517 |
assert Booking.objects.filter(in_waiting_list=True).count() == 5 |
|
1518 |
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1 |
|
1519 |
assert Booking.objects.filter().count() == 5 |
|
1520 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6}) |
|
1521 |
assert resp.json['err'] == 3 |
|
1522 |
assert resp.json['reason'] == 'sold out' # legacy |
|
1523 |
assert resp.json['err_class'] == 'sold out' |
|
1524 |
assert resp.json['err_desc'] == 'sold out' |
|
1525 | ||
1526 |
# resize a booking that is on multi events |
|
1527 |
secondary = Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary) |
|
1528 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42}) |
|
1529 |
assert resp.json['err'] == 4 |
|
1530 |
assert resp.json['reason'] == 'can not resize multi event booking' # legacy |
|
1531 |
assert resp.json['err_class'] == 'can not resize multi event booking' |
|
1532 |
assert resp.json['err_desc'] == 'can not resize multi event booking' |
|
1533 |
# resize a booking that was cancelled before |
|
1534 |
primary.cancel() |
|
1535 |
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42}) |
|
1536 |
assert resp.json['err'] == 1 |
|
1537 | ||
1538 | ||
1422 | 1539 |
def test_multiple_booking_api(app, some_data, user): |
1423 | 1540 |
agenda = Agenda.objects.filter(label=u'Foo bar')[0] |
1424 | 1541 |
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] |
1425 |
- |