api: allow getting all user bookings as ICS (#80685)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Valentin Deniaud 2023-09-18 14:28:57 +02:00
parent 8b924ef670
commit 42cc548a33
6 changed files with 75 additions and 3 deletions

View File

@ -2852,9 +2852,7 @@ class Booking(models.Model):
anonymization_datetime=now(),
)
def get_ics(self, request=None):
ics = vobject.iCalendar()
ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
def get_vevent_ics(self, request=None):
vevent = vobject.newFromBehavior('vevent')
vevent.add('uid').value = '%s-%s-%s' % (
self.event.start_datetime.isoformat(),
@ -2882,6 +2880,12 @@ class Booking(models.Model):
field_value = request and request.GET.get(field) or (self.extra_data or {}).get(field)
if field_value:
vevent.add(field).value = field_value
return vevent
def get_ics(self, request=None):
ics = vobject.iCalendar()
ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
vevent = self.get_vevent_ics(request)
ics.add(vevent)
return ics.serialize()

View File

@ -124,6 +124,7 @@ urlpatterns = [
name='api-agenda-subscription',
),
path('bookings/', views.bookings, name='api-bookings'),
path('bookings/ics/', views.bookings_ics, name='api-bookings-ics'),
path('booking/<int:booking_pk>/', views.booking, name='api-booking'),
path('booking/<int:booking_pk>/cancel/', views.cancel_booking, name='api-cancel-booking'),
path('booking/<int:booking_pk>/accept/', views.accept_booking, name='api-accept-booking'),

View File

@ -20,6 +20,7 @@ import datetime
import json
import uuid
import vobject
from django.conf import settings
from django.db import IntegrityError, transaction
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Prefetch, Q
@ -2003,7 +2004,11 @@ class EventsFillslots(APIView):
],
'cancelled_booking_count': cancelled_count,
'cancelled_events': cancelled_events,
'bookings_ics_url': request.build_absolute_uri(reverse('api-bookings-ics'))
+ '?user_external_id=%s' % user_external_id,
}
if not self.multiple_agendas:
response['bookings_ics_url'] += '&agenda=%s' % self.agenda.slug
return Response(response)
def get_events(self, request, payload, start_datetime, end_datetime):
@ -2550,6 +2555,29 @@ class BookingsAPI(ListAPIView):
bookings = BookingsAPI.as_view()
class BookingsICS(BookingsAPI):
def get(self, request, *args, **kwargs):
if not request.GET.get('user_external_id'):
raise APIError(N_('missing param user_external_id'))
try:
bookings = self.filter_queryset(self.get_queryset())
except ValidationError as e:
raise APIErrorBadRequest(N_('invalid payload'), errors=e.detail)
ics = vobject.iCalendar()
ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
for booking in bookings:
vevent = booking.get_vevent_ics()
ics.add(vevent)
return HttpResponse(ics.serialize(), content_type='text/calendar')
bookings_ics = BookingsICS.as_view()
class BookingAPI(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.BookingSerializer

View File

@ -48,6 +48,10 @@ def test_api_events_fillslots(app, user):
assert len(resp.json['waiting_list_events']) == 0
assert resp.json['cancelled_booking_count'] == 0
assert len(resp.json['cancelled_events']) == 0
assert (
resp.json['bookings_ics_url']
== 'http://testserver/api/bookings/ics/?user_external_id=user_id&agenda=foo-bar'
)
events = Event.objects.all()
assert events.filter(booked_places=1).count() == 2

View File

@ -178,6 +178,7 @@ def test_api_events_fillslots_multiple_agendas(app, user):
)
assert first_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1
assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1
assert resp.json['bookings_ics_url'] == 'http://testserver/api/bookings/ics/?user_external_id=user_id'
# booking modification
params = {'user_external_id': 'user_id', 'slots': 'first-agenda@event'}

View File

@ -104,6 +104,40 @@ def test_booking_ics(app, some_data, meetings_agenda, user, settings, rf):
assert 'DTEND:%sZ\r\n' % end in booking_ics
@pytest.mark.freeze_time('2023-09-18 14:00')
def test_bookings_ics(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event A', agenda=agenda, start_datetime=now() + datetime.timedelta(days=3), places=10
)
Booking.objects.create(event=event, user_external_id='enfant-1234')
agenda = Agenda.objects.create(label='Foo bar 2', kind='events')
event = Event.objects.create(
label='Event B', agenda=agenda, start_datetime=now() + datetime.timedelta(days=4), places=10
)
Booking.objects.create(event=event, user_external_id='enfant-1234')
resp = app.get('/api/bookings/ics/')
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'missing param user_external_id'
resp = app.get('/api/bookings/ics/', params={'user_external_id': 'enfant-1234'})
assert 'BEGIN:VCALENDAR' in resp.text
assert resp.text.count('UID') == 2
assert 'DTSTART:20230921' in resp.text
assert 'DTSTART:20230922' in resp.text
resp = app.get('/api/bookings/ics/', params={'user_external_id': 'enfant-1234', 'agenda': 'foo-bar'})
assert resp.text.count('UID') == 1
assert 'DTSTART:20230921' in resp.text
assert 'DTSTART:20230922' not in resp.text
resp = app.get('/api/bookings/ics/', params={'user_external_id': 'enfant-1234', 'agenda': 'xxx'})
assert 'BEGIN:VCALENDAR' in resp.text
assert resp.text.count('UID') == 0
def test_bookings_api(app, user):
events_agenda = Agenda.objects.create(label='Foo bar', kind='events')
events_event = Event.objects.create(