From 90dfb10afa828e8659ca6b8a4915306fb59dadb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Wed, 6 Nov 2019 17:00:12 +0100 Subject: [PATCH] api: get event status and fillslot with event pk or slug (#15726) --- chrono/api/urls.py | 4 ++-- chrono/api/views.py | 37 ++++++++++++++++++++++++++----------- tests/test_api.py | 20 +++++++++++++++----- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/chrono/api/urls.py b/chrono/api/urls.py index d595d41d..79ee179f 100644 --- a/chrono/api/urls.py +++ b/chrono/api/urls.py @@ -23,11 +23,11 @@ urlpatterns = [ url(r'agenda/(?P[\w-]+)/$', views.agenda_detail), url(r'agenda/(?P[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'), - url(r'agenda/(?P[\w-]+)/fillslot/(?P[\w:-]+)/$', + url(r'agenda/(?P[\w-]+)/fillslot/(?P[\w:-]+)/$', views.fillslot, name='api-fillslot'), url(r'agenda/(?P[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'), - url(r'agenda/(?P[\w-]+)/status/(?P\d+)/$', views.slot_status, + url(r'agenda/(?P[\w-]+)/status/(?P[\w-]+)/$', views.slot_status, name='api-event-status'), url(r'agenda/meetings/(?P[\w-]+)/datetimes/$', diff --git a/chrono/api/views.py b/chrono/api/views.py index 15cb3640..7465f1cf 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -185,6 +185,7 @@ class Datetimes(APIView): datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0)))) response = {'data': [{'id': x.id, + 'slug': x.slug, 'text': force_text(x), 'datetime': format_response_datetime(x.start_datetime), 'description': x.description, @@ -194,13 +195,13 @@ class Datetimes(APIView): reverse('api-fillslot', kwargs={ 'agenda_identifier': agenda.slug, - 'event_pk': x.id, + 'event_identifier': x.slug, })), 'status_url': request.build_absolute_uri( reverse('api-event-status', kwargs={ 'agenda_identifier': agenda.slug, - 'event_pk': x.id, + 'event_identifier': x.slug, })) }, } for x in entries]} @@ -240,12 +241,12 @@ class MeetingDatetimes(APIView): # create fillslot API URL as a template, to avoid expensive calls # to request.build_absolute_uri() - fake_event_pk = '__event_id__' + fake_event_identifier = '__event_identifier__' fillslot_url = request.build_absolute_uri( reverse('api-fillslot', kwargs={ 'agenda_identifier': agenda.slug, - 'event_pk': fake_event_pk, + 'event_identifier': fake_event_identifier, })) response = {'data': [{'id': x.id, @@ -253,7 +254,7 @@ class MeetingDatetimes(APIView): 'text': force_text(x), 'disabled': bool(x.full), 'api': { - 'fillslot_url': fillslot_url.replace(fake_event_pk, str(x.id)), + 'fillslot_url': fillslot_url.replace(fake_event_identifier, str(x.id)), }, } for x in slots]} return Response(response) @@ -333,7 +334,7 @@ class Fillslots(APIView): permission_classes = (permissions.IsAuthenticated,) serializer_class = SlotsSerializer - def post(self, request, agenda_identifier=None, event_pk=None, format=None): + def post(self, request, agenda_identifier=None, event_identifier=None, format=None): return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format) @@ -467,7 +468,10 @@ class Fillslots(APIView): full=False, places=1, desk=available_desk)) else: - events = Event.objects.filter(id__in=slots).order_by('start_datetime') + try: + events = Event.objects.filter(id__in=[int(s) for s in slots]).order_by('start_datetime') + except ValueError: + events = Event.objects.filter(slug__in=slots).order_by('start_datetime') # search free places. Switch to waiting list if necessary. in_waiting_list = False @@ -538,10 +542,10 @@ fillslots = Fillslots.as_view() class Fillslot(Fillslots): serializer_class = SlotSerializer - def post(self, request, agenda_identifier=None, event_pk=None, format=None): + def post(self, request, agenda_identifier=None, event_identifier=None, format=None): return self.fillslot(request=request, agenda_identifier=agenda_identifier, - slots=[event_pk], # fill a "list on one slot" + slots=[event_identifier], # fill a "list on one slot" format=format) fillslot = Fillslot.as_view() @@ -610,8 +614,18 @@ accept_booking = AcceptBooking.as_view() class SlotStatus(APIView): permission_classes = (permissions.IsAuthenticated,) - def get(self, request, agenda_identifier=None, event_pk=None, format=None): - event = get_object_or_404(Event, id=event_pk) + def get_object(self, event_identifier): + try: + return Event.objects.get(slug=event_identifier) + except Event.DoesNotExist: + try: + # legacy access by event id + return Event.objects.get(pk=int(event_identifier)) + except (ValueError, Event.DoesNotExist): + raise Http404() + + def get(self, request, agenda_identifier=None, event_identifier=None, format=None): + event = self.get_object(event_identifier) response = { 'err': 0, 'places': { @@ -626,6 +640,7 @@ class SlotStatus(APIView): response['places']['waiting_list_available'] = (event.waiting_list_places - event.waiting_list) return Response(response) + slot_status = SlotStatus.as_view() diff --git a/tests/test_api.py b/tests/test_api.py index 78e6d409..973ef75a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -225,7 +225,7 @@ def test_datetime_api_status_url(app, some_data): agenda = Agenda.objects.get(label=u'Foo bar2') resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) for datum in resp.json['data']: - assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % (agenda.slug, datum['id']) + assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % (agenda.slug, datum['slug']) def test_datetimes_api_meetings_agenda(app, meetings_agenda): meeting_type = MeetingType.objects.get(agenda=meetings_agenda) @@ -333,7 +333,7 @@ def test_booking_api(app, some_data, user): for agenda_key in (agenda.slug, agenda.id): # acces datetimes via agenda slug or id (legacy) resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda_key) event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api']['fillslot_url'] - assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id) + assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug) app.authorization = ('Basic', ('john.doe', 'password')) resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id)) @@ -346,7 +346,8 @@ def test_booking_api(app, some_data, user): assert urlparse.urlparse(resp.json['api']['ics_url']).netloc assert Booking.objects.count() == 1 - resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id)) + # access by slug + resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.slug)) assert Booking.objects.count() == 2 assert Booking.objects.filter(event__agenda=agenda).count() == 2 @@ -467,6 +468,7 @@ def test_booking_ics(app, some_data, meetings_agenda, user): def test_booking_api_fillslots(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] + events_slugs = [x.slug for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] assert len(events_ids) == 3 event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] # first event @@ -495,7 +497,8 @@ def test_booking_api_fillslots(app, some_data, user): assert bookings[1].primary_booking.id == bookings[0].id == primary_booking_id assert bookings[2].primary_booking.id == bookings[0].id == primary_booking_id - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_ids}) + # access by slug + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_slugs}) primary_booking_id_2 = resp.json['booking_id'] assert Booking.objects.count() == 6 assert Booking.objects.filter(event__agenda=agenda).count() == 6 @@ -915,6 +918,12 @@ def test_status(app, some_data, user): assert resp.json['places']['waiting_list_available'] == 4 assert resp.json['places']['waiting_list_reserved'] == 1 + # access by slug + resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.slug)) + # not found event + resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, 'unknown'), status=404) + + def test_waiting_list_datetimes(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] @@ -1343,7 +1352,8 @@ def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): booking_url = event_data['api']['fillslot_url'] with CaptureQueriesContext(connection) as ctx: app.post(booking_url) - assert len(ctx.captured_queries) == queries_count_fillslot1 + # 2 + idx: because of slug unicity + assert len(ctx.captured_queries) == queries_count_fillslot1 + 2 + idx with CaptureQueriesContext(connection) as ctx: app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)