api: add support for subscriptions in multiple agendas fillslots (#58446)
This commit is contained in:
parent
97b0b899af
commit
2e7d87ffe7
|
@ -95,9 +95,7 @@ class MultipleAgendasEventsSlotsSerializer(EventsSlotsSerializer):
|
|||
extra_agendas = slots_agenda_slugs - set(allowed_agenda_slugs)
|
||||
if extra_agendas:
|
||||
extra_agendas = ', '.join(sorted(extra_agendas))
|
||||
raise ValidationError(
|
||||
_('Some events belong to agendas that are not present in querystring: %s' % extra_agendas)
|
||||
)
|
||||
raise ValidationError(_('Events from the following agendas cannot be booked: %s') % extra_agendas)
|
||||
return value
|
||||
|
||||
|
||||
|
@ -214,7 +212,7 @@ class AgendaOrSubscribedSlugsMixin(metaclass=serializers.SerializerMetaclass):
|
|||
raise ValidationError(_('Either "agendas" or "subscribed" parameter is required.'))
|
||||
if 'agendas' in attrs and 'subscribed' in attrs:
|
||||
raise ValidationError(_('"agendas" and "subscribed" parameters are mutually exclusive.'))
|
||||
user_external_id = attrs.get('user_external_id')
|
||||
user_external_id = attrs.get('user_external_id', self.context.get('user_external_id'))
|
||||
if 'subscribed' in attrs and not user_external_id:
|
||||
raise ValidationError(
|
||||
{'user_external_id': _('This field is required when using "subscribed" parameter.')}
|
||||
|
@ -225,6 +223,7 @@ class AgendaOrSubscribedSlugsMixin(metaclass=serializers.SerializerMetaclass):
|
|||
if attrs['subscribed'] != ['all']:
|
||||
agendas = agendas.filter(category__slug__in=attrs['subscribed'])
|
||||
attrs['agendas'] = agendas
|
||||
attrs['agenda_slugs'] = [agenda.slug for agenda in agendas]
|
||||
else:
|
||||
attrs['agenda_slugs'] = self.agenda_slugs
|
||||
return attrs
|
||||
|
@ -238,6 +237,10 @@ class MultipleAgendasDatetimesSerializer(AgendaOrSubscribedSlugsMixin, Datetimes
|
|||
show_past_events = serializers.BooleanField(default=False)
|
||||
|
||||
|
||||
class AgendaOrSubscribedSlugsSerializer(AgendaOrSubscribedSlugsMixin, serializers.Serializer):
|
||||
pass
|
||||
|
||||
|
||||
class AgendaSlugsSerializer(serializers.Serializer):
|
||||
agendas = CommaSeparatedStringField(
|
||||
required=True, child=serializers.SlugField(max_length=160, allow_blank=False)
|
||||
|
|
|
@ -1720,8 +1720,16 @@ class MultipleAgendasEventsFillslots(EventsFillslots):
|
|||
serializer_class = serializers.MultipleAgendasEventsSlotsSerializer
|
||||
|
||||
def post(self, request):
|
||||
self.agenda_slugs = get_agendas_from_request(request)
|
||||
self.agendas = get_objects_from_slugs(self.agenda_slugs, qs=Agenda.objects.filter(kind='events'))
|
||||
serializer = serializers.AgendaOrSubscribedSlugsSerializer(
|
||||
data=request.query_params, context={'user_external_id': request.data.get('user_external_id')}
|
||||
)
|
||||
if not serializer.is_valid():
|
||||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
data = serializer.validated_data
|
||||
|
||||
self.agendas = data['agendas']
|
||||
self.agenda_slugs = data['agenda_slugs']
|
||||
|
||||
return self.fillslots(request)
|
||||
|
||||
def get_events(self, request, payload):
|
||||
|
@ -1738,6 +1746,20 @@ class MultipleAgendasEventsFillslots(EventsFillslots):
|
|||
for agenda_slug, event_slugs in events_by_agenda.items():
|
||||
events |= get_events_from_slots(event_slugs, request, agendas_by_slug[agenda_slug], payload)
|
||||
|
||||
if 'subscribed' in request.query_params:
|
||||
events_outside_subscriptions = events.difference(
|
||||
events.filter(
|
||||
agenda__subscriptions__user_external_id=payload['user_external_id'],
|
||||
agenda__subscriptions__date_start__lt=F('start_datetime'),
|
||||
agenda__subscriptions__date_end__gt=F('start_datetime'),
|
||||
)
|
||||
) # workaround exclude method bug https://code.djangoproject.com/ticket/29697
|
||||
if events_outside_subscriptions.exists():
|
||||
event_slugs = ', '.join(
|
||||
'%s@%s' % (event.agenda.slug, event.slug) for event in events_outside_subscriptions
|
||||
)
|
||||
raise APIErrorBadRequest(N_('Some events are outside user subscriptions: %s'), event_slugs)
|
||||
|
||||
return events
|
||||
|
||||
def get_already_booked_events(self, user_external_id):
|
||||
|
|
|
@ -11,10 +11,12 @@ from chrono.agendas.models import (
|
|||
Agenda,
|
||||
Booking,
|
||||
BookingColor,
|
||||
Category,
|
||||
Desk,
|
||||
Event,
|
||||
MeetingType,
|
||||
Resource,
|
||||
Subscription,
|
||||
TimePeriod,
|
||||
VirtualMember,
|
||||
)
|
||||
|
@ -2416,7 +2418,7 @@ def test_recurring_events_api_fillslots_multiple_agendas(app, user):
|
|||
resp = app.post_json(fillslots_url % 'second-agenda', params=params, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['errors']['slots'] == [
|
||||
'Some events belong to agendas that are not present in querystring: first-agenda'
|
||||
'Events from the following agendas cannot be booked: first-agenda'
|
||||
]
|
||||
|
||||
|
||||
|
@ -2671,19 +2673,19 @@ def test_api_events_fillslots_multiple_agendas(app, user):
|
|||
resp = app.post_json(
|
||||
'/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400
|
||||
)
|
||||
assert resp.json['errors']['slots'] == [
|
||||
'Some events belong to agendas that are not present in querystring: xxx, yyy'
|
||||
]
|
||||
assert resp.json['errors']['slots'] == ['Events from the following agendas cannot be booked: xxx, yyy']
|
||||
|
||||
# missing agendas parameter
|
||||
resp = app.post_json('/api/agendas/events/fillslots/', params=params, status=400)
|
||||
assert resp.json['errors']['agendas'] == ['This field is required.']
|
||||
assert resp.json['errors']['non_field_errors'] == [
|
||||
'Either "agendas" or "subscribed" parameter is required.'
|
||||
]
|
||||
|
||||
# valid agendas parameter and event slugs, but mismatch between the two
|
||||
params = {'user_external_id': 'user_id_3', 'slots': event_slugs}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400)
|
||||
assert resp.json['errors']['slots'] == [
|
||||
'Some events belong to agendas that are not present in querystring: second-agenda'
|
||||
'Events from the following agendas cannot be booked: second-agenda'
|
||||
]
|
||||
|
||||
# missing @ in slot
|
||||
|
@ -2754,6 +2756,129 @@ def test_api_events_fillslots_multiple_agendas_check_delays(app, user):
|
|||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
def test_api_events_fillslots_multiple_agendas_subscribed(app, user):
|
||||
category = Category.objects.create(label='Category A')
|
||||
first_agenda = Agenda.objects.create(label='First agenda', kind='events', category=category)
|
||||
second_agenda = Agenda.objects.create(label='Second agenda', kind='events', category=category)
|
||||
category = Category.objects.create(label='Category B')
|
||||
third_agenda = Agenda.objects.create(label='Third agenda', kind='events', category=category)
|
||||
for agenda in Agenda.objects.all():
|
||||
Event.objects.create(
|
||||
slug='event',
|
||||
start_datetime=now() + datetime.timedelta(days=5),
|
||||
places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
Event.objects.create(
|
||||
slug='event-2',
|
||||
start_datetime=now() + datetime.timedelta(days=20),
|
||||
places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
|
||||
# add subscriptions to first and second agenda
|
||||
for agenda in (first_agenda, second_agenda):
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='xxx',
|
||||
date_start=now(),
|
||||
date_end=now() + datetime.timedelta(days=10),
|
||||
)
|
||||
|
||||
# book events
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params)
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 1
|
||||
assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
|
||||
assert Booking.objects.count() == 2
|
||||
|
||||
# update bookings for category-a
|
||||
params = {'user_external_id': 'xxx', 'slots': 'second-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params)
|
||||
assert resp.json['booking_count'] == 0
|
||||
assert resp.json['cancelled_booking_count'] == 1
|
||||
assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 0
|
||||
assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
|
||||
assert Booking.objects.count() == 1
|
||||
|
||||
# try to book event from agenda with no subscription TODO messages
|
||||
params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
|
||||
for slug in ('all', 'category-a', 'category-b'):
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=%s' % slug, params=params, status=400)
|
||||
assert (
|
||||
resp.json['errors']['slots'][0]
|
||||
== 'Events from the following agendas cannot be booked: third-agenda'
|
||||
)
|
||||
|
||||
# add subscription to third agenda
|
||||
Subscription.objects.create(
|
||||
agenda=third_agenda,
|
||||
user_external_id='xxx',
|
||||
date_start=now(),
|
||||
date_end=now() + datetime.timedelta(days=10),
|
||||
)
|
||||
params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-b', params=params)
|
||||
assert resp.json['booking_count'] == 1
|
||||
assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 0
|
||||
assert Event.objects.get(agenda=second_agenda, slug='event').booking_set.count() == 1
|
||||
assert Event.objects.get(agenda=third_agenda, slug='event').booking_set.count() == 1
|
||||
assert Booking.objects.count() == 2
|
||||
|
||||
# add subscription to first agenda (disjoint) spanning event-2
|
||||
for agenda in (first_agenda, second_agenda):
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='xxx',
|
||||
date_start=now() + datetime.timedelta(days=15),
|
||||
date_end=now() + datetime.timedelta(days=25),
|
||||
)
|
||||
# book event-2 while updating all bookings
|
||||
params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event-2'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params)
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert resp.json['cancelled_booking_count'] == 2
|
||||
assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 1
|
||||
assert Event.objects.get(agenda=second_agenda, slug='event-2').booking_set.count() == 1
|
||||
assert Booking.objects.count() == 2
|
||||
|
||||
# other user
|
||||
for agenda in (first_agenda, second_agenda):
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='yyy',
|
||||
date_start=now(),
|
||||
date_end=now() + datetime.timedelta(days=25),
|
||||
)
|
||||
params = {'user_external_id': 'yyy', 'slots': 'first-agenda@event,second-agenda@event-2'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params)
|
||||
assert resp.json['booking_count'] == 2
|
||||
assert resp.json['cancelled_booking_count'] == 0
|
||||
assert Event.objects.get(agenda=first_agenda, slug='event').booking_set.count() == 2
|
||||
assert Event.objects.get(agenda=second_agenda, slug='event-2').booking_set.count() == 2
|
||||
assert Booking.objects.count() == 4
|
||||
|
||||
# try to book event outside subscription date range
|
||||
params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event-2'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400)
|
||||
assert resp.json['err_class'] == 'Some events are outside user subscriptions: third-agenda@event-2'
|
||||
|
||||
# mismatch between subscribed parameter and event
|
||||
params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params, status=400)
|
||||
assert (
|
||||
resp.json['errors']['slots'][0] == 'Events from the following agendas cannot be booked: third-agenda'
|
||||
)
|
||||
|
||||
# missing user_external_id
|
||||
params = {'slots': 'third-agenda@event'}
|
||||
resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400)
|
||||
assert 'required' in resp.json['errors']['user_external_id'][0]
|
||||
|
||||
|
||||
def test_url_translation(app, some_data, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
agenda_id = Agenda.objects.filter(label='Foo bar')[0].id
|
||||
|
|
Loading…
Reference in New Issue