api: remove legacy fillslots views (#80352)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
8127fbff66
commit
84463c84bf
|
@ -57,9 +57,6 @@ urlpatterns = [
|
|||
views.fillslot,
|
||||
name='api-fillslot',
|
||||
),
|
||||
re_path(
|
||||
r'^agenda/(?P<agenda_identifier>[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'
|
||||
),
|
||||
re_path(
|
||||
r'^agenda/(?P<agenda_identifier>[\w-]+)/events/fillslots/$',
|
||||
views.events_fillslots,
|
||||
|
|
|
@ -20,7 +20,6 @@ import datetime
|
|||
import json
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Prefetch, Q
|
||||
from django.db.models.expressions import RawSQL
|
||||
|
@ -113,9 +112,6 @@ def get_agenda_detail(request, agenda, check_events=False):
|
|||
),
|
||||
}
|
||||
)
|
||||
agenda_detail['api']['fillslots_url'] = request.build_absolute_uri(
|
||||
reverse('api-agenda-fillslots', kwargs={'agenda_identifier': agenda.slug})
|
||||
)
|
||||
agenda_detail['api']['backoffice_url'] = request.build_absolute_uri(
|
||||
reverse('chrono-manager-agenda-view', kwargs={'pk': agenda.pk})
|
||||
)
|
||||
|
@ -1179,20 +1175,14 @@ class AgendaResourceList(APIView):
|
|||
agenda_resource_list = AgendaResourceList.as_view()
|
||||
|
||||
|
||||
class EventsAgendaFillslots(APIView):
|
||||
class EventsAgendaFillslot(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.FillSlotsSerializer
|
||||
serializer_class = serializers.FillSlotSerializer
|
||||
|
||||
def post(self, request, agenda, slots):
|
||||
if not settings.LEGACY_FILLSLOTS_ENABLED and slots is None:
|
||||
raise APIErrorBadRequest(N_('deprecated call'))
|
||||
|
||||
return self.fillslot(request=request, agenda=agenda, slots=slots)
|
||||
|
||||
def fillslot(self, request, agenda, slots=None, retry=False):
|
||||
slots = slots or []
|
||||
multiple_booking = bool(not slots)
|
||||
def post(self, request, agenda, slot):
|
||||
return self.fillslot(request=request, agenda=agenda, slot=slot)
|
||||
|
||||
def fillslot(self, request, agenda, slot, retry=False):
|
||||
known_body_params = set(request.query_params).intersection(
|
||||
{'label', 'user_name', 'backoffice_url', 'user_display_label'}
|
||||
)
|
||||
|
@ -1207,9 +1197,6 @@ class EventsAgendaFillslots(APIView):
|
|||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
payload = serializer.validated_data
|
||||
|
||||
if 'slots' in payload:
|
||||
slots = payload['slots']
|
||||
|
||||
if 'count' in payload:
|
||||
places_count = payload['count']
|
||||
elif 'count' in request.query_params:
|
||||
|
@ -1253,29 +1240,28 @@ class EventsAgendaFillslots(APIView):
|
|||
|
||||
extra_data = get_extra_data(request, serializer.validated_data)
|
||||
|
||||
events = get_events_from_slots(slots, request, agenda, payload)
|
||||
event = get_events_from_slots([slot], request, agenda, payload)[0]
|
||||
|
||||
# search free places. Switch to waiting list if necessary.
|
||||
in_waiting_list = False
|
||||
for event in events:
|
||||
if event.start_datetime > now():
|
||||
if payload.get('force_waiting_list') and not event.waiting_list_places:
|
||||
raise APIError(N_('no waiting list'))
|
||||
if event.start_datetime > now():
|
||||
if payload.get('force_waiting_list') and not event.waiting_list_places:
|
||||
raise APIError(N_('no waiting list'))
|
||||
|
||||
if event.waiting_list_places:
|
||||
if (
|
||||
payload.get('force_waiting_list')
|
||||
or (event.booked_places + places_count) > event.places
|
||||
or event.booked_waiting_list_places
|
||||
):
|
||||
# if this is full or there are people waiting, put new bookings
|
||||
# in the waiting list.
|
||||
in_waiting_list = True
|
||||
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
|
||||
raise APIError(N_('sold out'))
|
||||
else:
|
||||
if (event.booked_places + places_count) > event.places:
|
||||
if event.waiting_list_places:
|
||||
if (
|
||||
payload.get('force_waiting_list')
|
||||
or (event.booked_places + places_count) > event.places
|
||||
or event.booked_waiting_list_places
|
||||
):
|
||||
# if this is full or there are people waiting, put new bookings
|
||||
# in the waiting list.
|
||||
in_waiting_list = True
|
||||
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
|
||||
raise APIError(N_('sold out'))
|
||||
else:
|
||||
if (event.booked_places + places_count) > event.places:
|
||||
raise APIError(N_('sold out'))
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
|
@ -1285,18 +1271,17 @@ class EventsAgendaFillslots(APIView):
|
|||
|
||||
# now we have a list of events, book them.
|
||||
primary_booking = None
|
||||
for event in events:
|
||||
for dummy in range(places_count):
|
||||
new_booking = make_booking(
|
||||
event=event,
|
||||
payload=payload,
|
||||
extra_data=extra_data,
|
||||
primary_booking=primary_booking,
|
||||
in_waiting_list=in_waiting_list,
|
||||
)
|
||||
new_booking.save()
|
||||
if primary_booking is None:
|
||||
primary_booking = new_booking
|
||||
for dummy in range(places_count):
|
||||
new_booking = make_booking(
|
||||
event=event,
|
||||
payload=payload,
|
||||
extra_data=extra_data,
|
||||
primary_booking=primary_booking,
|
||||
in_waiting_list=in_waiting_list,
|
||||
)
|
||||
new_booking.save()
|
||||
if primary_booking is None:
|
||||
primary_booking = new_booking
|
||||
except IntegrityError as e:
|
||||
if 'tstzrange_constraint' in str(e):
|
||||
# "optimistic concurrency control", between our availability
|
||||
|
@ -1310,14 +1295,14 @@ class EventsAgendaFillslots(APIView):
|
|||
# of fillslot().
|
||||
if retry:
|
||||
raise APIError(N_('no more desk available'))
|
||||
return self.fillslot(request=request, agenda=agenda, slots=slots, retry=True)
|
||||
return self.fillslot(request=request, agenda=agenda, slot=slot, retry=True)
|
||||
raise
|
||||
|
||||
response = {
|
||||
'err': 0,
|
||||
'in_waiting_list': in_waiting_list,
|
||||
'booking_id': primary_booking.id,
|
||||
'datetime': format_response_datetime(events[0].start_datetime),
|
||||
'datetime': format_response_datetime(event.start_datetime),
|
||||
'agenda': {
|
||||
'label': primary_booking.event.agenda.label,
|
||||
'slug': primary_booking.event.agenda.slug,
|
||||
|
@ -1345,47 +1330,26 @@ class EventsAgendaFillslots(APIView):
|
|||
}
|
||||
if to_cancel_booking:
|
||||
response['cancelled_booking_id'] = cancelled_booking_id
|
||||
if not multiple_booking:
|
||||
event = events[0]
|
||||
# event.full is not up to date, it might have been changed by previous new_booking.save().
|
||||
event.refresh_from_db()
|
||||
response['places'] = get_event_places(event)
|
||||
if event.end_datetime:
|
||||
response['end_datetime'] = format_response_datetime(event.end_datetime)
|
||||
else:
|
||||
response['end_datetime'] = None
|
||||
|
||||
# event.full is not up to date, it might have been changed by previous new_booking.save().
|
||||
event.refresh_from_db()
|
||||
response['places'] = get_event_places(event)
|
||||
if event.end_datetime:
|
||||
response['end_datetime'] = format_response_datetime(event.end_datetime)
|
||||
else:
|
||||
response['events'] = [
|
||||
{
|
||||
'slug': x.slug,
|
||||
'text': str(x),
|
||||
'datetime': format_response_datetime(x.start_datetime),
|
||||
'end_datetime': format_response_datetime(x.end_datetime) if x.end_datetime else None,
|
||||
'description': x.description,
|
||||
}
|
||||
for x in events
|
||||
]
|
||||
response['end_datetime'] = None
|
||||
|
||||
return Response(response)
|
||||
|
||||
|
||||
class EventsAgendaFillslot(EventsAgendaFillslots):
|
||||
class MeetingsAgendaFillslot(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.FillSlotSerializer
|
||||
|
||||
def post(self, request, agenda, slot):
|
||||
return self.fillslot(request=request, agenda=agenda, timeslot_id=slot)
|
||||
|
||||
class MeetingsAgendaFillslots(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.FillSlotsSerializer
|
||||
|
||||
def post(self, request, agenda, slots):
|
||||
if not settings.LEGACY_FILLSLOTS_ENABLED and slots is None:
|
||||
raise APIErrorBadRequest(N_('deprecated call'))
|
||||
|
||||
return self.fillslot(request=request, agenda=agenda, slots=slots)
|
||||
|
||||
def fillslot(self, request, agenda, slots=None, retry=False):
|
||||
slots = slots or []
|
||||
|
||||
def fillslot(self, request, agenda, timeslot_id, retry=False):
|
||||
known_body_params = set(request.query_params).intersection(
|
||||
{'label', 'user_name', 'backoffice_url', 'user_display_label'}
|
||||
)
|
||||
|
@ -1400,9 +1364,6 @@ class MeetingsAgendaFillslots(APIView):
|
|||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
payload = serializer.validated_data
|
||||
|
||||
if 'slots' in payload:
|
||||
slots = payload['slots']
|
||||
|
||||
to_cancel_booking = None
|
||||
cancel_booking_id = None
|
||||
if payload.get('cancel_booking_id'):
|
||||
|
@ -1430,23 +1391,15 @@ class MeetingsAgendaFillslots(APIView):
|
|||
user_external_id = payload.get('user_external_id') or None
|
||||
exclude_user = payload.get('exclude_user')
|
||||
|
||||
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids.
|
||||
# split them back to get both parts
|
||||
meeting_type_id = slots[0].split(':')[0]
|
||||
datetimes = set()
|
||||
for slot in slots:
|
||||
try:
|
||||
meeting_type_id_, datetime_str = slot.split(':')
|
||||
except ValueError:
|
||||
raise APIErrorBadRequest(N_('invalid slot: %s'), slot)
|
||||
if meeting_type_id_ != meeting_type_id:
|
||||
raise APIErrorBadRequest(
|
||||
N_('all slots must have the same meeting type id (%s)'), meeting_type_id
|
||||
)
|
||||
try:
|
||||
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
|
||||
except ValueError:
|
||||
raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
|
||||
try:
|
||||
meeting_type_id, datetime_str = timeslot_id.split(':')
|
||||
except ValueError:
|
||||
raise APIErrorBadRequest(N_('invalid timeslot_id: %s'), timeslot_id)
|
||||
|
||||
try:
|
||||
slot_datetime = make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M'))
|
||||
except ValueError:
|
||||
raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
|
||||
|
||||
resources = get_resources_from_request(request, agenda)
|
||||
|
||||
|
@ -1464,8 +1417,8 @@ class MeetingsAgendaFillslots(APIView):
|
|||
meeting_type,
|
||||
resources=resources,
|
||||
user_external_id=user_external_id if exclude_user else None,
|
||||
start_datetime=min(datetimes),
|
||||
end_datetime=max(datetimes) + datetime.timedelta(minutes=meeting_type.duration),
|
||||
start_datetime=slot_datetime,
|
||||
end_datetime=slot_datetime + datetime.timedelta(minutes=meeting_type.duration),
|
||||
),
|
||||
key=lambda slot: slot.start_datetime,
|
||||
)
|
||||
|
@ -1500,18 +1453,15 @@ class MeetingsAgendaFillslots(APIView):
|
|||
|
||||
# select a desk on the agenda with min fill_rate on the given date
|
||||
for available_desk_id in sorted(datetimes_by_desk.keys()):
|
||||
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
|
||||
if slot_datetime in datetimes_by_desk[available_desk_id]:
|
||||
desk = Desk.objects.get(id=available_desk_id)
|
||||
if available_desk is None:
|
||||
available_desk = desk
|
||||
available_desk_rate = 0
|
||||
for dt in datetimes:
|
||||
available_desk_rate += fill_rates[available_desk.agenda][dt.date()]['fill_rate']
|
||||
available_desk_rate = fill_rates[available_desk.agenda][slot_datetime.date()][
|
||||
'fill_rate'
|
||||
]
|
||||
else:
|
||||
for dt in datetimes:
|
||||
desk_rate = 0
|
||||
for dt in datetimes:
|
||||
desk_rate += fill_rates[desk.agenda][dt.date()]['fill_rate']
|
||||
desk_rate = fill_rates[desk.agenda][slot_datetime.date()]['fill_rate']
|
||||
if desk_rate < available_desk_rate:
|
||||
available_desk = desk
|
||||
available_desk_rate = desk_rate
|
||||
|
@ -1520,36 +1470,28 @@ class MeetingsAgendaFillslots(APIView):
|
|||
# meeting agenda
|
||||
# search first desk where all requested slots are free
|
||||
for available_desk_id in sorted(datetimes_by_desk.keys()):
|
||||
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
|
||||
if slot_datetime in datetimes_by_desk[available_desk_id]:
|
||||
available_desk = Desk.objects.get(id=available_desk_id)
|
||||
break
|
||||
|
||||
if available_desk is None:
|
||||
raise APIError(N_('no more desk available'))
|
||||
|
||||
# all datetimes are free, book them in order
|
||||
datetimes = list(datetimes)
|
||||
datetimes.sort()
|
||||
|
||||
# get a real meeting_type for virtual agenda
|
||||
if agenda.kind == 'virtual':
|
||||
meeting_type = MeetingType.objects.get(agenda=available_desk.agenda, slug=meeting_type.slug)
|
||||
|
||||
# booking requires real Event objects (not lazy Timeslots);
|
||||
# create them now, with data from the slots and the desk we found.
|
||||
events = []
|
||||
for start_datetime in datetimes:
|
||||
events.append(
|
||||
Event(
|
||||
agenda=available_desk.agenda,
|
||||
slug=str(uuid.uuid4()), # set slug to avoid queries during slug generation
|
||||
meeting_type=meeting_type,
|
||||
start_datetime=start_datetime,
|
||||
full=False,
|
||||
places=1,
|
||||
desk=available_desk,
|
||||
)
|
||||
)
|
||||
# booking requires real Event object (not lazy Timeslot);
|
||||
# create it now, with data from the slot and the desk we found.
|
||||
event = Event(
|
||||
agenda=available_desk.agenda,
|
||||
slug=str(uuid.uuid4()), # set slug to avoid queries during slug generation
|
||||
meeting_type=meeting_type,
|
||||
start_datetime=slot_datetime,
|
||||
full=False,
|
||||
places=1,
|
||||
desk=available_desk,
|
||||
)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
|
@ -1557,23 +1499,17 @@ class MeetingsAgendaFillslots(APIView):
|
|||
cancelled_booking_id = to_cancel_booking.pk
|
||||
to_cancel_booking.cancel()
|
||||
|
||||
# now we have a list of events, book them.
|
||||
primary_booking = None
|
||||
for event in events:
|
||||
if agenda.accept_meetings():
|
||||
event.save()
|
||||
if resources:
|
||||
event.resources.add(*resources)
|
||||
new_booking = make_booking(
|
||||
event=event,
|
||||
payload=payload,
|
||||
extra_data=extra_data,
|
||||
primary_booking=primary_booking,
|
||||
color=color,
|
||||
)
|
||||
new_booking.save()
|
||||
if primary_booking is None:
|
||||
primary_booking = new_booking
|
||||
# book event
|
||||
event.save()
|
||||
if resources:
|
||||
event.resources.add(*resources)
|
||||
booking = make_booking(
|
||||
event=event,
|
||||
payload=payload,
|
||||
extra_data=extra_data,
|
||||
color=color,
|
||||
)
|
||||
booking.save()
|
||||
except IntegrityError as e:
|
||||
if 'tstzrange_constraint' in str(e):
|
||||
# "optimistic concurrency control", between our availability
|
||||
|
@ -1587,33 +1523,33 @@ class MeetingsAgendaFillslots(APIView):
|
|||
# of fillslot().
|
||||
if retry:
|
||||
raise APIError(N_('no more desk available'))
|
||||
return self.fillslot(request=request, agenda=agenda, slots=slots, retry=True)
|
||||
return self.fillslot(request=request, agenda=agenda, timeslot_id=timeslot_id, retry=True)
|
||||
raise
|
||||
|
||||
response = {
|
||||
'err': 0,
|
||||
'booking_id': primary_booking.id,
|
||||
'datetime': format_response_datetime(events[0].start_datetime),
|
||||
'booking_id': booking.id,
|
||||
'datetime': format_response_datetime(event.start_datetime),
|
||||
'agenda': {
|
||||
'label': primary_booking.event.agenda.label,
|
||||
'slug': primary_booking.event.agenda.slug,
|
||||
'label': booking.event.agenda.label,
|
||||
'slug': booking.event.agenda.slug,
|
||||
},
|
||||
'end_datetime': format_response_datetime(events[-1].end_datetime),
|
||||
'duration': (events[-1].end_datetime - events[-1].start_datetime).seconds // 60,
|
||||
'end_datetime': format_response_datetime(event.end_datetime),
|
||||
'duration': (event.end_datetime - event.start_datetime).seconds // 60,
|
||||
'resources': [r.slug for r in resources],
|
||||
'desk': {'label': available_desk.label, 'slug': available_desk.slug},
|
||||
'api': {
|
||||
'booking_url': request.build_absolute_uri(
|
||||
reverse('api-booking', kwargs={'booking_pk': primary_booking.id})
|
||||
reverse('api-booking', kwargs={'booking_pk': booking.id})
|
||||
),
|
||||
'cancel_url': request.build_absolute_uri(
|
||||
reverse('api-cancel-booking', kwargs={'booking_pk': primary_booking.id})
|
||||
reverse('api-cancel-booking', kwargs={'booking_pk': booking.id})
|
||||
),
|
||||
'ics_url': request.build_absolute_uri(
|
||||
reverse('api-booking-ics', kwargs={'booking_pk': primary_booking.id})
|
||||
reverse('api-booking-ics', kwargs={'booking_pk': booking.id})
|
||||
),
|
||||
'anonymize_url': request.build_absolute_uri(
|
||||
reverse('api-anonymize-booking', kwargs={'booking_pk': primary_booking.id})
|
||||
reverse('api-anonymize-booking', kwargs={'booking_pk': booking.id})
|
||||
),
|
||||
},
|
||||
}
|
||||
|
@ -1623,13 +1559,10 @@ class MeetingsAgendaFillslots(APIView):
|
|||
return Response(response)
|
||||
|
||||
|
||||
class MeetingsAgendaFillslot(MeetingsAgendaFillslots):
|
||||
class Fillslot(APIView):
|
||||
serializer_class = serializers.FillSlotSerializer
|
||||
|
||||
|
||||
class Fillslots(APIView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
agenda_identifier = kwargs['agenda_identifier']
|
||||
def dispatch(self, request, agenda_identifier, event_identifier):
|
||||
try:
|
||||
agenda = Agenda.objects.get(slug=agenda_identifier)
|
||||
except Agenda.DoesNotExist:
|
||||
|
@ -1639,31 +1572,11 @@ class Fillslots(APIView):
|
|||
except (ValueError, Agenda.DoesNotExist):
|
||||
raise Http404()
|
||||
|
||||
if kwargs.get('slots'):
|
||||
if agenda.accept_meetings():
|
||||
api_view = MeetingsAgendaFillslot()
|
||||
else:
|
||||
api_view = EventsAgendaFillslot()
|
||||
if agenda.accept_meetings():
|
||||
api_view = MeetingsAgendaFillslot()
|
||||
else:
|
||||
if agenda.accept_meetings():
|
||||
api_view = MeetingsAgendaFillslots()
|
||||
else:
|
||||
api_view = EventsAgendaFillslots()
|
||||
return api_view.dispatch(request=request, agenda=agenda, slots=kwargs.get('slots'))
|
||||
|
||||
|
||||
fillslots = Fillslots.as_view()
|
||||
|
||||
|
||||
class Fillslot(Fillslots):
|
||||
serializer_class = serializers.FillSlotSerializer
|
||||
|
||||
def dispatch(self, request, agenda_identifier=None, event_identifier=None):
|
||||
return super().dispatch(
|
||||
request=request,
|
||||
agenda_identifier=agenda_identifier,
|
||||
slots=[event_identifier], # fill a "list on one slot"
|
||||
)
|
||||
api_view = EventsAgendaFillslot()
|
||||
return api_view.dispatch(request=request, agenda=agenda, slot=event_identifier)
|
||||
|
||||
|
||||
fillslot = Fillslot.as_view()
|
||||
|
|
|
@ -199,7 +199,6 @@ SMS_SENDER = ''
|
|||
REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'chrono.api.utils.exception_handler'}
|
||||
|
||||
SHARED_CUSTODY_ENABLED = False
|
||||
LEGACY_FILLSLOTS_ENABLED = False
|
||||
PARTIAL_BOOKINGS_ENABLED = False
|
||||
|
||||
CHRONO_ANTS_HUB_URL = None
|
||||
|
|
|
@ -434,7 +434,6 @@ def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda,
|
|||
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
assert len(resp.json['data']) == 2
|
||||
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
|
||||
two_slots = [resp.json['data'][0]['id'], resp.json['data'][1]['id']]
|
||||
|
||||
time_period.end_time = datetime.time(10, 15)
|
||||
time_period.save()
|
||||
|
@ -448,13 +447,6 @@ def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda,
|
|||
assert resp.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp.json['err_class'] == 'no more desk available'
|
||||
assert resp.json['err_desc'] == 'no more desk available'
|
||||
# booking the two slots fails too
|
||||
fillslots_url = '/api/agenda/%s/fillslots/' % meeting_type.agenda.slug
|
||||
resp = app.post(fillslots_url, params={'slots': two_slots})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp.json['err_class'] == 'no more desk available'
|
||||
assert resp.json['err_desc'] == 'no more desk available'
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-02-25')
|
||||
|
|
|
@ -454,205 +454,6 @@ def test_booking_api_meetings_agenda_exclude_slots(app, user):
|
|||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
def test_booking_api_fillslots(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
events = []
|
||||
for i in range(3):
|
||||
events.append(
|
||||
Event.objects.create(
|
||||
label='Event', start_datetime=now() + datetime.timedelta(days=5 + i), places=20, agenda=agenda
|
||||
)
|
||||
)
|
||||
events_ids = [x.id for x in events]
|
||||
events_slugs = [x.slug for x in events]
|
||||
event = events[0]
|
||||
|
||||
# unauthenticated
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, status=401)
|
||||
|
||||
for agenda_key in (agenda.slug, agenda.id): # acces datetimes via agenda slug or id (legacy)
|
||||
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda_key)
|
||||
api_event_slugs = [x['id'] for x in resp_datetimes.json['data']]
|
||||
assert api_event_slugs == events_slugs
|
||||
|
||||
assert Booking.objects.count() == 0
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_ids})
|
||||
primary_booking_id = resp.json['booking_id']
|
||||
Booking.objects.get(id=primary_booking_id)
|
||||
assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
||||
assert 'booking_url' in resp.json['api']
|
||||
assert 'accept_url' in resp.json['api']
|
||||
assert 'suspend_url' in resp.json['api']
|
||||
assert 'cancel_url' in resp.json['api']
|
||||
assert urlparse.urlparse(resp.json['api']['booking_url']).netloc
|
||||
assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
|
||||
assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc
|
||||
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
|
||||
assert Booking.objects.count() == 3
|
||||
# these 3 bookings are related, the first is the primary one
|
||||
bookings = Booking.objects.all().order_by('pk')
|
||||
assert bookings[0].primary_booking is None
|
||||
assert bookings[1].primary_booking.id == bookings[0].id == primary_booking_id
|
||||
assert bookings[2].primary_booking.id == bookings[0].id == primary_booking_id
|
||||
|
||||
# access by slug
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_slugs})
|
||||
primary_booking_id_2 = resp.json['booking_id']
|
||||
assert Booking.objects.count() == 6
|
||||
assert Booking.objects.filter(event__agenda=agenda).count() == 6
|
||||
# 6 = 2 primary + 2*2 secondary
|
||||
assert Booking.objects.filter(event__agenda=agenda, primary_booking__isnull=True).count() == 2
|
||||
assert Booking.objects.filter(event__agenda=agenda, primary_booking=primary_booking_id).count() == 2
|
||||
assert Booking.objects.filter(event__agenda=agenda, primary_booking=primary_booking_id_2).count() == 2
|
||||
|
||||
# test with additional data
|
||||
resp = app.post_json(
|
||||
'/api/agenda/%s/fillslots/' % agenda.id,
|
||||
params={
|
||||
'slots': events_ids,
|
||||
'label': 'foo',
|
||||
'user_external_id': 'some_external_id',
|
||||
'user_last_name': 'bar',
|
||||
'user_display_label': 'foo',
|
||||
'backoffice_url': 'http://example.net/',
|
||||
},
|
||||
)
|
||||
booking_id = resp.json['booking_id']
|
||||
booking = Booking.objects.get(pk=booking_id)
|
||||
assert booking.label == 'foo'
|
||||
assert booking.user_external_id == 'some_external_id'
|
||||
assert booking.user_last_name == 'bar'
|
||||
assert booking.user_display_label == 'foo'
|
||||
assert booking.backoffice_url == 'http://example.net/'
|
||||
assert Booking.objects.filter(primary_booking=booking_id, label='foo').count() == 2
|
||||
# cancel
|
||||
cancel_url = resp.json['api']['cancel_url']
|
||||
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 0
|
||||
assert Booking.objects.get(id=booking_id).cancellation_datetime is None
|
||||
resp_cancel = app.post(cancel_url)
|
||||
assert resp_cancel.json['err'] == 0
|
||||
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 3
|
||||
assert Booking.objects.get(id=booking_id).cancellation_datetime is not None
|
||||
|
||||
# extra data stored in extra_data field
|
||||
resp = app.post_json(
|
||||
'/api/agenda/%s/fillslots/' % agenda.id,
|
||||
params={'slots': events_ids, 'label': 'l', 'user_last_name': 'u', 'backoffice_url': '', 'foo': 'bar'},
|
||||
)
|
||||
assert Booking.objects.get(id=resp.json['booking_id']).label == 'l'
|
||||
assert Booking.objects.get(id=resp.json['booking_id']).user_last_name == 'u'
|
||||
assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == ''
|
||||
assert Booking.objects.get(id=resp.json['booking_id']).extra_data == {'foo': 'bar'}
|
||||
for booking in Booking.objects.filter(primary_booking=resp.json['booking_id']):
|
||||
assert booking.extra_data == {'foo': 'bar'}
|
||||
|
||||
# test invalid data are refused
|
||||
resp = app.post_json(
|
||||
'/api/agenda/%s/fillslots/' % agenda.id,
|
||||
params={'slots': events_ids, 'user_last_name': {'foo': 'bar'}},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid payload' # legacy
|
||||
assert resp.json['err_class'] == 'invalid payload'
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
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
|
||||
assert resp.json['reason'] == 'invalid payload' # legacy
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['slots'] == ['This field is required.']
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid payload' # legacy
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['slots'] == ['This field is required.']
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': ''}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert resp.json['errors']['slots'] == ['This field is required.']
|
||||
# invalid slots format
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': 'foobar'}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid slugs: foobar' # legacy
|
||||
assert resp.json['err_class'] == 'invalid slugs: foobar'
|
||||
assert resp.json['err_desc'] == 'invalid slugs: foobar'
|
||||
|
||||
# unknown agendas
|
||||
resp = app.post('/api/agenda/foobar/fillslots/', status=404)
|
||||
resp = app.post('/api/agenda/0/fillslots/', status=404)
|
||||
|
||||
# check bookable period
|
||||
with mock.patch('chrono.agendas.models.Event.in_bookable_period') as in_bookable_period:
|
||||
in_bookable_period.return_value = True
|
||||
resp = app.post_json(
|
||||
'/api/agenda/%s/fillslots/' % agenda.id,
|
||||
params={'slots': events_ids},
|
||||
)
|
||||
assert resp.json['err'] == 0
|
||||
in_bookable_period.return_value = False
|
||||
resp = app.post_json(
|
||||
'/api/agenda/%s/fillslots/' % agenda.id,
|
||||
params={'slots': events_ids},
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'event not bookable' # legacy
|
||||
assert resp.json['err_class'] == 'event not bookable'
|
||||
assert resp.json['err_desc'] == 'event event is not bookable'
|
||||
|
||||
|
||||
def test_booking_api_fillslots_slots_string_param(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
Event.objects.create(
|
||||
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
|
||||
)
|
||||
Event.objects.create(
|
||||
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
|
||||
)
|
||||
events_ids = [x.id for x in Event.objects.filter(agenda=agenda)]
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# empty string
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': ''}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_class'] == 'invalid payload'
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
||||
slots_string_param = ','.join([str(e) for e in events_ids])
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots_string_param})
|
||||
assert Booking.objects.count() == 2
|
||||
|
||||
start = now() + datetime.timedelta(days=2)
|
||||
Event.objects.create(label='Long Slug', slug='a' * 100, start_datetime=start, places=2, agenda=agenda)
|
||||
Event.objects.create(label='Long Slug', slug='b' * 100, start_datetime=start, places=2, agenda=agenda)
|
||||
events_ids = [x.id for x in Event.objects.filter(label='Long Slug')]
|
||||
slots_string_param = ','.join([str(e) for e in events_ids])
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots_string_param})
|
||||
assert Booking.objects.count() == 4
|
||||
|
||||
|
||||
def test_booking_api_meeting(app, meetings_agenda, user):
|
||||
agenda_id = meetings_agenda.slug
|
||||
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
||||
|
@ -867,90 +668,6 @@ def test_booking_api_meeting_with_resources(app, user):
|
|||
assert list(booking.event.resources.all()) == [resource1, resource2]
|
||||
|
||||
|
||||
def test_booking_api_meeting_fillslots(app, meetings_agenda, user):
|
||||
agenda_id = meetings_agenda.slug
|
||||
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
slots = [resp.json['data'][0]['id'], resp.json['data'][1]['id']]
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp_booking = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': slots})
|
||||
assert Booking.objects.count() == 2
|
||||
primary_booking = Booking.objects.filter(primary_booking__isnull=True).first()
|
||||
secondary_booking = Booking.objects.filter(primary_booking=primary_booking.id).first()
|
||||
assert resp_booking.json['datetime'] == localtime(primary_booking.event.start_datetime).strftime(
|
||||
'%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
assert resp_booking.json['end_datetime'] == localtime(secondary_booking.event.end_datetime).strftime(
|
||||
'%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x.get('disabled')]) + 2
|
||||
|
||||
# try booking the same timeslots
|
||||
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': slots})
|
||||
assert resp2.json['err'] == 1
|
||||
assert resp2.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp2.json['err_class'] == 'no more desk available'
|
||||
assert resp2.json['err_desc'] == 'no more desk available'
|
||||
|
||||
# try booking partially free timeslots (one free, one busy)
|
||||
nonfree_slots = [resp.json['data'][0]['id'], resp.json['data'][2]['id']]
|
||||
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': nonfree_slots})
|
||||
assert resp2.json['err'] == 1
|
||||
assert resp2.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp2.json['err_class'] == 'no more desk available'
|
||||
assert resp2.json['err_desc'] == 'no more desk available'
|
||||
|
||||
# booking other free timeslots
|
||||
free_slots = [resp.json['data'][3]['id'], resp.json['data'][2]['id']]
|
||||
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': free_slots})
|
||||
assert resp2.json['err'] == 0
|
||||
cancel_url = resp2.json['api']['cancel_url']
|
||||
assert Booking.objects.count() == 4
|
||||
# 4 = 2 primary + 2 secondary
|
||||
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
||||
assert Booking.objects.filter(primary_booking__isnull=False).count() == 2
|
||||
# cancel
|
||||
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 0
|
||||
resp_cancel = app.post(cancel_url)
|
||||
assert resp_cancel.json['err'] == 0
|
||||
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 2
|
||||
|
||||
|
||||
def test_booking_api_meeting_fillslots_wrong_slot(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo', kind='meetings')
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
impossible_slots = ['1:2017-05-22-1130', '2:2017-05-22-1100']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': impossible_slots}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'all slots must have the same meeting type id (1)' # legacy
|
||||
assert resp.json['err_class'] == 'all slots must have the same meeting type id (1)'
|
||||
assert resp.json['err_desc'] == 'all slots must have the same meeting type id (1)'
|
||||
|
||||
unknown_slots = ['0:2017-05-22-1130']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': unknown_slots}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid meeting type id: 0' # legacy
|
||||
assert resp.json['err_class'] == 'invalid meeting type id: 0'
|
||||
assert resp.json['err_desc'] == 'invalid meeting type id: 0'
|
||||
unknown_slots = ['foobar:2017-05-22-1130']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': unknown_slots}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid meeting type id: foobar' # legacy
|
||||
assert resp.json['err_class'] == 'invalid meeting type id: foobar'
|
||||
assert resp.json['err_desc'] == 'invalid meeting type id: foobar'
|
||||
|
||||
badformat_slots = ['foo:2020-10-28-14h00']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': badformat_slots}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'bad datetime format: 2020-10-28-14h00' # legacy
|
||||
assert resp.json['err_class'] == 'bad datetime format: 2020-10-28-14h00'
|
||||
assert resp.json['err_desc'] == 'bad datetime format: 2020-10-28-14h00'
|
||||
|
||||
|
||||
def test_booking_api_meeting_across_daylight_saving_time(app, meetings_agenda, user):
|
||||
meetings_agenda.maximal_booking_delay = 365
|
||||
meetings_agenda.save()
|
||||
|
@ -1002,12 +719,6 @@ def test_booking_api_meeting_weekday_indexes(app, user):
|
|||
assert Booking.objects.count() == 1
|
||||
assert resp.json['duration'] == 30
|
||||
|
||||
# multiple slots
|
||||
slots = [datetimes_resp.json['data'][1]['id'], datetimes_resp.json['data'][2]['id']]
|
||||
assert slots == ['plop:2022-02-03-1130', 'plop:2022-02-17-1100']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots})
|
||||
assert Booking.objects.count() == 3
|
||||
|
||||
# try to book slot on a skipped week
|
||||
slot = datetimes_resp.json['data'][3]['id']
|
||||
time_period.weekday_indexes = [1]
|
||||
|
@ -1057,11 +768,12 @@ def test_booking_api_meeting_date_time_period(app, user):
|
|||
assert Booking.objects.count() == 1
|
||||
assert resp.json['duration'] == 30
|
||||
|
||||
# multiple slots
|
||||
# book another two slots
|
||||
slots = [datetimes_resp.json['data'][1]['id'], datetimes_resp.json['data'][2]['id']]
|
||||
assert slots == ['plop:2022-10-24-1230', 'plop:2022-10-24-1300']
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots})
|
||||
assert Booking.objects.count() == 3
|
||||
for i, slot in enumerate(slots, 2):
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
|
||||
assert Booking.objects.count() == i
|
||||
assert resp.json['duration'] == 30
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
|
||||
assert resp.json['err'] == 1
|
||||
|
@ -1148,16 +860,6 @@ def test_booking_api_available(app, user):
|
|||
assert resp.json['err'] == 0
|
||||
assert 'places' not in resp.json
|
||||
|
||||
# not for multiple booking
|
||||
events = [
|
||||
x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period()
|
||||
][:2]
|
||||
slots = [x.pk for x in events]
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'})
|
||||
assert resp.json['err'] == 0
|
||||
assert 'places' not in resp.json
|
||||
|
||||
|
||||
def test_booking_api_force_waiting_list(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
|
@ -1487,164 +1189,6 @@ def test_multiple_booking_api(app, user):
|
|||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 2
|
||||
|
||||
|
||||
def test_multiple_booking_api_fillslots(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
events = []
|
||||
for i in range(2):
|
||||
events.append(
|
||||
Event.objects.create(
|
||||
label='Event', start_datetime=now() + datetime.timedelta(days=5 + i), places=20, agenda=agenda
|
||||
)
|
||||
)
|
||||
events_slugs = [x.slug for x in events]
|
||||
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
|
||||
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_slugs]
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/agenda/%s/fillslots/?count=NaN' % agenda.slug, params={'slots': slots}, status=400)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid value for count (NaN)' # legacy
|
||||
assert resp.json['err_class'] == 'invalid value for count (NaN)'
|
||||
assert resp.json['err_desc'] == 'invalid value for count (NaN)'
|
||||
|
||||
resp = app.post(
|
||||
'/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 'NaN'}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'invalid payload' # legacy
|
||||
assert resp.json['err_class'] == 'invalid payload'
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
assert 'count' in resp.json['errors']
|
||||
|
||||
# get 3 places on 2 slots
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'})
|
||||
# one booking with 5 children
|
||||
booking = Booking.objects.get(id=resp.json['booking_id'])
|
||||
cancel_url = resp.json['api']['cancel_url']
|
||||
assert Booking.objects.filter(primary_booking=booking).count() == 5
|
||||
assert resp.json['datetime'] == localtime(events[0].start_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
||||
assert 'accept_url' in resp.json['api']
|
||||
assert 'cancel_url' in resp.json['api']
|
||||
assert 'ics_url' in resp.json['api']
|
||||
resp_events = resp.json['events']
|
||||
assert len(resp_events) == len(events)
|
||||
for e, resp_e in zip(events, resp_events):
|
||||
assert e.slug == resp_e['slug']
|
||||
assert e.description == resp_e['description']
|
||||
assert str(e) == resp_e['text']
|
||||
assert localtime(e.start_datetime).strftime('%Y-%m-%d %H:%M:%S') == resp_e['datetime']
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 2})
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 5
|
||||
|
||||
resp = app.post(cancel_url)
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
|
||||
# check available places overflow
|
||||
# NB: limit only the first event !
|
||||
events[0].places = 3
|
||||
events[0].waiting_list_places = 8
|
||||
events[0].save()
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
accept_url = resp.json['api']['accept_url']
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'sold out' # legacy
|
||||
assert resp.json['err_class'] == 'sold out'
|
||||
assert resp.json['err_desc'] == 'sold out'
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
|
||||
# accept the waiting list
|
||||
resp = app.post(accept_url)
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 7
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
# check with a short waiting list
|
||||
Booking.objects.all().delete()
|
||||
# NB: limit only the first event !
|
||||
events[0].places = 4
|
||||
events[0].waiting_list_places = 2
|
||||
events[0].save()
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'sold out' # legacy
|
||||
assert resp.json['err_class'] == 'sold out'
|
||||
assert resp.json['err_desc'] == 'sold out'
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3})
|
||||
assert resp.json['err'] == 0
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'sold out' # legacy
|
||||
assert resp.json['err_class'] == 'sold out'
|
||||
assert resp.json['err_desc'] == 'sold out'
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '2'})
|
||||
assert resp.json['err'] == 0
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 2
|
||||
|
||||
|
||||
def test_multiple_booking_move_booking(app, user):
|
||||
agenda = Agenda(label='Foo bar')
|
||||
agenda.save()
|
||||
first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0)
|
||||
first_date += datetime.timedelta(days=1)
|
||||
events = []
|
||||
for i in range(10):
|
||||
event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda)
|
||||
event.save()
|
||||
events.append(event)
|
||||
|
||||
first_two_events = events[:2]
|
||||
events_slugs = [x.slug for x in first_two_events]
|
||||
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
|
||||
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_slugs]
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# get 1 place on 2 slots
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots})
|
||||
booking = Booking.objects.get(id=resp.json['booking_id'])
|
||||
assert Booking.objects.filter(primary_booking=booking).count() == 1
|
||||
for event in first_two_events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 1
|
||||
|
||||
# change, 1 place on 2 other slots
|
||||
last_two_events = events[-2:]
|
||||
events_slugs = [x.slug for x in last_two_events]
|
||||
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
|
||||
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_slugs]
|
||||
|
||||
resp = app.post(
|
||||
'/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'cancel_booking_id': booking.pk}
|
||||
)
|
||||
booking = Booking.objects.get(id=resp.json['booking_id'])
|
||||
assert Booking.objects.filter(primary_booking=booking).count() == 1
|
||||
for event in first_two_events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 0
|
||||
for event in last_two_events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 1
|
||||
|
||||
|
||||
def test_agenda_meeting_api_multiple_desk(app, user):
|
||||
agenda = Agenda.objects.create(
|
||||
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
|
||||
|
@ -1743,92 +1287,6 @@ def test_agenda_meeting_api_multiple_desk(app, user):
|
|||
assert len(ctx.captured_queries) == 9
|
||||
|
||||
|
||||
def test_agenda_meeting_api_fillslots_multiple_desks(app, user):
|
||||
agenda = Agenda.objects.create(
|
||||
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
|
||||
)
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
|
||||
default_desk = Desk.objects.create(agenda=agenda, label='Desk 1')
|
||||
time_period = TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# add a second desk, same timeperiods
|
||||
time_period = agenda.desk_set.first().timeperiod_set.first()
|
||||
desk2 = Desk.objects.create(label='Desk 2', agenda=agenda)
|
||||
TimePeriod.objects.create(
|
||||
start_time=time_period.start_time,
|
||||
end_time=time_period.end_time,
|
||||
weekday=time_period.weekday,
|
||||
desk=desk2,
|
||||
)
|
||||
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
slots = [x['id'] for x in resp.json['data'][:3]]
|
||||
|
||||
def get_free_places():
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
return len([x for x in resp.json['data'] if not x['disabled']])
|
||||
|
||||
start_free_places = get_free_places()
|
||||
|
||||
# booking 3 slots on desk 1
|
||||
fillslots_url = '/api/agenda/%s/fillslots/' % agenda.pk
|
||||
resp = app.post(fillslots_url, params={'slots': slots})
|
||||
assert resp.json['err'] == 0
|
||||
desk1 = resp.json['desk']['slug']
|
||||
cancel_url = resp.json['api']['cancel_url']
|
||||
assert get_free_places() == start_free_places
|
||||
|
||||
# booking same slots again, will be on desk 2
|
||||
resp = app.post(fillslots_url, params={'slots': slots})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['desk']['slug'] != desk2
|
||||
# 3 places are disabled in datetimes list
|
||||
assert get_free_places() == start_free_places - len(slots)
|
||||
|
||||
# try booking again: no desk available
|
||||
resp = app.post(fillslots_url, params={'slots': slots})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp.json['err_class'] == 'no more desk available'
|
||||
assert resp.json['err_desc'] == 'no more desk available'
|
||||
assert get_free_places() == start_free_places - len(slots)
|
||||
|
||||
# cancel desk 1 booking
|
||||
resp = app.post(cancel_url)
|
||||
assert resp.json['err'] == 0
|
||||
# all places are free again
|
||||
assert get_free_places() == start_free_places
|
||||
|
||||
# booking a single slot (must be on desk 1)
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, slots[1]))
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['desk']['slug'] == desk1
|
||||
cancel_url = resp.json['api']['cancel_url']
|
||||
assert get_free_places() == start_free_places - 1
|
||||
|
||||
# try booking the 3 slots again: no desk available, one slot is not fully available
|
||||
resp = app.post(fillslots_url, params={'slots': slots})
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['reason'] == 'no more desk available' # legacy
|
||||
assert resp.json['err_class'] == 'no more desk available'
|
||||
assert resp.json['err_desc'] == 'no more desk available'
|
||||
|
||||
# cancel last signel slot booking, desk1 will be free
|
||||
resp = app.post(cancel_url)
|
||||
assert resp.json['err'] == 0
|
||||
assert get_free_places() == start_free_places
|
||||
|
||||
# booking again is ok, on desk 1
|
||||
resp = app.post(fillslots_url, params={'slots': slots})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['desk']['slug'] == desk1
|
||||
assert get_free_places() == start_free_places - len(slots)
|
||||
|
||||
|
||||
def test_agenda_meeting_same_day(app, mock_now, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
agenda = Agenda(label='Foo', kind='meetings')
|
||||
|
@ -2237,59 +1695,6 @@ def test_duration_on_booking_api_fillslot_response(app, user):
|
|||
assert 'DTEND:20170521T235700Z' in ics
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2017-04-01')
|
||||
def test_duration_on_booking_api_fillslots_response(app, user):
|
||||
agenda = Agenda(label='Foo bar')
|
||||
agenda.save()
|
||||
first_date = datetime.datetime(2017, 5, 20, 1, 12)
|
||||
durations = [None, 0, 45]
|
||||
evt = []
|
||||
for i in range(3):
|
||||
evt.append(
|
||||
Event(
|
||||
start_datetime=first_date + datetime.timedelta(days=i),
|
||||
duration=durations[i],
|
||||
places=20,
|
||||
agenda=agenda,
|
||||
)
|
||||
)
|
||||
evt[i].save()
|
||||
|
||||
assert evt[0].end_datetime is None
|
||||
assert evt[1].end_datetime == evt[1].start_datetime
|
||||
assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# first event having null duration
|
||||
string_param = ','.join([str(e.id) for e in evt[::-1]]) # unordered parameters
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
|
||||
r_evt = resp.json['events']
|
||||
|
||||
assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
|
||||
assert r_evt[0]['end_datetime'] is None
|
||||
assert r_evt[1]['datetime'] == '2017-05-21 01:12:00'
|
||||
assert r_evt[1]['end_datetime'] == r_evt[1]['datetime']
|
||||
assert r_evt[2]['datetime'] == '2017-05-22 01:12:00'
|
||||
assert r_evt[2]['end_datetime'] == '2017-05-22 01:57:00'
|
||||
assert 'ics_url' in resp.json['api']
|
||||
ics = app.get(resp.json['api']['ics_url']).text
|
||||
assert 'DTSTART:20170519T231200Z' in ics
|
||||
assert 'DTEND:' not in ics
|
||||
|
||||
# first event having duration
|
||||
evt[0].duration = 90
|
||||
evt[0].save()
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
|
||||
r_evt = resp.json['events']
|
||||
|
||||
assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
|
||||
assert r_evt[0]['end_datetime'] == '2017-05-20 02:42:00'
|
||||
assert 'ics_url' in resp.json['api']
|
||||
ics = app.get(resp.json['api']['ics_url']).text
|
||||
assert 'DTSTART:20170519T231200Z' in ics
|
||||
assert 'DTEND:20170520T004200Z' in ics
|
||||
|
||||
|
||||
def test_fillslot_past_event(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
agenda = Agenda.objects.create(
|
||||
|
@ -2621,21 +2026,3 @@ def test_user_external_id(app, user):
|
|||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
|
||||
meeting_event.delete()
|
||||
|
||||
|
||||
def test_booking_api_fillslots_deprecated(app, user, settings):
|
||||
settings.LEGACY_FILLSLOTS_ENABLED = False
|
||||
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': [event.id]}, status=400)
|
||||
assert 'deprecated' in resp.json['err_desc']
|
||||
assert Booking.objects.count() == 0
|
||||
|
||||
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id))
|
||||
assert Booking.objects.count() == 1
|
||||
|
|
|
@ -75,7 +75,6 @@ def test_agendas_api(app):
|
|||
'booking_form_url': None,
|
||||
'api': {
|
||||
'datetimes_url': 'http://testserver/api/agenda/foo-bar/datetimes/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda.pk,
|
||||
},
|
||||
},
|
||||
|
@ -96,7 +95,6 @@ def test_agendas_api(app):
|
|||
'booking_form_url': None,
|
||||
'api': {
|
||||
'datetimes_url': 'http://testserver/api/agenda/foo-bar-2/datetimes/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar-2/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda2.pk,
|
||||
},
|
||||
},
|
||||
|
@ -117,7 +115,6 @@ def test_agendas_api(app):
|
|||
'booking_form_url': None,
|
||||
'api': {
|
||||
'datetimes_url': 'http://testserver/api/agenda/foo-bar-3/datetimes/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar-3/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda3.pk,
|
||||
},
|
||||
},
|
||||
|
@ -141,7 +138,6 @@ def test_agendas_api(app):
|
|||
'meetings_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/',
|
||||
'desks_url': 'http://testserver/api/agenda/foo-bar-meeting/desks/',
|
||||
'resources_url': 'http://testserver/api/agenda/foo-bar-meeting/resources/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % meetings_agenda1.pk,
|
||||
},
|
||||
},
|
||||
|
@ -162,7 +158,6 @@ def test_agendas_api(app):
|
|||
'meetings_url': 'http://testserver/api/agenda/foo-bar-meeting-2/meetings/',
|
||||
'desks_url': 'http://testserver/api/agenda/foo-bar-meeting-2/desks/',
|
||||
'resources_url': 'http://testserver/api/agenda/foo-bar-meeting-2/resources/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting-2/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % meetings_agenda2.pk,
|
||||
},
|
||||
},
|
||||
|
@ -181,7 +176,6 @@ def test_agendas_api(app):
|
|||
'api': {
|
||||
'meetings_url': 'http://testserver/api/agenda/virtual-agenda/meetings/',
|
||||
'desks_url': 'http://testserver/api/agenda/virtual-agenda/desks/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/virtual-agenda/fillslots/',
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % virtual_agenda.pk,
|
||||
},
|
||||
},
|
||||
|
@ -429,7 +423,6 @@ def test_virtual_agenda_detail(app, virtual_meetings_agenda):
|
|||
'api': {
|
||||
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_meetings_agenda.slug,
|
||||
'desks_url': 'http://testserver/api/agenda/%s/desks/' % virtual_meetings_agenda.slug,
|
||||
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % virtual_meetings_agenda.slug,
|
||||
'backoffice_url': 'http://testserver/manage/agendas/%s/' % virtual_meetings_agenda.pk,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -45,6 +45,5 @@ EXCEPTIONS_SOURCES = {}
|
|||
SITE_BASE_URL = 'https://example.com'
|
||||
|
||||
SHARED_CUSTODY_ENABLED = True
|
||||
LEGACY_FILLSLOTS_ENABLED = True
|
||||
|
||||
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
|
||||
|
|
Loading…
Reference in New Issue