api: endpoint to get a list of serialized events (#66874)

This commit is contained in:
Lauréline Guérin 2022-07-01 18:01:15 +02:00
parent 03279b1b1c
commit c9d2a4681c
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 212 additions and 11 deletions

View File

@ -67,7 +67,7 @@ class AgendaSlugsMixin(metaclass=serializers.SerializerMetaclass):
return Agenda.objects.filter(kind='events').select_related('events_type')
class SlotSerializer(serializers.Serializer):
class FillSlotSerializer(serializers.Serializer):
label = serializers.CharField(max_length=250, allow_blank=True)
user_external_id = serializers.CharField(max_length=250, allow_blank=True)
user_name = serializers.CharField(max_length=250, allow_blank=True) # compatibility
@ -95,7 +95,7 @@ class SlotSerializer(serializers.Serializer):
check_overlaps = serializers.BooleanField(default=False)
class SlotsSerializer(SlotSerializer):
class SlotsSerializer(serializers.Serializer):
slots = StringOrListField(required=True, child=serializers.CharField(max_length=160, allow_blank=False))
def validate(self, attrs):
@ -105,7 +105,11 @@ class SlotsSerializer(SlotSerializer):
return attrs
class EventsSlotsSerializer(SlotSerializer):
class FillSlotsSerializer(FillSlotSerializer, SlotsSerializer):
pass
class EventsFillSlotsSerializer(FillSlotSerializer):
slots = StringOrListField(required=True, child=serializers.CharField(max_length=160))
def validate(self, attrs):
@ -117,7 +121,7 @@ class EventsSlotsSerializer(SlotSerializer):
return attrs
class MultipleAgendasEventsSlotsSerializer(EventsSlotsSerializer):
class MultipleAgendasEventsFillSlotsSerializer(EventsFillSlotsSerializer):
def validate_slots(self, value):
allowed_agenda_slugs = self.context['allowed_agenda_slugs']
slots_agenda_slugs = set()
@ -151,7 +155,7 @@ class MultipleAgendasEventsCheckStatusSerializer(AgendaSlugsMixin, DateRangeMixi
return get_objects_from_slugs(value, qs=self.get_agenda_qs())
class RecurringFillslotsSerializer(MultipleAgendasEventsSlotsSerializer):
class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer):
include_booked_events_detail = serializers.BooleanField(default=False)
def validate_slots(self, value):

View File

@ -23,6 +23,11 @@ urlpatterns = [
url(r'^agendas/datetimes/$', views.agendas_datetimes, name='api-agendas-datetimes'),
url(r'^agendas/recurring-events/$', views.recurring_events_list, name='api-agenda-recurring-events'),
url(r'^agendas/recurring-events/fillslots/$', views.recurring_fillslots, name='api-recurring-fillslots'),
url(
r'^agendas/events/$',
views.agendas_events,
name='api-agendas-events',
),
url(
r'^agendas/events/fillslots/$',
views.agendas_events_fillslots,

View File

@ -676,12 +676,12 @@ def get_resources_from_request(request, agenda):
return list(get_objects_from_slugs(resources_slugs, qs=agenda.resources))
def get_objects_from_slugs(slugs, qs):
def get_objects_from_slugs(slugs, qs, prefix=''):
slugs = set(slugs)
objects = qs.filter(slug__in=slugs)
if len(objects) != len(slugs):
unknown_slugs = sorted(slugs - {obj.slug for obj in objects})
unknown_slugs = ', '.join(unknown_slugs)
unknown_slugs = ', '.join('%s%s' % (prefix, s) for s in unknown_slugs)
raise APIErrorBadRequest(N_('invalid slugs: %s'), unknown_slugs)
return objects
@ -1314,7 +1314,7 @@ agenda_resource_list = AgendaResourceList.as_view()
class Fillslots(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.SlotsSerializer
serializer_class = serializers.FillSlotsSerializer
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
@ -1633,7 +1633,7 @@ fillslots = Fillslots.as_view()
class Fillslot(Fillslots):
serializer_class = serializers.SlotSerializer
serializer_class = serializers.FillSlotSerializer
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
return self.fillslot(
@ -1857,7 +1857,7 @@ recurring_fillslots = RecurringFillslots.as_view()
class EventsFillslots(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.EventsSlotsSerializer
serializer_class = serializers.EventsFillSlotsSerializer
serializer_extra_context = None
multiple_agendas = False
@ -2019,7 +2019,7 @@ events_fillslots = EventsFillslots.as_view()
class MultipleAgendasEventsFillslots(EventsFillslots):
serializer_class = serializers.MultipleAgendasEventsSlotsSerializer
serializer_class = serializers.MultipleAgendasEventsFillSlotsSerializer
multiple_agendas = True
def post(self, request):
@ -2095,6 +2095,48 @@ class MultipleAgendasEventsFillslots(EventsFillslots):
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view()
class MultipleAgendasEvents(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.SlotsSerializer
def get(self, request):
serializer = self.serializer_class(data=request.query_params)
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=1)
slots = serializer.validated_data['slots']
events_by_agenda = collections.defaultdict(list)
for slot in slots:
agenda, event = slot.split('@')
events_by_agenda[agenda].append(event)
agendas = get_objects_from_slugs(events_by_agenda.keys(), qs=Agenda.objects.filter(kind='events'))
agendas_by_slug = {a.slug: a for a in agendas}
events = []
for agenda_slug, event_slugs in events_by_agenda.items():
events += get_objects_from_slugs(
event_slugs,
qs=agendas_by_slug[agenda_slug]
.event_set.filter(cancelled=False, recurrence_days__isnull=True)
.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
.order_by(),
prefix='%s@' % agenda_slug,
)
data = []
event_querystring_indexes = {event_slug: i for i, event_slug in enumerate(slots)}
events.sort(key=lambda event: (event_querystring_indexes['%s@%s' % (event.agenda.slug, event.slug)],))
for event in events:
data.append(serializers.EventSerializer(event).data)
return Response({'err': 0, 'data': data})
agendas_events = MultipleAgendasEvents.as_view()
class MultipleAgendasEventsCheckStatus(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.MultipleAgendasEventsCheckStatusSerializer

View File

@ -818,6 +818,156 @@ def test_delete_recurring_event_forbidden(app, user):
assert not Event.objects.exists()
@pytest.mark.freeze_time('2022-07-01 14:00')
def test_events(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
# missing slots
resp = app.get(
'/api/agendas/events/',
params={},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['slots'] == ['This field is required.']
agenda = Agenda.objects.create(label='Foo')
agenda2 = Agenda.objects.create(label='Bar')
start_datetime = now()
# recurring event
recurring_event = Event.objects.create(
slug='recurring-event-slug',
label='Recurring Event Label',
start_datetime=start_datetime,
recurrence_days=[start_datetime.weekday()],
recurrence_end_date=start_datetime + datetime.timedelta(days=8),
places=10,
agenda=agenda,
custom_fields={
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
},
)
recurring_event.create_all_recurrences()
Event.objects.create(
slug='event-slug',
label='Event Label',
start_datetime=start_datetime + datetime.timedelta(days=1),
places=10,
agenda=agenda2,
checked=True,
)
# cancelled event, not returned
Event.objects.create(
slug='cancelled',
label='Cancelled',
start_datetime=start_datetime,
places=10,
agenda=agenda,
cancelled=True,
)
# unknown event in list
resp = app.get(
'/api/agendas/events/',
params={'slots': ['foo@event-slug', 'foo@recurring-event-slug--2022-07-01-1600']},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid slugs: foo@event-slug'
# cancelled event in list
resp = app.get(
'/api/agendas/events/',
params={'slots': ['bar@event-slug', 'foo@recurring-event-slug--2022-07-01-1600', 'foo@cancelled']},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid slugs: foo@cancelled'
# primary event in list
resp = app.get(
'/api/agendas/events/',
params={
'slots': [
'bar@event-slug',
'foo@recurring-event-slug--2022-07-01-1600',
'foo@recurring-event-slug',
]
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid slugs: foo@recurring-event-slug'
# ok
resp = app.get(
'/api/agendas/events/',
params={
'slots': [
'bar@event-slug',
'foo@recurring-event-slug--2022-07-01-1600',
]
},
)
assert resp.json['data'] == [
{
'agenda': 'bar',
'description': None,
'duration': None,
'label': 'Event Label',
'places': 10,
'pricing': None,
'primary_event': None,
'publication_datetime': None,
'recurrence_days': None,
'recurrence_end_date': None,
'recurrence_week_interval': 1,
'slug': 'event-slug',
'start_datetime': '2022-07-02T16:00:00+02:00',
'url': None,
'waiting_list_places': 0,
},
{
'agenda': 'foo',
'description': None,
'duration': None,
'label': 'Recurring Event Label',
'places': 10,
'pricing': None,
'primary_event': 'recurring-event-slug',
'publication_datetime': None,
'recurrence_days': None,
'recurrence_end_date': None,
'recurrence_week_interval': 1,
'slug': 'recurring-event-slug--2022-07-01-1600',
'start_datetime': '2022-07-01T16:00:00+02:00',
'url': None,
'waiting_list_places': 0,
},
]
# result sorting ?
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/api/agendas/events/',
params={
'slots': [
'foo@recurring-event-slug--2022-07-01-1600',
'bar@event-slug',
'foo@recurring-event-slug--2022-07-08-1600',
]
},
)
assert len(ctx.captured_queries) == 6
assert [(d['agenda'], d['slug']) for d in resp.json['data']] == [
('foo', 'recurring-event-slug--2022-07-01-1600'),
('bar', 'event-slug'),
('foo', 'recurring-event-slug--2022-07-08-1600'),
]
def test_events_check_status_params(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))