api: include custom fields in events details (#63288)
This commit is contained in:
parent
51f554aeaf
commit
0c9b65cead
|
@ -1827,6 +1827,14 @@ class Event(models.Model):
|
|||
datetime_part = self.slug.rsplit('--')[-1]
|
||||
return datetime.datetime.strptime(datetime_part, '%Y-%m-%d-%H%M')
|
||||
|
||||
def get_custom_fields(self):
|
||||
if not self.agenda.events_type:
|
||||
return {}
|
||||
custom_fields = {}
|
||||
for custom_field in self.agenda.events_type.get_custom_fields():
|
||||
custom_fields[custom_field['varname']] = self.custom_fields.get(custom_field['varname'])
|
||||
return custom_fields
|
||||
|
||||
|
||||
class EventsType(models.Model):
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
|
|
|
@ -220,6 +220,9 @@ class AgendaOrSubscribedSlugsMixin(DateRangeMixin):
|
|||
user_external_id = serializers.CharField(required=False, max_length=250, allow_blank=False)
|
||||
guardian_external_id = serializers.CharField(required=False, max_length=250, allow_blank=False)
|
||||
|
||||
def get_agenda_qs(self):
|
||||
return Agenda.objects.filter(kind='events').select_related('events_type')
|
||||
|
||||
def validate(self, attrs):
|
||||
super().validate(attrs)
|
||||
if 'agendas' not in attrs and 'subscribed' not in attrs:
|
||||
|
@ -244,7 +247,7 @@ class AgendaOrSubscribedSlugsMixin(DateRangeMixin):
|
|||
if 'date_end' in attrs:
|
||||
# subscription must start before requested date end
|
||||
lookups['subscriptions__date_start__lte'] = attrs['date_end']
|
||||
agendas = Agenda.objects.filter(**lookups).distinct().select_related('category')
|
||||
agendas = self.get_agenda_qs().filter(**lookups).distinct().select_related('category')
|
||||
|
||||
if attrs['subscribed'] != ['all']:
|
||||
agendas = agendas.filter(category__slug__in=attrs['subscribed'])
|
||||
|
@ -257,7 +260,7 @@ class AgendaOrSubscribedSlugsMixin(DateRangeMixin):
|
|||
|
||||
def validate_agendas(self, value):
|
||||
self.agenda_slugs = value
|
||||
return get_objects_from_slugs(value, qs=Agenda.objects.filter(kind='events'))
|
||||
return get_objects_from_slugs(value, qs=self.get_agenda_qs())
|
||||
|
||||
|
||||
class MultipleAgendasDatetimesSerializer(AgendaOrSubscribedSlugsMixin, DatetimesSerializer):
|
||||
|
|
|
@ -503,6 +503,8 @@ def get_event_detail(
|
|||
'url': event.url,
|
||||
'duration': event.duration,
|
||||
}
|
||||
for key, value in event.get_custom_fields().items():
|
||||
details['custom_field_%s' % key] = value
|
||||
if booking:
|
||||
details['booking'] = {
|
||||
'id': booking.pk,
|
||||
|
@ -803,12 +805,13 @@ class Datetimes(APIView):
|
|||
serializer_class = serializers.DatetimesSerializer
|
||||
|
||||
def get(self, request, agenda_identifier=None, format=None):
|
||||
agenda_qs = Agenda.objects.select_related('events_type')
|
||||
try:
|
||||
agenda = Agenda.objects.get(slug=agenda_identifier)
|
||||
agenda = agenda_qs.get(slug=agenda_identifier)
|
||||
except Agenda.DoesNotExist:
|
||||
try:
|
||||
# legacy access by agenda id
|
||||
agenda = Agenda.objects.get(id=int(agenda_identifier))
|
||||
agenda = agenda_qs.get(id=int(agenda_identifier))
|
||||
except (ValueError, Agenda.DoesNotExist):
|
||||
raise Http404()
|
||||
if agenda.kind != 'events':
|
||||
|
@ -1653,6 +1656,7 @@ class RecurringFillslots(APIView):
|
|||
payload = serializer.validated_data
|
||||
user_external_id = payload['user_external_id']
|
||||
agendas = Agenda.prefetch_events(data['agendas'], user_external_id=user_external_id)
|
||||
agendas_by_id = {x.id: x for x in agendas}
|
||||
|
||||
if data['action'] == 'update':
|
||||
events_to_book = self.get_event_recurrences(
|
||||
|
@ -1702,6 +1706,9 @@ class RecurringFillslots(APIView):
|
|||
|
||||
# exclude full events
|
||||
full_events = list(events_to_book.filter(full=True))
|
||||
# don't reload agendas and events types
|
||||
for event in full_events:
|
||||
event.agenda = agendas_by_id[event.agenda_id]
|
||||
events_to_book = events_to_book.filter(full=False)
|
||||
|
||||
events_to_book = events_to_book.annotate(
|
||||
|
@ -1739,8 +1746,13 @@ class RecurringFillslots(APIView):
|
|||
'full_events': [get_event_detail(request, x, multiple_agendas=True) for x in full_events],
|
||||
}
|
||||
if payload.get('include_booked_events_detail'):
|
||||
# don't reload agendas and events types
|
||||
for event in events_to_book:
|
||||
event.agenda = agendas_by_id[event.agenda_id]
|
||||
events_to_book_by_id = {x.id: x for x in events_to_book}
|
||||
response['booked_events'] = [
|
||||
get_event_detail(request, x.event, booking=x, multiple_agendas=True) for x in created_bookings
|
||||
get_event_detail(request, events_to_book_by_id[x.event_id], booking=x, multiple_agendas=True)
|
||||
for x in created_bookings
|
||||
]
|
||||
return Response(response)
|
||||
|
||||
|
@ -1805,7 +1817,9 @@ class EventsFillslots(APIView):
|
|||
multiple_agendas = False
|
||||
|
||||
def post(self, request, agenda_identifier):
|
||||
self.agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events')
|
||||
self.agenda = get_object_or_404(
|
||||
Agenda.objects.select_related('events_type'), slug=agenda_identifier, kind='events'
|
||||
)
|
||||
return self.fillslots(request)
|
||||
|
||||
def fillslots(self, request):
|
||||
|
@ -1892,18 +1906,26 @@ class EventsFillslots(APIView):
|
|||
# create missing bookings
|
||||
created_bookings = Booking.objects.bulk_create(bookings)
|
||||
|
||||
# don't reload agendas and events types
|
||||
for event in events:
|
||||
event.agenda = agendas_by_ids[event.agenda_id]
|
||||
events_by_id = {x.id: x for x in events}
|
||||
response = {
|
||||
'err': 0,
|
||||
'booking_count': len(bookings),
|
||||
'booked_events': [
|
||||
get_event_detail(request, x.event, booking=x, multiple_agendas=self.multiple_agendas)
|
||||
get_event_detail(
|
||||
request, events_by_id[x.event_id], booking=x, multiple_agendas=self.multiple_agendas
|
||||
)
|
||||
for x in created_bookings
|
||||
if x.event.pk not in waiting_list_event_ids
|
||||
if x.event_id not in waiting_list_event_ids
|
||||
],
|
||||
'waiting_list_events': [
|
||||
get_event_detail(request, x.event, booking=x, multiple_agendas=self.multiple_agendas)
|
||||
get_event_detail(
|
||||
request, events_by_id[x.event_id], booking=x, multiple_agendas=self.multiple_agendas
|
||||
)
|
||||
for x in created_bookings
|
||||
if x.event.pk in waiting_list_event_ids
|
||||
if x.event_id in waiting_list_event_ids
|
||||
],
|
||||
'cancelled_booking_count': cancelled_count,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.test import override_settings
|
|||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils.timezone import localtime, make_aware, make_naive, now
|
||||
|
||||
from chrono.agendas.models import Agenda, Booking, Desk, Event, TimePeriodException
|
||||
from chrono.agendas.models import Agenda, Booking, Desk, Event, EventsType, TimePeriodException
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -177,7 +177,7 @@ def test_datetime_api_backoffice_url(app, admin_user):
|
|||
assert event.label in app.get(url).text
|
||||
|
||||
|
||||
def test_datetime_api_min_places(app):
|
||||
def test_datetimes_api_min_places(app):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
event = Event.objects.create(start_datetime=now() + datetime.timedelta(days=7), places=5, agenda=agenda)
|
||||
|
||||
|
@ -198,6 +198,35 @@ def test_datetime_api_min_places(app):
|
|||
assert resp.json['err'] == 1
|
||||
|
||||
|
||||
def test_datetimes_api_(app):
|
||||
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', kind='events', events_type=events_type)
|
||||
Event.objects.create(
|
||||
slug='event-slug',
|
||||
start_datetime=localtime(now() + datetime.timedelta(days=5)).replace(hour=17, minute=0),
|
||||
places=5,
|
||||
agenda=agenda,
|
||||
custom_fields={
|
||||
'text': 'foo',
|
||||
'textarea': 'foo bar',
|
||||
'bool': True,
|
||||
},
|
||||
)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
||||
assert len(ctx.captured_queries) == 2
|
||||
assert resp.json['data'][0]['custom_field_text'] == 'foo'
|
||||
assert resp.json['data'][0]['custom_field_textarea'] == 'foo bar'
|
||||
assert resp.json['data'][0]['custom_field_bool'] is True
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-02-23')
|
||||
def test_datetimes_api_exclude_slots(app):
|
||||
agenda = Agenda.objects.create(
|
||||
|
|
|
@ -15,6 +15,11 @@ def test_status(app, user):
|
|||
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
custom_fields={
|
||||
'text': 'foo',
|
||||
'textarea': 'foo bar',
|
||||
'bool': True,
|
||||
},
|
||||
)
|
||||
agenda2 = Agenda.objects.create(label='Foo bar2', kind='events', minimal_booking_delay=0)
|
||||
# other event with the same slug but in another agenda
|
||||
|
@ -31,9 +36,6 @@ def test_status(app, user):
|
|||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug))
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['places']['total'] == 10
|
||||
assert resp.json['places']['available'] == 9
|
||||
assert resp.json['places']['reserved'] == 1
|
||||
assert resp.json == {
|
||||
'err': 0,
|
||||
'id': 'event-slug',
|
||||
|
@ -59,6 +61,44 @@ def test_status(app, user):
|
|||
}
|
||||
assert 'waiting_list_total' not in resp.json['places']
|
||||
|
||||
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()
|
||||
resp = app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug))
|
||||
assert resp.json == {
|
||||
'err': 0,
|
||||
'id': 'event-slug',
|
||||
'slug': 'event-slug',
|
||||
'text': str(event),
|
||||
'label': '',
|
||||
'agenda_label': 'Foo bar',
|
||||
'date': localtime(event.start_datetime).strftime('%Y-%m-%d'),
|
||||
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'description': None,
|
||||
'pricing': None,
|
||||
'url': None,
|
||||
'disabled': False,
|
||||
'duration': None,
|
||||
'custom_field_text': 'foo',
|
||||
'custom_field_textarea': 'foo bar',
|
||||
'custom_field_bool': True,
|
||||
'api': {
|
||||
'bookings_url': 'http://testserver/api/agenda/foo-bar/bookings/event-slug/',
|
||||
'fillslot_url': 'http://testserver/api/agenda/foo-bar/fillslot/event-slug/',
|
||||
'status_url': 'http://testserver/api/agenda/foo-bar/status/event-slug/',
|
||||
'check_url': 'http://testserver/api/agenda/foo-bar/check/event-slug/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk),
|
||||
},
|
||||
'places': {'available': 9, 'reserved': 1, 'total': 10, 'full': False, 'has_waiting_list': False},
|
||||
}
|
||||
|
||||
Booking(event=event, in_waiting_list=True).save()
|
||||
event.waiting_list_places = 5
|
||||
event.save()
|
||||
|
|
|
@ -14,6 +14,7 @@ from chrono.agendas.models import (
|
|||
Category,
|
||||
Desk,
|
||||
Event,
|
||||
EventsType,
|
||||
MeetingType,
|
||||
Person,
|
||||
Resource,
|
||||
|
@ -2381,11 +2382,13 @@ def test_fillslot_past_events_recurring_event(app, user):
|
|||
@pytest.mark.parametrize('action', ['book', 'update'])
|
||||
def test_recurring_events_api_fillslots(app, user, freezer, action):
|
||||
freezer.move_to('2021-09-06 12:00')
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
agenda = Agenda.objects.create(
|
||||
label='Foo bar',
|
||||
kind='events',
|
||||
minimal_booking_delay=7,
|
||||
maximal_booking_delay=35, # only 4 bookable weeks
|
||||
events_type=events_type,
|
||||
)
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
event = Event.objects.create(
|
||||
|
@ -2459,7 +2462,9 @@ def test_recurring_events_api_fillslots(app, user, freezer, action):
|
|||
assert events.filter(booked_waiting_list_places=1).count() == 1
|
||||
|
||||
params['user_external_id'] = 'user_id_3'
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
assert len(ctx.captured_queries) in [12, 13]
|
||||
# everything goes in waiting list
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 6
|
||||
# but an event was full
|
||||
|
@ -3619,8 +3624,9 @@ def test_recurring_events_api_fillslots_multiple_agendas(app, user):
|
|||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
for i in range(20):
|
||||
agenda = Agenda.objects.create(slug=f'{i}', kind='events')
|
||||
agenda = Agenda.objects.create(slug=f'{i}', kind='events', events_type=events_type)
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
start, end = now(), now() + datetime.timedelta(days=30)
|
||||
event = Event.objects.create(
|
||||
|
@ -3636,7 +3642,11 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
|||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json(
|
||||
'/api/agendas/recurring-events/fillslots/?action=update&agendas=%s' % agenda_slugs,
|
||||
params={'slots': events_to_book, 'user_external_id': 'user'},
|
||||
params={
|
||||
'slots': events_to_book,
|
||||
'user_external_id': 'user',
|
||||
'include_booked_events_detail': True,
|
||||
},
|
||||
)
|
||||
assert resp.json['booking_count'] == 180
|
||||
assert len(ctx.captured_queries) == 13
|
||||
|
@ -3662,7 +3672,8 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
|||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_api_events_fillslots(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events', events_type=events_type)
|
||||
event = Event.objects.create(
|
||||
label='Event',
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
|
@ -3680,7 +3691,9 @@ def test_api_events_fillslots(app, user):
|
|||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
fillslots_url = '/api/agenda/%s/events/fillslots/' % agenda.slug
|
||||
params = {'user_external_id': 'user_id', 'slots': 'event,event-2'}
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
assert len(ctx.captured_queries) == 11
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert len(resp.json['booked_events']) == 2
|
||||
assert resp.json['booked_events'][0]['id'] == 'event'
|
||||
|
@ -4082,7 +4095,8 @@ def test_api_events_fillslots_preserve_out_of_delays_bookings(app, user, freezer
|
|||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_api_events_fillslots_multiple_agendas(app, user):
|
||||
first_agenda = Agenda.objects.create(label='First agenda', kind='events')
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
first_agenda = Agenda.objects.create(label='First agenda', kind='events', events_type=events_type)
|
||||
Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder')
|
||||
first_event = Event.objects.create(
|
||||
label='Event',
|
||||
|
@ -4106,7 +4120,9 @@ def test_api_events_fillslots_multiple_agendas(app, user):
|
|||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
params = {'user_external_id': 'user_id', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
|
||||
assert len(ctx.captured_queries) == 16
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert len(resp.json['booked_events']) == 2
|
||||
assert resp.json['booked_events'][0]['id'] == 'first-agenda@event'
|
||||
|
|
|
@ -11,6 +11,7 @@ from chrono.agendas.models import (
|
|||
Category,
|
||||
Desk,
|
||||
Event,
|
||||
EventsType,
|
||||
Person,
|
||||
SharedCustodyAgenda,
|
||||
SharedCustodyHolidayRule,
|
||||
|
@ -359,9 +360,12 @@ def test_datetimes_multiple_agendas_sort(app):
|
|||
|
||||
@pytest.mark.freeze_time('2021-05-06 14:00')
|
||||
def test_datetimes_multiple_agendas_queries(app):
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
category = Category.objects.create(label='Category A')
|
||||
for i in range(10):
|
||||
agenda = Agenda.objects.create(label=str(i), kind='events', category=category)
|
||||
agenda = Agenda.objects.create(
|
||||
label=str(i), kind='events', category=category, events_type=events_type
|
||||
)
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='xxx',
|
||||
|
|
|
@ -10,6 +10,7 @@ from chrono.agendas.models import (
|
|||
Category,
|
||||
Desk,
|
||||
Event,
|
||||
EventsType,
|
||||
Person,
|
||||
SharedCustodyAgenda,
|
||||
SharedCustodyHolidayRule,
|
||||
|
@ -300,9 +301,10 @@ def test_recurring_events_api_list_multiple_agendas(app):
|
|||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_recurring_events_api_list_multiple_agendas_queries(app):
|
||||
events_type = EventsType.objects.create(label='Foo')
|
||||
category = Category.objects.create(label='Category A')
|
||||
for i in range(20):
|
||||
agenda = Agenda.objects.create(slug=f'{i}', kind='events', category=category)
|
||||
agenda = Agenda.objects.create(slug=f'{i}', kind='events', category=category, events_type=events_type)
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
start, end = now(), now() + datetime.timedelta(days=30)
|
||||
event = Event.objects.create(
|
||||
|
|
Loading…
Reference in New Issue