api: endpoint to get a list of serialized events (#66874)
This commit is contained in:
parent
03279b1b1c
commit
c9d2a4681c
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
Loading…
Reference in New Issue