api: configure custom fields on event (#63287)

This commit is contained in:
Lauréline Guérin 2022-04-05 16:29:23 +02:00
parent 7cd25676fc
commit c945f6b13f
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 201 additions and 20 deletions

View File

@ -1717,6 +1717,7 @@ class Event(models.Model):
description=self.description,
pricing=self.pricing,
url=self.url,
custom_fields=self.custom_fields,
)
# remove pytz info because dateutil doesn't support DST changes

View File

@ -316,6 +316,50 @@ class EventSerializer(serializers.ModelSerializer):
'url',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.agenda.events_type and not self.instance.primary_event:
field_classes = {
'text': serializers.CharField,
'textarea': serializers.CharField,
'bool': serializers.NullBooleanField,
}
field_options = {
'text': {'allow_blank': True},
'textarea': {'allow_blank': True},
}
for custom_field in self.instance.agenda.events_type.get_custom_fields():
field_class = field_classes[custom_field['field_type']]
field_name = 'custom_field_%s' % custom_field['varname']
self.fields[field_name] = field_class(
required=False,
**(field_options.get(custom_field['field_type']) or {}),
)
def validate(self, attrs):
if not self.instance.agenda.events_type:
return attrs
if self.instance.primary_event:
return attrs
defaults = {
'text': '',
'textarea': '',
'bool': None,
}
custom_fields = self.instance.custom_fields
for custom_field in self.instance.agenda.events_type.get_custom_fields():
varname = custom_field['varname']
field_name = 'custom_field_%s' % varname
if varname not in custom_fields:
# set default
custom_fields[varname] = defaults[custom_field['field_type']]
if field_name in attrs:
custom_fields[varname] = attrs[field_name]
del attrs[field_name]
attrs['custom_fields'] = custom_fields
return attrs
class AgendaSerializer(serializers.ModelSerializer):
edit_role = serializers.CharField(required=False, max_length=150)

View File

@ -2507,11 +2507,11 @@ class EventsAPI(APIView):
def post(self, request, agenda_identifier):
agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
serializer = self.serializer_class(data=request.data)
event = Event(agenda=agenda)
serializer = self.serializer_class(data=request.data, instance=event)
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
payload = serializer.validated_data
event = Event.objects.create(agenda=agenda, **payload)
event = serializer.save()
if event.recurrence_days:
event.create_all_recurrences()
return Response({'err': 0, 'data': get_event_detail(request, event)})

View File

