Projet

Général

Profil

0001-api-add-an-enpoint-to-patch-an-event-57305.patch

Nicolas Roche, 30 septembre 2021 18:39

Télécharger (11 ko)

Voir les différences:

Subject: [PATCH 1/2] api: add an enpoint to patch an event (#57305)

 chrono/api/urls.py      |   5 ++
 chrono/api/views.py     |  85 ++++++++++++++++++++++++
 tests/api/test_event.py | 140 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 230 insertions(+)
chrono/api/urls.py
44 44
        views.recurring_events_list,
45 45
        name='api-agenda-recurring-events',
46 46
    ),
47 47
    url(
48 48
        r'^agenda/(?P<agenda_identifier>[\w-]+)/recurring-events/fillslots/$',
49 49
        views.recurring_fillslots,
50 50
        name='api-recurring-fillslots',
51 51
    ),
52
    url(
53
        r'^agenda/(?P<agenda_identifier>[\w-]+)/event/(?P<event_identifier>[\w:-]+)/$',
54
        views.events,
55
        name='api-event',
56
    ),
52 57
    url(
53 58
        r'^agenda/(?P<agenda_identifier>[\w-]+)/status/(?P<event_identifier>[\w:-]+)/$',
54 59
        views.event_status,
55 60
        name='api-event-status',
56 61
    ),
57 62
    url(
58 63
        r'^agenda/(?P<agenda_identifier>[\w-]+)/bookings/(?P<event_identifier>[\w:-]+)/$',
59 64
        views.event_bookings,
chrono/api/views.py
2159 2159
    def success(self, booking):
2160 2160
        response = {'err': 0, 'booking_id': booking.pk}
2161 2161
        return Response(response)
2162 2162

  
2163 2163

  
2164 2164
resize_booking = ResizeBooking.as_view()
2165 2165

  
2166 2166

  
2167
class Events(APIView):
2168
    permission_classes = (permissions.IsAuthenticated,)
2169
    serializer_class = serializers.EventSerializer
2170

  
2171
    def get_object(self, agenda_identifier, event_identifier):
2172
        try:
2173
            agenda = Agenda.objects.get(slug=agenda_identifier, kind='events')
2174
        except Agenda.DoesNotExist:
2175
            try:
2176
                # legacy access by agenda id
2177
                agenda = Agenda.objects.get(pk=agenda_identifier, kind='events')
2178
            except (ValueError, Agenda.DoesNotExist):
2179
                raise Http404()
2180
        if ':' in event_identifier:
2181
            return get_event_recurrence(agenda, event_identifier)
2182
        try:
2183
            return agenda.event_set.get(slug=event_identifier)
2184
        except Event.DoesNotExist:
2185
            try:
2186
                # legacy access by event id
2187
                return agenda.event_set.get(pk=event_identifier)
2188
            except (ValueError, Event.DoesNotExist):
2189
                raise Http404()
2190

  
2191
    def patch(self, request, agenda_identifier=None, event_identifier=None, format=None):
2192
        event = self.get_object(agenda_identifier, event_identifier)
2193
        serializer = self.serializer_class(event, data=request.data, partial=True)
2194
        if not serializer.is_valid():
2195
            raise APIError(
2196
                _('invalid payload'),
2197
                err_class='invalid payload',
2198
                errors=serializer.errors,
2199
                http_status=status.HTTP_400_BAD_REQUEST,
2200
            )
2201

  
2202
        payload = serializer.validated_data
2203
        protected_fields = ['start_datetime', 'recurrence_days', 'recurrence_week_interval']
2204
        changed_data = []
2205
        for field in serializer.fields.keys():
2206
            if payload.get(field) and payload.get(field) != getattr(event, field):
2207
                changed_data.append(field)
2208

  
2209
        if event.recurrence_days and event.has_recurrences_booked():
2210
            errors = {}
2211
            for field in changed_data:
2212
                if field in protected_fields:
2213
                    errors[field] = [
2214
                        _(
2215
                            'This field cannot be modified because some recurrences have bookings attached to them.'
2216
                        )
2217
                    ]
2218
            if 'recurrence_end_date' in changed_data and event.has_recurrences_booked(
2219
                after=payload['recurrence_end_date']
2220
            ):
2221
                errors['recurrence_end_date'] = [_('Bookings exist after this date.')]
2222
            if errors:
2223
                raise APIError(
2224
                    _('invalid payload'),
2225
                    err_class='invalid payload',
2226
                    errors=errors,
2227
                    http_status=status.HTTP_400_BAD_REQUEST,
2228
                )
2229

  
2230
        with transaction.atomic():
2231
            if any(field for field in changed_data if field in protected_fields):
2232
                event.recurrences.all().delete()
2233
            elif event.recurrence_days:
2234
                protected_fields.append('recurrence_end_date')
2235
                update_fields = {
2236
                    field: value for field, value in payload.items() if field not in protected_fields
2237
                }
2238
                event.recurrences.update(**update_fields)
2239

  
2240
            event = serializer.save()
2241
            if event.recurrence_end_date:
2242
                event.recurrences.filter(start_datetime__gt=event.recurrence_end_date).delete()
2243
                excluded_datetimes = [evt.datetime_slug for evt in event.recurrences.all()]
2244
                event.create_all_recurrences(excluded_datetimes)
2245

  
2246
        return Response({'err': 0, 'data': get_event_detail(request, event)})
2247

  
2248

  
2249
events = Events.as_view()
2250

  
2251

  
2167 2252
class EventStatus(APIView):
2168 2253
    permission_classes = (permissions.IsAuthenticated,)
2169 2254

  
2170 2255
    def get_object(self, agenda_identifier, event_identifier):
2171 2256
        try:
2172 2257
            agenda = Agenda.objects.get(slug=agenda_identifier, kind='events')
2173 2258
        except Agenda.DoesNotExist:
2174 2259
            try:
tests/api/test_event.py
326 326
        '2021-11-20',
327 327
        '2021-11-29',
328 328
        '2021-12-02',
329 329
        '2021-12-04',
330 330
        '2021-12-13',
331 331
        '2021-12-16',
332 332
        '2021-12-18',
333 333
    ]
