diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 79ee1a87..c2135df3 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -475,6 +475,12 @@ class Booking(models.Model): self.secondary_booking_set.update(in_waiting_list=False) self.save() + def suspend(self): + self.in_waiting_list = True + with transaction.atomic(): + self.secondary_booking_set.update(in_waiting_list=True) + self.save() + def get_ics(self, request=None): ics = vobject.iCalendar() ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik' diff --git a/chrono/api/urls.py b/chrono/api/urls.py index 0fd391cf..119210a9 100644 --- a/chrono/api/urls.py +++ b/chrono/api/urls.py @@ -48,5 +48,6 @@ urlpatterns = [ url(r'^booking/(?P\w+)/$', views.booking), url(r'^booking/(?P\w+)/cancel/$', views.cancel_booking, name='api-cancel-booking'), url(r'^booking/(?P\w+)/accept/$', views.accept_booking, name='api-accept-booking'), + url(r'^booking/(?P\w+)/suspend/$', views.suspend_booking, name='api-suspend-booking'), url(r'^booking/(?P\w+)/ics/$', views.booking_ics, name='api-booking-ics'), ] diff --git a/chrono/api/views.py b/chrono/api/views.py index 2eaec494..6a664c18 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -658,6 +658,10 @@ class Fillslots(APIView): response['api']['accept_url'] = request.build_absolute_uri( reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.id}) ) + else: + response['api']['suspend_url'] = request.build_absolute_uri( + reverse('api-suspend-booking', kwargs={'booking_pk': primary_booking.pk}) + ) if agenda.kind == 'meetings': response['end_datetime'] = format_response_datetime(events[-1].end_datetime) response['duration'] = (events[-1].end_datetime - events[-1].start_datetime).seconds // 60 @@ -776,6 +780,40 @@ class AcceptBooking(APIView): accept_booking = AcceptBooking.as_view() +class SuspendBooking(APIView): + ''' + Suspend a accepted booking. + + It will return error codes if the booking was cancelled before (code 1) and + if the bookingis already in waiting list (code 2). + ''' + + permission_classes = (permissions.IsAuthenticated,) + + def post(self, request, booking_pk=None, format=None): + booking = get_object_or_404(Booking, pk=booking_pk) + if booking.cancellation_datetime: + response = { + 'err': 1, + 'err_class': 'booking is cancelled', + 'err_desc': _('booking is cancelled'), + } + return Response(response) + if booking.in_waiting_list: + response = { + 'err': 2, + 'err_class': 'booking is already in waiting list', + 'err_desc': _('booking is already in waiting list'), + } + return Response(response) + booking.suspend() + response = {'err': 0, 'booking_id': booking.pk} + return Response(response) + + +suspend_booking = SuspendBooking.as_view() + + class SlotStatus(APIView): permission_classes = (permissions.IsAuthenticated,) diff --git a/tests/test_api.py b/tests/test_api.py index 4155fb9a..4555ed7c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -410,8 +410,10 @@ def test_booking_api(app, some_data, user): Booking.objects.get(id=resp.json['booking_id']) assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S') assert 'accept_url' not in resp.json['api'] + assert 'suspend_url' in resp.json['api'] assert 'cancel_url' in resp.json['api'] assert 'ics_url' in resp.json['api'] + assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc assert urlparse.urlparse(resp.json['api']['ics_url']).netloc assert Booking.objects.count() == 1 @@ -606,7 +608,9 @@ def test_booking_api_fillslots(app, some_data, user): Booking.objects.get(id=primary_booking_id) assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S') assert 'accept_url' not in resp.json['api'] + assert 'suspend_url' in resp.json['api'] assert 'cancel_url' in resp.json['api'] + assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc assert Booking.objects.count() == 3 # these 3 bookings are related, the first is the primary one @@ -1201,6 +1205,7 @@ def test_waiting_list_booking(app, some_data, user): assert resp.json['err'] == 0 assert resp.json['in_waiting_list'] is True assert 'accept_url' in resp.json['api'] + assert 'suspend_url' not in resp.json['api'] assert 'cancel_url' in resp.json['api'] assert 'ics_url' in resp.json['api'] assert urlparse.urlparse(resp.json['api']['accept_url']).netloc @@ -1263,6 +1268,36 @@ def test_accept_booking(app, some_data, user): assert Booking.objects.filter(in_waiting_list=False).count() == 0 +def test_suspend_booking(app, some_data, user): + agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id + event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0] + event.waiting_list_places = 5 + event.save() + + # create a booking not on the waiting list + booking = Booking.objects.create(event=event, in_waiting_list=False) + assert booking.in_waiting_list is False + + app.authorization = ('Basic', ('john.doe', 'password')) + resp = app.post('/api/booking/%s/suspend/' % booking.pk) + booking.refresh_from_db() + assert booking.in_waiting_list is True + + # suspend a booking that doesn't exist + resp = app.post('/api/booking/0/suspend/', status=404) + + # suspend a booking that is in the waiting list + resp = app.post('/api/booking/%s/suspend/' % booking.pk, status=200) + assert resp.json['err'] == 2 + + # suspend a booking that was cancelled before + booking.in_waiting_list = False + booking.cancel() + resp = app.post('/api/booking/%s/suspend/' % booking.pk, status=200) + assert resp.json['err'] == 1 + assert booking.in_waiting_list is False + + def test_multiple_booking_api(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]