@ -3,7 +3,7 @@ import datetime
import pytest
from django.utils.timezone import localtime, now
from chrono.agendas.models import Agenda, Booking, Event
from chrono.agendas.models import Agenda, Booking, Event, EventsType
pytestmark = pytest.mark.django_db
@ -191,15 +191,12 @@ def test_add_event(app, user):
assert resp.json['detail'] == 'Not found.'
# using meeting agenda
meeting_agenda = Agenda(label='Foo bar Meeting', kind='meetings')
meeting_agenda.save()
meeting_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='meetings')
api_url = '/api/agenda/%s/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()
agenda = Agenda.objects.create(label='Foo bar', maximal_booking_delay=0)
api_url = '/api/agenda/%s/event/' % (agenda.slug)
# missing fields
@ -237,6 +234,31 @@ def test_add_event(app, user):
assert str(event.start_datetime.tzinfo) == 'UTC'
assert event.places == 10
assert event.publication_datetime is None
assert event.custom_fields == {}
# add an event without custom fields
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
agenda.events_type = events_type
agenda.save()
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.latest('pk')
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
# add with almost all optional managed fields
params = {
@ -249,6 +271,9 @@ def test_add_event(app, user):
'description': 'An event',
'pricing': 'free',
'url': 'http://example.org/foo/bar/?',
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
@ -266,6 +291,22 @@ def test_add_event(app, user):
assert event.url == 'http://example.org/foo/bar/?'
assert str(event.publication_datetime) == '2021-09-20 08:00:00+00:00'
assert str(event.publication_datetime.tzinfo) == 'UTC'
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
# add with errors in bool custom field
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'custom_field_bool': 'foobar',
}
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['custom_field_bool'][0] == 'Must be a valid boolean.'
# add with errors in recurrence_days list
params = {
@ -295,20 +336,22 @@ def test_add_event(app, user):
'recurrence_week_interval': '2',
'description': 'A recurrent event',
}
assert Event.objects.filter(agenda=agenda).count() == 2
assert Event.objects.filter(agenda=agenda).count() == 3
resp = app.post_json(api_url, params=params)
assert Event.objects.filter(agenda=agenda).count() == 28
assert Event.objects.filter(agenda=agenda).count() == 29
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-1'
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-1')
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-2')
assert event.description == 'A recurrent event'
assert event.recurrence_days == [3]
assert event.recurrence_week_interval == 2
assert event.recurrence_end_date is None
# some occurrences created
assert event.recurrences.count() == 25
# add a recurrent event with end recurrence date creates 9 recurrences
params = {
@ -318,22 +361,31 @@ def test_add_event(app, user):
'recurrence_week_interval': '2',
'recurrence_end_date': '2021-12-27',
'description': 'A recurrent event having recurrences',
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-2'
assert resp.json['data']['id'] == 'foo-bar-event-3'
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() == 38
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-3')
assert Event.objects.filter(agenda=agenda).count() == 39
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 event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
assert event.recurrences.count() == 9
assert sorted(
str(x.start_datetime.date()) for x in Event.objects.all() if 'foo-bar-event-2--' in x.slug
str(x.start_datetime.date()) for x in Event.objects.all() if 'foo-bar-event-3--' in x.slug
) == [
'2021-11-15',
'2021-11-18',
@ -345,6 +397,12 @@ def test_add_event(app, user):
'2021-12-16',
'2021-12-18',
]
for ev in event.recurrences.all():
assert ev.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
app.delete('/api/agenda/%s/event/' % agenda.slug, status=405) # forbidden
@ -375,7 +433,15 @@ def test_update_event(app, user):
resp = app.patch(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
agenda = Agenda.objects.create(label='Foo bar')
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
agenda = Agenda.objects.create(label='Foo bar', events_type=events_type)
# missing event
api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, 'nop')
@ -420,18 +486,38 @@ def test_update_event(app, user):
assert event.url == 'http://example.org/foo/bar/?'
assert str(event.publication_datetime) == '2021-09-20 10:00:00+00:00'
assert str(event.publication_datetime.tzinfo) == 'UTC'
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
# update event as a recurring event
params = {
'recurrence_days': '0,3,5',
'recurrence_week_interval': 2,
'recurrence_end_date': '2021-12-27',
# with custom fields
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.recurrences.count() == 9
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
assert [x.places for x in event.recurrences.all()] == [8] * 9
for ev in event.recurrences.all():
assert ev.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
recurrence_slug = event.recurrences.first().slug
assert recurrence_slug == 'foo-bar-event--2021-11-15-1538'
@ -467,14 +553,26 @@ def test_update_event(app, user):
assert resp.json['err']
assert 'cannot be modified' in resp.json['err_desc']
# update protected fields of one of the event recurrencies providing same value
assert 'cannot be modified' in resp.json['err_desc']
# update protected fields of one of the event recurrencies providing same value
params = {
'recurrence_week_interval': 1,
}
resp = app.patch_json(recurrence_url, params=params)
assert not resp.json['err']
# update of custom field of one of event recurrences is ignored
params = {
'custom_field_text': 'foo bar baz',
}
resp = app.patch_json(recurrence_url, params=params)
assert not resp.json['err']
recurrence.refresh_from_db()
assert recurrence.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
booking = Booking.objects.create(event=event.recurrences.all()[2])
# update unprotected fields
@ -525,6 +623,44 @@ def test_update_event(app, user):
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.recurrences.count() == 5
# update just one custom field
params = {
'custom_field_textarea': 'foo bar baz',
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar baz',
'bool': True,
}
params = {
'custom_field_bool': False,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar baz',
'bool': False,
}
# reset custom fields
params = {
'custom_field_text': '',
'custom_field_textarea': '',
'custom_field_bool': None,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
@pytest.mark.freeze_time('2021-11-01 10:00')
def test_delete_event(app, user):