Booking: n'importe quoi dans extra_data (TypeError: unhashable type: 'list') (#74872) #43

Merged
lguerin merged 2 commits from wip/74872-api-extra-data into main 2023-03-02 14:36:32 +01:00
9 changed files with 122 additions and 11 deletions

View File

@ -721,6 +721,16 @@ def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_li
)
def get_extra_data(request, payload):
extra_data = {}
for k, v in request.data.items():
if k not in payload:
if isinstance(v, (list, dict)):
raise APIErrorBadRequest(N_('wrong type for extra_data %s value') % k)
extra_data[k] = v
return extra_data
class Agendas(APIView):
serializer_class = serializers.AgendaSerializer
@ -1412,10 +1422,7 @@ class Fillslots(APIView):
if cancel_error:
raise APIError(N_(cancel_error))
extra_data = {}
for k, v in request.data.items():
if k not in serializer.validated_data:
extra_data[k] = v
extra_data = get_extra_data(request, serializer.validated_data)
available_desk = None
color = None
@ -1791,7 +1798,7 @@ class RecurringFillslots(APIView):
)
)
extra_data = {k: v for k, v in request.data.items() if k not in payload}
extra_data = get_extra_data(request, payload)
# don't reload agendas and events types
for event in events_to_book:
event.agenda = agendas_by_id[event.agenda_id]
@ -2001,7 +2008,7 @@ class EventsFillslots(APIView):
)
waiting_list_event_ids = [event.pk for event in events if event.in_waiting_list]
extra_data = {k: v for k, v in request.data.items() if k not in payload}
extra_data = get_extra_data(request, payload)
bookings = [make_booking(event, payload, extra_data) for event in events]
bookings_to_cancel_out_of_min_delay = Booking.objects.filter(
@ -2336,7 +2343,7 @@ class SubscriptionsAPI(ListAPIView):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
extra_data = {k: v for k, v in request.data.items() if k not in serializer.validated_data}
extra_data = get_extra_data(request, serializer.validated_data)
date_start = serializer.validated_data['date_start']
date_end = serializer.validated_data['date_end']
@ -2607,7 +2614,7 @@ class BookingAPI(APIView):
raise APIErrorBadRequest(N_('user is marked as absent, can not set presence reason'), err=6)
serializer.save()
extra_data = {k: v for k, v in request.data.items() if k not in serializer.validated_data}
extra_data = get_extra_data(request, serializer.validated_data)
if extra_data:
self.booking.extra_data = self.booking.extra_data or {}
self.booking.extra_data.update(extra_data)

View File

@ -2543,7 +2543,7 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
).values_list('extra_data', flat=True)
for extra_data in list(extra_data_from_booked) + list(extra_data_from_subscriptions):
for k, v in extra_data.items():
if k in agenda_filters:
if k in agenda_filters and not isinstance(v, (list, dict)):
filters[k].add(v)
filters = sorted(filters.items())
filters = {k: sorted(list(v)) for k, v in filters}

View File

