Projet

Général

Profil

0001-api-add-booking-ics-view-22930.patch

Serghei Mihai, 18 juillet 2018 16:48

Télécharger (10,4 ko)

Voir les différences:

Subject: [PATCH] api: add booking ics view (#22930)

 chrono/agendas/models.py | 24 ++++++++++++
 chrono/api/urls.py       |  2 +
 chrono/api/views.py      | 24 +++++++++++-
 tests/test_api.py        | 83 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 131 insertions(+), 2 deletions(-)
chrono/agendas/models.py
376 376
            self.secondary_booking_set.update(in_waiting_list=False)
377 377
            self.save()
378 378

  
379
    def get_ics(self, request=None):
380
        ics = vobject.iCalendar()
381
        ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
382
        vevent = vobject.newFromBehavior('vevent')
383
        vevent.add('uid').value = '%s-%s-%s' % (self.event.start_datetime.isoformat(), self.event.agenda.pk, self.pk)
384

  
385
        vevent.add('summary').value = self.label
386
        vevent.add('dtstart').value = self.event.start_datetime
387
        if self.user_name:
388
            vevent.add('attendee').value = self.user_name
389
        if self.event.meeting_type:
390
            vevent.add('dtend').value = self.event.start_datetime + datetime.timedelta(minutes=self.event.meeting_type.duration)
391

  
392
        if self.backoffice_url:
393
            vevent.add('url').value = self.backoffice_url
394

  
395
        for field in ('description', 'location', 'comment'):
396
            field_value = request and request.GET.get(field) or self.extra_data.get(field)
397
            if field_value:
398
                vevent.add(field).value = field_value
399
        ics.add(vevent)
400
        return ics.serialize()
401

  
402

  
379 403

  
380 404
@python_2_unicode_compatible
381 405
class Desk(models.Model):
chrono/api/urls.py
44 44
        name='api-cancel-booking'),
45 45
    url(r'booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking,
46 46
        name='api-accept-booking'),
47
    url(r'booking/(?P<booking_pk>\w+)/ics/$', views.booking_ics,
48
        name='api-booking-ics'),
47 49
]
chrono/api/views.py
18 18
import datetime
19 19

  
20 20
from django.core.urlresolvers import reverse
21
from django.http import Http404
21
from django.http import Http404, HttpResponse
22 22
from django.shortcuts import get_object_or_404
23 23
from django.utils.dateparse import parse_date
24 24
from django.utils.encoding import force_text
......
456 456
            'datetime': localtime(events[0].start_datetime),
457 457
            'api': {
458 458
                'cancel_url': request.build_absolute_uri(
459
                    reverse('api-cancel-booking', kwargs={'booking_pk': primary_booking.id}))
459
                    reverse('api-cancel-booking', kwargs={'booking_pk': primary_booking.id})),
460
                'ics_url': request.build_absolute_uri(
461
                    reverse('api-booking-ics', kwargs={'booking_pk': primary_booking.id})),
460 462
            }
461 463
        }
462 464
        if in_waiting_list:
......
566 568
        return Response(response)
567 569

  
568 570
slot_status = SlotStatus.as_view()
571

  
572

  
573
class BookingICS(APIView):
574
    permission_classes = (permissions.IsAuthenticated,)
575

  
576
    def get(self, request, booking_pk=None, format=None):
577
        booking = get_object_or_404(Booking, id=booking_pk)
578
        if booking.cancellation_datetime:
579
            return Response({'err': 1, 'reason': 'booking is cancelled'},
580
                            status=status.HTTP_400_BAD_REQUEST)
581
        if booking.in_waiting_list:
582
            return Response({'err': 2, 'reason': 'booking is in waiting list'},
583
                            status=status.HTTP_400_BAD_REQUEST)
584
        response = HttpResponse(booking.get_ics(request), content_type='text/calendar')
585
        response['Content-Disposition'] = 'inline; filename=%s.ics;' % booking.pk
586
        return response
587

  
588
booking_ics = BookingICS.as_view()
tests/test_api.py
329 329
    assert resp.json['datetime'] == localtime(event.start_datetime).isoformat()
330 330
    assert 'accept_url' not in resp.json['api']
331 331
    assert 'cancel_url' in resp.json['api']
332
    assert 'ics_url' in resp.json['api']
332 333
    assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
334
    assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
333 335
    assert Booking.objects.count() == 1
334 336

  
335 337
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id))
......
370 372

  
371 373
    resp = app.post('/api/agenda/233/fillslot/%s/' % event.id, status=404)
372 374

  
375
def test_booking_ics(app, some_data, meetings_agenda, user):
376
    agenda = Agenda.objects.filter(label=u'Foo bar')[0]
377
    event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
378
    app.authorization = ('Basic', ('john.doe', 'password'))
379
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id))
380

  
381
    assert Booking.objects.count() == 1
382
    assert 'ics_url' in resp.json['api']
