api: configure custom fields on event (#63287)
This commit is contained in:
parent
7cd25676fc
commit
c945f6b13f
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)})
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue