api: add add-event endpoint (#47337)

This commit is contained in:
Nicolas Roche 2020-12-08 16:27:57 +01:00
parent 06291d148f
commit 9bf248a095
4 changed files with 269 additions and 30 deletions

View File

@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from chrono.agendas.models import AbsenceReason, Booking
from chrono.agendas.models import AbsenceReason, Booking, Event
class StringOrListField(serializers.ListField):
@ -133,3 +133,26 @@ class MultipleAgendasDatetimesSerializer(DatetimesSerializer):
agendas = CommaSeparatedStringField(
required=True, child=serializers.SlugField(max_length=160, allow_blank=False)
)
class EventSerializer(serializers.ModelSerializer):
recurrence_days = CommaSeparatedStringField(
required=False, child=serializers.IntegerField(min_value=0, max_value=6)
)
class Meta:
model = Event
fields = [
'start_datetime',
'recurrence_days',
'recurrence_week_interval',
'recurrence_end_date',
'duration',
'publication_date',
'places',
'waiting_list_places',
'label',
'description',
'pricing',
'url',
]

View File

@ -64,6 +64,11 @@ urlpatterns = [
views.event_check,
name='api-event-check',
),
url(
r'^agenda/(?P<agenda_identifier>[\w-]+)/add-event/$',
views.agenda_add_event,
name='api-agenda-add-event',
),
url(
r'^agenda/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$',
views.meeting_datetimes,

View File

@ -440,7 +440,7 @@ def get_event_text(event, agenda, day=None):
event.label,
date_format(localtime(event.start_datetime), 'DATETIME_FORMAT'),
)
elif event.recurrence_days:
elif day is not None:
event_text = _('%(weekday)s: %(event)s') % {
'weekday': WEEKDAYS[day].capitalize(),
'event': event_text,
@ -470,35 +470,48 @@ def get_event_detail(
'pricing': event.pricing,
'url': event.url,
'duration': event.duration,
'disabled': is_event_disabled(event, min_places=min_places, disable_booked=disable_booked),
'api': {
'bookings_url': request.build_absolute_uri(
reverse(
'api-event-bookings',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'fillslot_url': request.build_absolute_uri(
reverse(
'api-fillslot',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'status_url': request.build_absolute_uri(
reverse(
'api-event-status',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'check_url': request.build_absolute_uri(
reverse(
'api-event-check',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
},
'places': get_event_places(event),
}
if event.recurrence_days:
details.update(
{
'recurrence_days': event.recurrence_days,
'recurrence_week_interval': event.recurrence_week_interval,
'recurrence_end_date': event.recurrence_end_date,
}
)
else:
details.update(
{
'disabled': is_event_disabled(event, min_places=min_places, disable_booked=disable_booked),
'api': {
'bookings_url': request.build_absolute_uri(
reverse(
'api-event-bookings',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'fillslot_url': request.build_absolute_uri(
reverse(
'api-fillslot',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'status_url': request.build_absolute_uri(
reverse(
'api-event-status',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
'check_url': request.build_absolute_uri(
reverse(
'api-event-check',
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': event.slug},
)
),
},
'places': get_event_places(event),
}
)
if show_events is not None:
details['api']['fillslot_url'] += '?events=%s' % show_events
if booked_user_external_id:
@ -2407,3 +2420,28 @@ class BookingsStatistics(APIView):
bookings_statistics = BookingsStatistics.as_view()
class AgendaAddEventView(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.EventSerializer
def post(self, request, agenda_identifier):
agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
raise APIError(
_('invalid payload'),
err_class='invalid payload',
errors=serializer.errors,
http_status=status.HTTP_400_BAD_REQUEST,
)
payload = serializer.validated_data
event = Event.objects.create(agenda=agenda, **payload)
if event.recurrence_days and event.recurrence_end_date:
event.create_all_recurrences()
return Response({'err': 0, 'data': get_event_detail(request, event)})
agenda_add_event = AgendaAddEventView.as_view()

View File

@ -158,3 +158,176 @@ def test_event_checked(app, user):
agenda.kind = 'virtual'
agenda.save()
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug), status=404)
def test_add_event(app, user):
api_url = '/api/agenda/%s/add-event/' % ('999')
# no authentication
resp = app.post(api_url, status=401)
assert resp.json['detail'] == 'Authentication credentials were not provided.'
# wrong password
app.authorization = ('Basic', ('john.doe', 'wrong'))
resp = app.post(api_url, status=401)
assert resp.json['detail'] == 'Invalid username/password.'
app.authorization = ('Basic', ('john.doe', 'password'))
# missing agenda
resp = app.post(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
# using meeting agenda
meeting_agenda = Agenda(label='Foo bar Meeting', kind='meetings')
meeting_agenda.save()
api_url = '/api/agenda/%s/add-event/' % (meeting_agenda.slug)
resp = app.post(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
agenda = Agenda(label='Foo bar')
agenda.maximal_booking_delay = 0
agenda.save()
api_url = '/api/agenda/%s/add-event/' % (agenda.slug)
# missing fields
resp = app.post(api_url, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'start_datetime': ['This field is required.'],
'places': ['This field is required.'],
}
# add with errors in datetime parts
params = {
'start_datetime': '2021-11-15 minuit',
'places': 10,
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert 'Datetime has wrong format' in resp.json['errors']['start_datetime'][0]
# add an event
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event'
assert {'api', 'disabled', 'places'}.issubset(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.isdisjoint(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event')
assert str(event.start_datetime) == '2021-11-15 14:38:00+00:00'
assert str(event.start_datetime.tzinfo) == 'UTC'
assert event.places == 10
assert event.publication_date is None
# add with almost all optional managed fields
params = {
'start_datetime': '2021-11-15 15:38',
'duration': 42,
'publication_date': '2021-09-20',
'places': 11,
'waiting_list_places': 3,
'label': 'FOO camp',
'description': 'An event',
'pricing': 'free',
'url': 'http://example.org/foo/bar/?',
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-camp'
assert {'api', 'disabled', 'places'}.issubset(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.isdisjoint(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-camp')
assert event.duration == 42
assert event.waiting_list_places == 3
assert event.label == 'FOO camp'
assert event.description == 'An event'
assert event.pricing == 'free'
assert event.url == 'http://example.org/foo/bar/?'
# add with errors in recurrence_days list
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'recurrence_days': 'oups',
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['recurrence_days']['0'][0] == 'A valid integer is required.'
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'recurrence_days': '7',
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['recurrence_days']['0'][0] == 'Ensure this value is less than or equal to 6.'
# add a recurrent event
params = {
'start_datetime': '2021-11-15 15:38',
'places': 12,
'recurrence_days': '0,3,5',
'recurrence_week_interval': '2',
'description': 'A recurrent event',
}
assert Event.objects.filter(agenda=agenda).count() == 2
resp = app.post(api_url, params=params)
assert Event.objects.filter(agenda=agenda).count() == 3
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-1'
assert {'api', 'disabled', 'places'}.isdisjoint(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.issubset(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-1')
assert event.description == 'A recurrent event'
assert event.recurrence_days == [0, 3, 5]
assert event.recurrence_week_interval == 2
assert event.recurrence_end_date is None
# add a recurrent event with end recurrence date creates 9 recurrences
params = {
'start_datetime': '2021-11-15 15:38',
'places': 13,
'recurrence_days': '0,3,5', # Monday, Tuesday, Saturday
'recurrence_week_interval': '2',
'recurrence_end_date': '2021-12-27',
'description': 'A recurrent event having recurrences',
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-2'
assert {'api', 'disabled', 'places'}.isdisjoint(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.issubset(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-2')
assert Event.objects.filter(agenda=agenda).count() == 13
assert event.description == 'A recurrent event having recurrences'
assert event.recurrence_days == [0, 3, 5]
assert event.recurrence_week_interval == 2
assert event.recurrence_end_date == datetime.date(2021, 12, 27)
assert sorted(
str(x.start_datetime.date()) for x in Event.objects.all() if 'foo-bar-event-2--' in x.slug
) == [
'2021-11-15',
'2021-11-18',
'2021-11-20',
'2021-11-29',
'2021-12-02',
'2021-12-04',
'2021-12-13',
'2021-12-16',
'2021-12-18',
]