api: allow partial booking in all event fillslot endpoints (#80050)

This commit is contained in:
Valentin Deniaud 2023-08-08 17:59:41 +02:00
parent 9b340a01d6
commit 11ef5b4bd2
5 changed files with 128 additions and 15 deletions

View File

@ -95,6 +95,18 @@ class FillSlotSerializer(serializers.Serializer):
required=False, child=serializers.CharField(max_length=16, allow_blank=False)
)
check_overlaps = serializers.BooleanField(default=False)
start_time = serializers.TimeField(required=False)
end_time = serializers.TimeField(required=False)
def validate(self, attrs):
super().validate(attrs)
use_partial_bookings = any(agenda.partial_bookings for agenda in self.context.get('agendas', []))
if use_partial_bookings:
if not attrs.get('start_time') or not attrs.get('end_time'):
raise ValidationError(_('must include start_time and end_time for partial bookings agenda'))
if attrs['start_time'] > attrs['end_time']:
raise ValidationError(_('start_time must be before end_time'))
return attrs
class SlotsSerializer(serializers.Serializer):
@ -188,18 +200,6 @@ class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer):
check_overlaps = CommaSeparatedStringField(
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
)
start_time = serializers.TimeField(required=False)
end_time = serializers.TimeField(required=False)
def validate(self, attrs):
super().validate(attrs)
use_partial_bookings = any(agenda.partial_bookings for agenda in self.context['agendas'])
if use_partial_bookings:
if not attrs.get('start_time') or not attrs.get('end_time'):
raise ValidationError(_('must include start_time and end_time for partial bookings agenda'))
if attrs['start_time'] > attrs['end_time']:
raise ValidationError(_('start_time must be before end_time'))
return attrs
def validate_slots(self, value):
super().validate_slots(value)

View File

@ -1195,7 +1195,8 @@ class EventsAgendaFillslot(APIView):
N_('parameters "%s" must be included in request body, not query'), params
)
serializer = self.serializer_class(data=request.data, partial=True)
context = {'agendas': [agenda]}
serializer = self.serializer_class(data=request.data, partial=True, context=context)
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
payload = serializer.validated_data
@ -1842,7 +1843,6 @@ recurring_fillslots = RecurringFillslots.as_view()
class EventsFillslots(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.EventsFillSlotsSerializer
serializer_extra_context = None
multiple_agendas = False
def post(self, request, agenda_identifier):
@ -2017,6 +2017,10 @@ class EventsFillslots(APIView):
def get_agendas_by_ids(self):
return {self.agenda.pk: self.agenda}
@property
def serializer_extra_context(self):
return {'agendas': [self.agenda]}
events_fillslots = EventsFillslots.as_view()
@ -2096,7 +2100,7 @@ class MultipleAgendasEventsFillslots(EventsFillslots):
@property
def serializer_extra_context(self):
return {'allowed_agenda_slugs': self.agenda_slugs}
return {'allowed_agenda_slugs': self.agenda_slugs, 'agendas': self.agendas}
agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view()

View File

@ -2026,3 +2026,51 @@ def test_user_external_id(app, user):
assert not any(x['disabled'] for x in resp.json['data'])
meeting_event.delete()
@pytest.mark.freeze_time('2021-02-23 14:00')
def test_booking_api_partial_booking(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
event = Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=5),
duration=120,
places=1,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id),
params={'start_time': '10:00', 'end_time': '15:00'},
)
booking = Booking.objects.get()
assert booking.start_time == datetime.time(10, 00)
assert booking.end_time == datetime.time(15, 00)
# missing start_time
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id), params={'end_time': '10:00'}, status=400
)
assert (
resp.json['errors']['non_field_errors'][0]
== 'must include start_time and end_time for partial bookings agenda'
)
# missing end_time
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id), params={'start_time': '10:00'}, status=400
)
assert (
resp.json['errors']['non_field_errors'][0]
== 'must include start_time and end_time for partial bookings agenda'
)
# end before start
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id),
params={'start_time': '10:00', 'end_time': '09:00'},
status=400,
)
assert resp.json['errors']['non_field_errors'][0] == 'start_time must be before end_time'

View File

@ -556,3 +556,31 @@ def test_api_events_fillslots_exclude_user_forbidden(app, user):
resp = app.post_json(fillslots_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['errors']['exclude_user'][0] == 'This parameter is not supported.'
@pytest.mark.freeze_time('2021-02-23 14:00')
def test_api_events_fillslots_partial_bookings(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=5),
duration=120,
places=1,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
fillslots_url = '/api/agenda/foo-bar/events/fillslots/'
params = {'user_external_id': 'user_id', 'start_time': '10:00', 'end_time': '15:00', 'slots': 'event'}
resp = app.post_json(fillslots_url, params=params)
booking = Booking.objects.get()
assert booking.start_time == datetime.time(10, 00)
assert booking.end_time == datetime.time(15, 00)
del params['start_time']
resp = app.post_json(fillslots_url, params=params, status=400)
assert (
resp.json['errors']['non_field_errors'][0]
== 'must include start_time and end_time for partial bookings agenda'
)

View File

@ -795,3 +795,36 @@ def test_api_events_fillslots_multiple_agendas_overlapping_events(app, user, fre
params={'user_external_id': 'user_id', 'check_overlaps': True, 'slots': 'foo-bar-2@event-2'},
)
assert resp.json['booking_count'] == 1
@pytest.mark.freeze_time('2021-02-23 14:00')
def test_api_events_fillslots_multiple_agendas_partial_bookings(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=5),
duration=120,
places=1,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
fillslots_url = '/api/agendas/events/fillslots/?agendas=foo-bar'
params = {
'user_external_id': 'user_id',
'start_time': '10:00',
'end_time': '15:00',
'slots': 'foo-bar@event',
}
resp = app.post_json(fillslots_url, params=params)
booking = Booking.objects.get()
assert booking.start_time == datetime.time(10, 00)
assert booking.end_time == datetime.time(15, 00)
del params['start_time']
resp = app.post_json(fillslots_url, params=params, status=400)
assert (
resp.json['errors']['non_field_errors'][0]
== 'must include start_time and end_time for partial bookings agenda'
)