api: remove legacy fillslots views (#80352)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Valentin Deniaud 2023-08-09 16:44:33 +02:00
parent 8127fbff66
commit 84463c84bf
7 changed files with 108 additions and 828 deletions

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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,
},
},

View File

@ -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']