334

  
335

  
336
def test_update_event(app, user):
337
    api_url = '/api/agenda/%s/event/%s/' % ('nop', 'nop')
338

  
339
    # no authentication
340
    resp = app.patch(api_url, status=401)
341
    assert resp.json['detail'] == 'Authentication credentials were not provided.'
342

  
343
    # wrong password
344
    app.authorization = ('Basic', ('john.doe', 'wrong'))
345
    resp = app.patch(api_url, status=401)
346
    assert resp.json['detail'] == 'Invalid username/password.'
347

  
348
    app.authorization = ('Basic', ('john.doe', 'password'))
349

  
350
    # missing agenda
351
    resp = app.patch(api_url, status=404)
352
    assert resp.json['detail'] == 'Not found.'
353

  
354
    meeting_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='meetings')
355

  
356
    # using meeting agenda
357
    api_url = '/api/agenda/%s/event/%s/' % (meeting_agenda.slug, 'nop')
358
    resp = app.patch(api_url, status=404)
359
    assert resp.json['detail'] == 'Not found.'
360

  
361
    agenda = Agenda.objects.create(label='Foo bar')
362

  
363
    # missing event
364
    api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, 'nop')
365
    resp = app.patch(api_url, status=404)
366
    assert resp.json['detail'] == 'Not found.'
367

  
368
    # missing recurring event
369
    api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, 'nop:2021-11-15-1538')
370
    resp = app.patch(api_url, status=400)
371
    assert resp.json['err']
372
    assert resp.json['err_desc'] == 'unknown recurring event slug: nop'
373

  
374
    event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, waiting_list_places=5)
375
    api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, event.slug)
376

  
377
    # update with errors in datetime parts
378
    params = {
379
        'start_datetime': '2021-11-15 minuit',
380
        'recurrence_days': '7, 8',
381
    }
382
    resp = app.patch(api_url, params=params, status=400)
383
    assert resp.json['err']
384
    assert resp.json['err_desc'] == 'invalid payload'
385
    assert 'Datetime has wrong format' in resp.json['errors']['start_datetime'][0]
386
    assert resp.json['errors']['recurrence_days']['0'][0] == 'Ensure this value is less than or equal to 6.'
387

  
388
    # update with almost all optional managed fields
389
    params = {
390
        'start_datetime': '2021-11-15 15:38',
391
        'duration': 42,
392
        'publication_date': '2021-09-20',
393
        'places': 8,
394
        'waiting_list_places': 3,
395
        'label': 'FOO camp',
396
        'description': 'An event',
397
        'pricing': 'free',
398
        'url': 'http://example.org/foo/bar/?',
399
    }
400
    resp = app.patch(api_url, params=params)
401
    assert not resp.json['err']
402
    event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
403
    assert event.duration == 42
404
    assert event.places == 8
405
    assert event.waiting_list_places == 3
406
    assert event.label == 'FOO camp'
407
    assert event.description == 'An event'
408
    assert event.pricing == 'free'
409
    assert event.url == 'http://example.org/foo/bar/?'
410

  
411
    # update event as a recurring event
412
    params = {
413
        'recurrence_days': '0,3,5',
414
        'recurrence_week_interval': 2,
415
        'recurrence_end_date': '2021-12-27',
416
    }
417
    resp = app.patch(api_url, params=params)
418
    assert not resp.json['err']
419
    event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
420
    assert event.recurrences.count() == 9
421
    assert [x.places for x in event.recurrences.all()] == [8] * 9
422

  
423
    booking = Booking.objects.create(event=event.recurrences.all()[2])
424

  
425
    # update unprotected fields
426
    params = {
427
        'places': 7,
428
    }
429
    resp = app.patch(api_url, params=params)
430
    assert not resp.json['err']
431
    event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
432
    assert [x.places for x in event.recurrences.all()] == [7] * 9
433

  
434
    # try to update recurring event protected fields
435
    params = {
436
        'recurrence_days': '1,2',
437
        'recurrence_week_interval': 1,
438
    }
439
    resp = app.patch(api_url, params=params, status=400)
440
    assert resp.json['err']
441
    assert 'cannot be modified' in resp.json['errors']['recurrence_days'][0]
442
    assert 'cannot be modified' in resp.json['errors']['recurrence_week_interval'][0]
443

  
444
    assert booking.event.start_datetime.date() == datetime.date(2021, 11, 20)
445

  
446
    # try to reduce recurrence end date before booked event
447
    params = {
448
        'recurrence_end_date': '2021-11-20',
449
    }
450
    resp = app.patch(api_url, params=params, status=400)
451
    assert resp.json['err']
452
    assert resp.json['errors']['recurrence_end_date'][0] == 'Bookings exist after this date.'
453

  
454
    # reduce recurrence end date after booked event
455
    params = {
456
        'recurrence_end_date': '2021-11-21',
457
    }
458
    resp = app.patch(api_url, params=params)
459
    assert not resp.json['err']
460
    event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
461
    assert event.recurrences.count() == 3
462

  
463
    booking.cancel()
464

  
465
    # update no more protected fields
466
    params = {
467
        'recurrence_days': '1,2,3,4,5',
468
        'recurrence_week_interval': 1,
469
    }
470
    resp = app.patch(api_url, params=params)
471
    assert not resp.json['err']
472
    event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
473
    assert event.recurrences.count() == 5
334
-