0001-api-move-MultipleAgendasEventsFillslots-validation-t.patch
chrono/api/serializers.py | ||
---|---|---|
64 | 64 |
return attrs |
65 | 65 | |
66 | 66 | |
67 |
class MultipleAgendasEventsSlotsSerializer(EventsSlotsSerializer): |
|
68 |
def validate_slots(self, value): |
|
69 |
allowed_agenda_slugs = self.context['allowed_agenda_slugs'] |
|
70 |
slots_agenda_slugs = set() |
|
71 |
for slot in value: |
|
72 |
try: |
|
73 |
agenda_slug, event_slug = slot.split('@') |
|
74 |
except ValueError: |
|
75 |
raise ValidationError(_('Invalid format for slot %s') % slot) |
|
76 |
if not agenda_slug: |
|
77 |
raise ValidationError(_('Missing agenda slug in slot %s') % slot) |
|
78 |
if not event_slug: |
|
79 |
raise ValidationError(_('Missing event slug in slot %s') % slot) |
|
80 |
slots_agenda_slugs.add(agenda_slug) |
|
81 | ||
82 |
extra_agendas = slots_agenda_slugs - set(allowed_agenda_slugs) |
|
83 |
if extra_agendas: |
|
84 |
extra_agendas = ', '.join(sorted(extra_agendas)) |
|
85 |
raise ValidationError( |
|
86 |
_('Some events belong to agendas that are not present in querystring: %s' % extra_agendas) |
|
87 |
) |
|
88 |
return value |
|
89 | ||
90 | ||
67 | 91 |
class BookingSerializer(serializers.ModelSerializer): |
68 | 92 |
user_absence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True) |
69 | 93 | |
... | ... | |
137 | 161 |
show_past_events = serializers.BooleanField(default=False) |
138 | 162 | |
139 | 163 | |
164 |
class AgendaSlugsSerializer(serializers.Serializer): |
|
165 |
agendas = CommaSeparatedStringField( |
|
166 |
required=True, child=serializers.SlugField(max_length=160, allow_blank=False) |
|
167 |
) |
|
168 | ||
169 | ||
140 | 170 |
class EventSerializer(serializers.ModelSerializer): |
141 | 171 |
recurrence_days = CommaSeparatedStringField( |
142 | 172 |
required=False, child=serializers.IntegerField(min_value=0, max_value=6) |
chrono/api/views.py | ||
---|---|---|
675 | 675 |
return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end') |
676 | 676 | |
677 | 677 | |
678 |
def get_agendas_from_request(request): |
|
679 |
serializer = serializers.AgendaSlugsSerializer(data=request.query_params) |
|
680 |
if not serializer.is_valid(): |
|
681 |
raise APIError( |
|
682 |
_('invalid payload'), |
|
683 |
err_class='invalid payload', |
|
684 |
errors=serializer.errors, |
|
685 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
686 |
) |
|
687 | ||
688 |
return serializer.validated_data.get('agendas') |
|
689 | ||
690 | ||
678 | 691 |
def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_list=False, color=None): |
679 | 692 |
return Booking( |
680 | 693 |
event_id=event.pk, |
... | ... | |
1664 | 1677 |
class EventsFillslots(APIView): |
1665 | 1678 |
permission_classes = (permissions.IsAuthenticated,) |
1666 | 1679 |
serializer_class = serializers.EventsSlotsSerializer |
1680 |
serializer_extra_context = None |
|
1667 | 1681 | |
1668 | 1682 |
def post(self, request, agenda_identifier): |
1669 | 1683 |
self.agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events') |
... | ... | |
1672 | 1686 |
def fillslots(self, request): |
1673 | 1687 |
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request) |
1674 | 1688 | |
1675 |
serializer = self.serializer_class(data=request.data, partial=True) |
|
1689 |
serializer = self.serializer_class( |
|
1690 |
data=request.data, partial=True, context=self.serializer_extra_context |
|
1691 |
) |
|
1676 | 1692 |
if not serializer.is_valid(): |
1677 | 1693 |
raise APIError( |
1678 | 1694 |
_('invalid payload'), |
... | ... | |
1736 | 1752 | |
1737 | 1753 | |
1738 | 1754 |
class MultipleAgendasEventsFillslots(EventsFillslots): |
1755 |
serializer_class = serializers.MultipleAgendasEventsSlotsSerializer |
|
1756 | ||
1739 | 1757 |
def post(self, request): |
1740 |
self.agendas = None |
|
1741 |
if 'agendas' not in request.GET: |
|
1742 |
raise APIError( |
|
1743 |
_('Missing agendas list in querystring'), |
|
1744 |
err_class='Missing agendas list in querystring', |
|
1745 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
1746 |
) |
|
1747 |
self.agenda_slugs = [s for s in request.GET.get('agendas', '').split(',') if s] |
|
1758 |
self.agenda_slugs = get_agendas_from_request(request) |
|
1748 | 1759 |
self.agendas = get_objects_from_slugs(self.agenda_slugs, qs=Agenda.objects.filter(kind='events')) |
1749 | 1760 |
return self.fillslots(request) |
1750 | 1761 | |
... | ... | |
1754 | 1765 |
agenda, event = slot.split('@') |
1755 | 1766 |
events_by_agenda[agenda].append(event) |
1756 | 1767 | |
1757 |
agendas = get_objects_from_slugs(events_by_agenda.keys(), qs=Agenda.objects.filter(kind='events')) |
|
1758 |
agendas_by_slug = {agenda.slug: agenda for agenda in agendas} |
|
1759 | ||
1760 |
if not set(agendas_by_slug).issubset(self.agenda_slugs): |
|
1761 |
extra_agendas = set(agendas_by_slug) - set(self.agenda_slugs) |
|
1762 |
extra_agendas = ','.join(extra_agendas) |
|
1763 |
raise APIError( |
|
1764 |
_('Some events belong to agendas that are not present in querystring: %s' % extra_agendas), |
|
1765 |
err_class='Some events belong to agendas that are not present in querystring: %s' |
|
1766 |
% extra_agendas, |
|
1767 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
1768 |
) |
|
1768 |
agendas_by_slug = get_objects_from_slugs( |
|
1769 |
events_by_agenda.keys(), qs=Agenda.objects.filter(kind='events') |
|
1770 |
).in_bulk(field_name='slug') |
|
1769 | 1771 | |
1770 | 1772 |
events = Event.objects.none() |
1771 | 1773 |
for agenda_slug, event_slugs in events_by_agenda.items(): |
... | ... | |
1776 | 1778 |
def get_already_booked_events(self, user_external_id): |
1777 | 1779 |
return Event.objects.filter(agenda__in=self.agendas, booking__user_external_id=user_external_id) |
1778 | 1780 | |
1781 |
@property |
|
1782 |
def serializer_extra_context(self): |
|
1783 |
return {'allowed_agenda_slugs': self.agenda_slugs} |
|
1784 | ||
1779 | 1785 | |
1780 | 1786 |
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view() |
1781 | 1787 |
tests/api/test_fillslot.py | ||
---|---|---|
2487 | 2487 |
assert resp.json['err'] == 1 |
2488 | 2488 |
assert resp.json['err_desc'] == 'some events are full: Event' |
2489 | 2489 | |
2490 |
# invalid agenda slugs |
|
2490 |
# invalid agenda slugs in querystring
|
|
2491 | 2491 |
resp = app.post_json( |
2492 | 2492 |
'/api/agendas/events/fillslots/?agendas=first-agenda,xxx,yyy', params=params, status=400 |
2493 | 2493 |
) |
2494 | 2494 |
assert resp.json['err_desc'] == 'invalid slugs: xxx, yyy' |
2495 | 2495 | |
2496 |
# invalid agenda slugs in payload |
|
2496 | 2497 |
params = {'user_external_id': 'user_id_3', 'slots': 'first-agenda@event,xxx@event,yyy@event'} |
2497 | 2498 |
resp = app.post_json( |
2498 | 2499 |
'/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400 |
2499 | 2500 |
) |
2500 |
assert resp.json['err_desc'] == 'invalid slugs: xxx, yyy' |
|
2501 |
assert resp.json['errors']['slots'] == [ |
|
2502 |
'Some events belong to agendas that are not present in querystring: xxx, yyy' |
|
2503 |
] |
|
2501 | 2504 | |
2502 | 2505 |
# missing agendas parameter |
2503 | 2506 |
resp = app.post_json('/api/agendas/events/fillslots/', params=params, status=400) |
2504 |
assert resp.json['err_desc'] == 'Missing agendas list in querystring'
|
|
2507 |
assert resp.json['errors']['agendas'] == ['This field is required.']
|
|
2505 | 2508 | |
2506 | 2509 |
# valid agendas parameter and event slugs, but mismatch between the two |
2507 | 2510 |
params = {'user_external_id': 'user_id_3', 'slots': event_slugs} |
2508 | 2511 |
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) |
2509 |
assert ( |
|
2510 |
resp.json['err_desc'] |
|
2511 |
== 'Some events belong to agendas that are not present in querystring: second-agenda' |
|
2512 |
) |
|
2512 |
assert resp.json['errors']['slots'] == [ |
|
2513 |
'Some events belong to agendas that are not present in querystring: second-agenda' |
|
2514 |
] |
|
2515 | ||
2516 |
# missing @ in slot |
|
2517 |
params['slots'] = 'first-agenda' |
|
2518 |
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) |
|
2519 |
assert resp.json['errors']['slots'] == ['Invalid format for slot first-agenda'] |
|
2520 | ||
2521 |
# empty event slug |
|
2522 |
params['slots'] = 'first-agenda@' |
|
2523 |
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) |
|
2524 |
assert resp.json['errors']['slots'] == ['Missing event slug in slot first-agenda@'] |
|
2525 | ||
2526 |
# empty agenda slug |
|
2527 |
params['slots'] = '@event' |
|
2528 |
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) |
|
2529 |
assert resp.json['errors']['slots'] == ['Missing agenda slug in slot @event'] |
|
2513 | 2530 | |
2514 | 2531 | |
2515 | 2532 |
def test_url_translation(app, some_data, user): |
2516 |
- |