@ -152,6 +152,22 @@ def test_booking_api(app, user):
assert len(resp.json['errors']) == 1
assert 'user_last_name' in resp.json['errors']
# extra_data list/dict values are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'foo': ['bar', 'baz']},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'foo': {'bar': 'baz'}},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
# test parameters wrongly passed in query are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/?backoffice_url=https://example.com&label=test' % (agenda.id, event.id),
@ -540,6 +556,22 @@ def test_booking_api_fillslots(app, user):
assert len(resp.json['errors']) == 1
assert 'user_last_name' in resp.json['errors']
# extra_data list/dict values are refused
resp = app.post_json(
'/api/agenda/%s/fillslots/' % agenda.id,
params={'slots': events_ids, 'foo': ['bar', 'baz']},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
resp = app.post_json(
'/api/agenda/%s/fillslots/' % agenda.id,
params={'slots': events_ids, 'foo': {'bar': 'baz'}},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
# empty or missing slots
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': []}, status=400)
assert resp.json['err'] == 1

View File

@ -122,6 +122,21 @@ def test_api_events_fillslots(app, user):
resp = app.post('/api/agenda/foobar/events/fillslots/', status=404)
resp = app.post('/api/agenda/0/events/fillslots/', status=404)
params = {'user_external_id': 'user_id', 'slots': 'event,event-2', 'foo': 'bar'}
resp = app.post_json(fillslots_url, params=params)
assert resp.json['booking_count'] == 2
assert [b.extra_data for b in Booking.objects.order_by('-pk')[:2]] == [{'foo': 'bar'}, {'foo': 'bar'}]
params.update({'foo': ['bar', 'baz']})
resp = app.post_json(fillslots_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
params.update({'foo': {'bar': 'baz'}})
resp = app.post_json(fillslots_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
@pytest.mark.freeze_time('2021-09-06 12:00')
def test_api_events_fillslots_with_cancelled(app, user):

View File

@ -251,6 +251,25 @@ def test_api_events_fillslots_multiple_agendas(app, user):
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400)
assert resp.json['errors']['slots'] == ['Missing agenda slug in slot @event']
params = {'user_external_id': 'user_id', 'slots': event_slugs, 'foo': 'bar'}
resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params)
assert resp.json['booking_count'] == 1
assert Booking.objects.order_by('-pk')[0].extra_data == {'foo': 'bar'}
params.update({'foo': ['bar', 'baz']})
resp = app.post_json(
'/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
params.update({'foo': {'bar': 'baz'}})
resp = app.post_json(
'/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
@pytest.mark.freeze_time('2021-09-06 12:00')
def test_api_events_fillslots_multiple_agendas_with_cancelled(app, user):

View File

@ -169,6 +169,25 @@ def test_recurring_events_api_fillslots(app, user, freezer, action):
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['action'] == ['"invalid" is not a valid choice.']
params = {
'user_external_id': 'user_id',
'slots': 'foo-bar@event:1',
'foo': 'bar',
}
resp = app.post_json(fillslots_url, params=params)
assert resp.json['booking_count'] == 2
assert [b.extra_data for b in Booking.objects.order_by('-pk')[:2]] == [{'foo': 'bar'}, {'foo': 'bar'}]
params.update({'foo': ['bar', 'baz']})
resp = app.post_json(fillslots_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
params.update({'foo': {'bar': 'baz'}})
resp = app.post_json(fillslots_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
def test_recurring_events_api_fillslots_waiting_list(app, user, freezer):
freezer.move_to('2021-09-06 12:00')

View File

@ -1021,6 +1021,11 @@ def test_booking_patch_api_extra_data(app, user):
assert booking.user_was_present is True # not changed
assert booking.extra_data == {'foo': None, 'foooo': 'baz'} # not changed
resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'foo': ['bar', 'baz']}, status=400)
assert resp.json['err_desc'] == 'wrong type for extra_data foo value'
resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'foo': {'bar': 'baz'}}, status=400)
assert resp.json['err_desc'] == 'wrong type for extra_data foo value'
def test_booking_cancellation_api(app, user):
agenda = Agenda.objects.create(kind='events')

View File

@ -224,6 +224,20 @@ def test_api_create_subscription(app, user):
resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400)
assert resp.json['errors']['non_field_errors'][0] == 'start_datetime must be before end_datetime'
params = {
'user_external_id': 'xxx',
'user_first_name': 'Foo',
'user_last_name': 'BAR',
'date_start': '2021-10-01',
'date_end': '2021-11-01',
'foo': ['bar', 'baz'],
}
resp = app.post_json('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400)
assert resp.json['err_desc'] == 'wrong type for extra_data foo value'
params.update({'foo': {'bar': 'baz'}})
resp = app.post_json('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400)
assert resp.json['err_desc'] == 'wrong type for extra_data foo value'
def test_api_create_subscription_check_dates(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')

View File

@ -1758,7 +1758,7 @@ def test_event_check_filters(check_types, app, admin_user):
user_external_id='user:1',
user_first_name='User',
user_last_name='foo-val1 bar-none presence',
extra_data={'foo': 'val1'},
extra_data={'foo': 'val1', 'bar': ['val1']}, # bar is ignored, wrong value
user_was_present=True,
)
Booking.objects.create(
@ -1837,7 +1837,7 @@ def test_event_check_filters(check_types, app, admin_user):
user_external_id='subscription:1',
user_first_name='Subscription',
user_last_name='foo-val1 bar-none',
extra_data={'foo': 'val1'},
extra_data={'foo': 'val1', 'bar': ['val1']}, # bar is ignored, wrong value
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=1),
)