383
    assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
384
    assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
385

  
386
    formatted_start_date = event.start_datetime.strftime('%Y%m%dT%H%M%S')
387
    booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics()
388
    assert 'UID:%s-%s-%s\r\n' % (event.start_datetime.isoformat(), agenda.pk, resp.json['booking_id']) in booking_ics
389
    assert 'SUMMARY:\r\n' in booking_ics
390
    assert 'DTSTART:%sZ\r\n' % formatted_start_date in booking_ics
391
    assert 'DTEDND:' not in booking_ics
392

  
393
    # test with additional data
394
    resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
395
            params={'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/'})
396
    assert Booking.objects.count() == 2
397
    booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics()
398
    assert 'SUMMARY:foo\r\n' in booking_ics
399
    assert 'ATTENDEE:bar\r\n' in booking_ics
400
    assert 'URL:http://example.net/\r\n' in booking_ics
401

  
402
    # extra data stored in extra_data field
403
    resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
404
            params={'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'location': 'bar',
405
                    'comment': 'booking comment', 'description': 'booking description'})
406
    assert Booking.objects.count() == 3
407
    booking_id = resp.json['booking_id']
408
    booking = Booking.objects.get(id=booking_id)
409
    booking_ics = booking.get_ics()
410
    assert 'COMMENT:booking comment\r\n' in booking_ics
411
    assert 'LOCATION:bar\r\n' in booking_ics
412
    assert 'DESCRIPTION:booking description\r\n' in booking_ics
413

  
414
    # unauthenticated
415
    app.authorization = None
416
    app.get('/api/booking/%s/ics/' % resp.json['booking_id'], status=403)
417

  
418
    app.authorization = ('Basic', ('john.doe', 'password'))
419
    resp = app.get('/api/booking/%s/ics/' % resp.json['booking_id'])
420
    assert resp.headers['Content-Type'] == 'text/calendar'
421
    assert resp.headers['Content-Disposition'] == 'inline; filename=%s.ics;' % booking_id
422

  
423
    resp = app.get('/api/booking/%s/ics/?description=custom booking description&location=custom booking location&comment=custom comment' % booking_id)
424
    assert 'DESCRIPTION:custom booking description\r\n' in resp.text
425
    assert 'LOCATION:custom booking location\r\n' in resp.text
426
    assert 'COMMENT:custom comment\r\n' in resp.text
427

  
428
    booking.in_waiting_list = True
429
    booking.save()
430
    resp = app.get('/api/booking/%s/ics/' % booking_id, status=400)
431
    assert resp.json['reason'] == 'booking is in waiting list'
432

  
433
    booking.cancellation_datetime = now()
434
    booking.save()
435
    resp = app.get('/api/booking/%s/ics/' % booking_id, status=400)
436
    assert resp.json['reason'] == 'booking is cancelled'
437

  
438
    meetings_agenda_id = Agenda.objects.filter(label=u'Foo bar Meeting')[0].id
439
    meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
440
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
441
    event = resp.json['data'][2]
442
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda_id, event['id']))
443
    assert Booking.objects.count() == 4
444
    assert 'ics_url' in resp.json['api']
445
    booking = Booking.objects.get(id=resp.json['booking_id'])
446
    booking_ics = booking.get_ics()
447
    start = booking.event.start_datetime.strftime('%Y%m%dT%H%M%S')
448
    end = (booking.event.start_datetime + datetime.timedelta(minutes=booking.event.meeting_type.duration)).strftime('%Y%m%dT%H%M%S')
449
    assert "DTSTART:%sZ\r\n" % start in booking_ics
450
    assert "DTEND:%sZ\r\n" % end in booking_ics
451

  
373 452
def test_booking_api_fillslots(app, some_data, user):
374 453
    agenda = Agenda.objects.filter(label=u'Foo bar')[0]
375 454
    events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]
......
779 858
    assert resp.json['in_waiting_list'] is True
780 859
    assert 'accept_url' in resp.json['api']
781 860
    assert 'cancel_url' in resp.json['api']
861
    assert 'ics_url' in resp.json['api']
782 862
    assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
783 863
    assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
864
    assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
784 865

  
785 866
    # cancel a booking that was not on the waiting list
786 867
    booking = Booking.objects.filter(event=event, in_waiting_list=False)[0]
......
933 1014
    assert resp.json['datetime'] == localtime(events[0].start_datetime).isoformat()
934 1015
    assert 'accept_url' not in resp.json['api']
935 1016
    assert 'cancel_url' in resp.json['api']
1017
    assert 'ics_url' in resp.json['api']
936 1018
    for event in events:
937 1019
        assert Event.objects.get(id=event.id).booked_places == 3
938 1020

  
......
1541 1623
    # them.
1542 1624
    resp = app.get(api_url)
1543 1625
    assert len([x for x in resp.json['data'] if x['disabled']]) == 2
1626
    #
1544
-