Revert "api: add lock_code parameter to fillslot and datetimes" (#54956)
This reverts commit 2f9bf16a57
.
This commit is contained in:
parent
5975b12e29
commit
15e1c48ea2
|
@ -1,64 +0,0 @@
|
|||
# Generated by Django 2.2.19 on 2021-03-16 13:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
create_gist_constraints_on_leases = """
|
||||
ALTER TABLE agendas_lease
|
||||
ADD CONSTRAINT lease_desk_constraint
|
||||
EXCLUDE USING GIST(desk_id WITH =, tstzrange(start_datetime, end_datetime) WITH &&)
|
||||
WHERE (desk_id IS NOT NULL);
|
||||
ALTER TABLE agendas_lease
|
||||
ADD CONSTRAINT lease_resource_constraint
|
||||
EXCLUDE USING GIST(resource_id WITH =, tstzrange(start_datetime, end_datetime) WITH &&)
|
||||
WHERE (resource_id IS NOT NULL);
|
||||
"""
|
||||
|
||||
drop_gist_constraints_on_leases = """
|
||||
ALTER TABLE agendas_lease DROP CONSTRAINT lease_desk_constraint;
|
||||
ALTER TABLE agendas_lease DROP CONSTRAINT lease_resource_constraint;
|
||||
"""
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('agendas', '0090_default_view'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Lease',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('lock_code', models.CharField(max_length=64, verbose_name='Lock code')),
|
||||
('lock_expiration_datetime', models.DateTimeField(verbose_name='Lock expiration time')),
|
||||
('start_datetime', models.DateTimeField(verbose_name='Start')),
|
||||
('end_datetime', models.DateTimeField(verbose_name='End')),
|
||||
(
|
||||
'agenda',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to='agendas.Agenda'
|
||||
),
|
||||
),
|
||||
(
|
||||
'desk',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to='agendas.Desk'
|
||||
),
|
||||
),
|
||||
(
|
||||
'resource',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to='agendas.Resource'
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'index_together': {('start_datetime', 'end_datetime')},
|
||||
},
|
||||
),
|
||||
migrations.RunSQL(sql=create_gist_constraints_on_leases, reverse_sql=drop_gist_constraints_on_leases),
|
||||
]
|
|
@ -2673,16 +2673,3 @@ class AbsenceReason(models.Model):
|
|||
@property
|
||||
def base_slug(self):
|
||||
return slugify(self.label)
|
||||
|
||||
|
||||
class Lease(models.Model):
|
||||
desk = models.ForeignKey(Desk, null=True, on_delete=models.CASCADE)
|
||||
resource = models.ForeignKey(Resource, null=True, on_delete=models.CASCADE)
|
||||
agenda = models.ForeignKey(Agenda, null=True, on_delete=models.CASCADE)
|
||||
lock_code = models.CharField(_('Lock code'), max_length=64, blank=False)
|
||||
lock_expiration_datetime = models.DateTimeField(_('Lock expiration time'))
|
||||
start_datetime = models.DateTimeField(_('Start'))
|
||||
end_datetime = models.DateTimeField(_('End'))
|
||||
|
||||
class Meta:
|
||||
index_together = (('start_datetime', 'end_datetime'),)
|
||||
|
|
|
@ -19,8 +19,7 @@ import datetime
|
|||
import itertools
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.db.models.functions import TruncDay
|
||||
from django.http import Http404, HttpResponse
|
||||
|
@ -47,7 +46,6 @@ from chrono.agendas.models import (
|
|||
Category,
|
||||
Desk,
|
||||
Event,
|
||||
Lease,
|
||||
MeetingType,
|
||||
TimePeriodException,
|
||||
)
|
||||
|
@ -88,7 +86,6 @@ def get_all_slots(
|
|||
start_datetime=None,
|
||||
end_datetime=None,
|
||||
excluded_user_external_id=None,
|
||||
lock_code=None,
|
||||
):
|
||||
"""Get all occupation state of all possible slots for the given agenda (of
|
||||
its real agendas for a virtual agenda) and the given meeting_type.
|
||||
|
@ -106,8 +103,7 @@ def get_all_slots(
|
|||
min/max_datetime; for each time slot check its status in the exclusion
|
||||
and bookings sets.
|
||||
If it is excluded, ignore it completely.
|
||||
If it is booked, report the slot as full.
|
||||
If it is booked but match the lock code, report the slot as open.
|
||||
It if is booked, report the slot as full.
|
||||
"""
|
||||
resources = resources or []
|
||||
# virtual agendas have one constraint :
|
||||
|
@ -273,22 +269,6 @@ def get_all_slots(
|
|||
for event_start_datetime, event_duration in booked_events
|
||||
)
|
||||
|
||||
# delete old locks
|
||||
Lease.objects.filter(lock_expiration_datetime__lt=now()).delete()
|
||||
# aggregate non-expired locked time slots
|
||||
desk_locked_intervals = collections.defaultdict(lambda: IntervalSet())
|
||||
resource_locked_intervals = IntervalSet()
|
||||
q = Q(agenda__in=agendas)
|
||||
if resources:
|
||||
q |= Q(resource__in=resources)
|
||||
for lock in (
|
||||
Lease.objects.filter(q).exclude(lock_code=lock_code).order_by('start_datetime', 'end_datetime')
|
||||
):
|
||||
if lock.desk:
|
||||
desk_locked_intervals[lock.desk_id].add(lock.start_datetime, lock.end_datetime)
|
||||
if resources and lock.resource:
|
||||
resource_locked_intervals.add(lock.start_datetime, lock.end_datetime)
|
||||
|
||||
unique_booked = {}
|
||||
for time_period in base_agenda.get_effective_time_periods():
|
||||
duration = (
|
||||
|
@ -335,12 +315,7 @@ def get_all_slots(
|
|||
|
||||
# slot is full if an already booked event overlaps it
|
||||
# check resources first
|
||||
booked = False
|
||||
if resources:
|
||||
if not booked:
|
||||
booked = resources_bookings.overlaps(start_datetime, end_datetime)
|
||||
if not booked:
|
||||
booked = resource_locked_intervals.overlaps(start_datetime, end_datetime)
|
||||
booked = resources_bookings.overlaps(start_datetime, end_datetime)
|
||||
# then check user boookings
|
||||
if not booked:
|
||||
booked = user_bookings.overlaps(start_datetime, end_datetime)
|
||||
|
@ -349,9 +324,6 @@ def get_all_slots(
|
|||
booked = desk.id in bookings and bookings[desk.id].overlaps(
|
||||
start_datetime, end_datetime
|
||||
)
|
||||
# then locks
|
||||
if not booked and desk.id in desk_locked_intervals:
|
||||
booked = desk_locked_intervals[desk.id].overlaps(start_datetime, end_datetime)
|
||||
if unique and unique_booked.get(timestamp) is booked:
|
||||
continue
|
||||
unique_booked[timestamp] = booked
|
||||
|
@ -754,14 +726,6 @@ class MeetingDatetimes(APIView):
|
|||
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
|
||||
user_external_id = request.GET.get('exclude_user_external_id') or None
|
||||
|
||||
lock_code = request.GET.get('lock_code', None)
|
||||
if lock_code == '':
|
||||
raise APIError(
|
||||
_('lock_code must not be empty'),
|
||||
err_class='lock_code must not be empty',
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Generate an unique slot for each possible meeting [start_datetime,
|
||||
# end_datetime] range.
|
||||
# First use get_all_slots() to get each possible meeting by desk and
|
||||
|
@ -784,7 +748,6 @@ class MeetingDatetimes(APIView):
|
|||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
excluded_user_external_id=user_external_id,
|
||||
lock_code=lock_code,
|
||||
)
|
||||
)
|
||||
for slot in sorted(all_slots, key=lambda slot: slot[:3]):
|
||||
|
@ -982,12 +945,6 @@ class SlotSerializer(serializers.Serializer):
|
|||
force_waiting_list = serializers.BooleanField(default=False)
|
||||
use_color_for = serializers.CharField(max_length=250, allow_blank=True)
|
||||
|
||||
lock_code = serializers.CharField(max_length=64, required=False)
|
||||
lock_duration = serializers.IntegerField(
|
||||
min_value=0, default=lambda: settings.CHRONO_LOCK_DURATION
|
||||
) # in seconds
|
||||
confirm_after_lock = serializers.BooleanField(default=False)
|
||||
|
||||
|
||||
class StringOrListField(serializers.ListField):
|
||||
def to_internal_value(self, data):
|
||||
|
@ -1044,8 +1001,6 @@ class Fillslots(APIView):
|
|||
)
|
||||
payload = serializer.validated_data
|
||||
|
||||
lock_code = payload.get('lock_code')
|
||||
|
||||
if 'slots' in payload:
|
||||
slots = payload['slots']
|
||||
if not slots:
|
||||
|
@ -1168,7 +1123,6 @@ class Fillslots(APIView):
|
|||
meeting_type,
|
||||
resources=resources,
|
||||
excluded_user_external_id=user_external_id if exclude_user else None,
|
||||
lock_code=lock_code,
|
||||
),
|
||||
key=lambda slot: slot.start_datetime,
|
||||
)
|
||||
|
@ -1246,69 +1200,20 @@ class Fillslots(APIView):
|
|||
# booking requires real Event objects (not lazy Timeslots);
|
||||
# create them now, with data from the slots and the desk we found.
|
||||
events = []
|
||||
if not lock_code or payload.get('confirm_after_lock'):
|
||||
for start_datetime in datetimes:
|
||||
event = Event.objects.create(
|
||||
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,
|
||||
)
|
||||
if resources:
|
||||
event.resources.add(*resources)
|
||||
events.append(event)
|
||||
else:
|
||||
# remove existing locks
|
||||
Lease.objects.filter(lock_code=lock_code).delete()
|
||||
|
||||
# create new locks
|
||||
lock_duration = payload.get('lock_duration')
|
||||
if not lock_duration or lock_duration < settings.CHRONO_LOCK_DURATION:
|
||||
lock_duration = settings.CHRONO_LOCK_DURATION
|
||||
lock_expiration_datetime = now() + datetime.timedelta(seconds=lock_duration)
|
||||
meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
|
||||
locks = []
|
||||
for start_datetime in datetimes:
|
||||
locks.append(
|
||||
Lease(
|
||||
desk=available_desk,
|
||||
agenda=available_desk.agenda,
|
||||
lock_code=lock_code,
|
||||
lock_expiration_datetime=lock_expiration_datetime,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=start_datetime + meeting_duration,
|
||||
)
|
||||
)
|
||||
for resource in resources:
|
||||
locks.append(
|
||||
Lease(
|
||||
resource=resource,
|
||||
lock_code=lock_code,
|
||||
lock_expiration_datetime=lock_expiration_datetime,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=start_datetime + meeting_duration,
|
||||
)
|
||||
)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
Lease.objects.bulk_create(locks)
|
||||
except IntegrityError:
|
||||
raise APIError(
|
||||
_('no more desk available'),
|
||||
err_class='no more desk available',
|
||||
)
|
||||
else:
|
||||
return Response({'err': 0})
|
||||
else:
|
||||
if lock_code:
|
||||
raise APIError(
|
||||
_('lock_code does not work with events'),
|
||||
err_class='lock_code does not work with events',
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
for start_datetime in datetimes:
|
||||
event = Event.objects.create(
|
||||
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,
|
||||
)
|
||||
if resources:
|
||||
event.resources.add(*resources)
|
||||
events.append(event)
|
||||
else:
|
||||
# convert event recurrence identifiers to real event slugs
|
||||
for i, slot in enumerate(slots.copy()):
|
||||
if ':' not in slot:
|
||||
|
@ -1374,9 +1279,6 @@ class Fillslots(APIView):
|
|||
cancelled_booking_id = to_cancel_booking.pk
|
||||
to_cancel_booking.cancel()
|
||||
|
||||
if lock_code:
|
||||
Lease.objects.filter(lock_code=lock_code).delete()
|
||||
|
||||
# now we have a list of events, book them.
|
||||
primary_booking = None
|
||||
for event in events:
|
||||
|
|
|
@ -169,9 +169,6 @@ MELLON_IDENTITY_PROVIDERS = []
|
|||
# (see http://docs.python-requests.org/en/master/user/advanced/#proxies)
|
||||
REQUESTS_PROXIES = None
|
||||
|
||||
# default lock duration, in seconds
|
||||
CHRONO_LOCK_DURATION = 10 * 60
|
||||
|
||||
# timeout used in python-requests call, in seconds
|
||||
# we use 28s by default: timeout just before web server, which is usually 30s
|
||||
REQUESTS_TIMEOUT = 28
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
import urllib.parse as urlparse
|
||||
|
||||
import pytest
|
||||
|
||||
from chrono.agendas.models import Booking, Lease, MeetingType, Resource
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_booking_api_meeting_lock_and_confirm(app, meetings_agenda, user):
|
||||
agenda_id = meetings_agenda.slug
|
||||
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
||||
|
||||
# list free slots, with or without a lock
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
free_slots = len(resp.json['data'])
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=OTHERLOCK' % meeting_type.id)
|
||||
assert free_slots == len(resp.json['data'])
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=MYLOCK' % meeting_type.id)
|
||||
assert free_slots == len(resp.json['data'])
|
||||
|
||||
# lock a slot
|
||||
event_id = resp.json['data'][2]['id']
|
||||
assert urlparse.urlparse(
|
||||
resp.json['data'][2]['api']['fillslot_url']
|
||||
).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id), params={'lock_code': 'MYLOCK'})
|
||||
assert Booking.objects.count() == 0
|
||||
assert Lease.objects.count() == 1
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda=meetings_agenda,
|
||||
desk=meetings_agenda.desk_set.get(),
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
|
||||
# list free slots: one is locked ...
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 1
|
||||
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=OTHERLOCK' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 1
|
||||
|
||||
# ... unless it's MYLOCK
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=MYLOCK' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 0
|
||||
|
||||
# can't lock the same timeslot ...
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id), params={'lock_code': 'OTHERLOCK'}
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
# ... unless with MYLOCK (aka "relock")
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id), params={'lock_code': 'MYLOCK'}
|
||||
)
|
||||
assert resp_booking.json['err'] == 0
|
||||
assert Booking.objects.count() == 0
|
||||
assert Lease.objects.count() == 1
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda=meetings_agenda,
|
||||
desk=meetings_agenda.desk_set.get(),
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
|
||||
# can't book the slot ...
|
||||
resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id), params={'confirm_after_lock': True}
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id),
|
||||
params={'lock_code': 'OTHERLOCK', 'confirm_after_lock': True},
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
# ... unless with MYLOCK (aka "confirm")
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id),
|
||||
params={'lock_code': 'MYLOCK', 'confirm_after_lock': True},
|
||||
)
|
||||
assert resp_booking.json['err'] == 0
|
||||
assert Booking.objects.count() == 1
|
||||
assert Lease.objects.count() == 0
|
||||
|
||||
|
||||
def test_booking_api_meeting_lock_and_confirm_with_resource(app, meetings_agenda, user):
|
||||
resource1 = Resource.objects.create(label='Resource 1', slug='re1')
|
||||
resource2 = Resource.objects.create(label='Resource 2', slug='re2')
|
||||
meetings_agenda.resources.add(resource1, resource2)
|
||||
agenda_id = meetings_agenda.slug
|
||||
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
||||
|
||||
# list free slots, with or without a lock
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/?resources=re1' % meeting_type.id)
|
||||
free_slots = len(resp.json['data'])
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=OTHERLOCK&resources=re1' % meeting_type.id)
|
||||
assert free_slots == len(resp.json['data'])
|
||||
resp = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=MYLOCK&resources=re1' % meeting_type.id)
|
||||
assert free_slots == len(resp.json['data'])
|
||||
|
||||
# lock a slot
|
||||
event_id = resp.json['data'][2]['id']
|
||||
assert urlparse.urlparse(
|
||||
resp.json['data'][2]['api']['fillslot_url']
|
||||
).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id), params={'lock_code': 'MYLOCK'}
|
||||
)
|
||||
assert Booking.objects.count() == 0
|
||||
assert Lease.objects.count() == 2
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda=meetings_agenda,
|
||||
desk=meetings_agenda.desk_set.get(),
|
||||
resource__isnull=True,
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda__isnull=True,
|
||||
desk__isnull=True,
|
||||
resource=resource1,
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
old_lock_ids = set(Lease.objects.values_list('id', flat=True))
|
||||
|
||||
# list free slots: one is locked ...
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/?resources=re1' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 1
|
||||
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=OTHERLOCK&resources=re1' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 1
|
||||
|
||||
# ... unless it's MYLOCK
|
||||
resp2 = app.get('/api/agenda/meetings/%s/datetimes/?lock_code=MYLOCK&resources=re1' % meeting_type.id)
|
||||
assert free_slots == len([x for x in resp2.json['data']])
|
||||
assert len([x for x in resp2.json['data'] if x.get('disabled')]) == 0
|
||||
|
||||
# can't lock the same timeslot ...
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id), params={'lock_code': 'OTHERLOCK'}
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
# ... unless with MYLOCK (aka "relock")
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id), params={'lock_code': 'MYLOCK'}
|
||||
)
|
||||
assert resp_booking.json['err'] == 0
|
||||
assert Booking.objects.count() == 0
|
||||
assert Lease.objects.count() == 2
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda=meetings_agenda,
|
||||
desk=meetings_agenda.desk_set.get(),
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
assert (
|
||||
Lease.objects.filter(
|
||||
agenda__isnull=True,
|
||||
desk__isnull=True,
|
||||
resource=resource1,
|
||||
lock_code='MYLOCK',
|
||||
lock_expiration_datetime__isnull=False,
|
||||
).count()
|
||||
== 1
|
||||
)
|
||||
new_lock_ids = set(Lease.objects.values_list('id', flat=True))
|
||||
assert not (old_lock_ids & new_lock_ids)
|
||||
|
||||
# can't book the slot ...
|
||||
resp_booking = app.post('/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id))
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id),
|
||||
params={'confirm_after_lock': True},
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id),
|
||||
params={'lock_code': 'OTHERLOCK', 'confirm_after_lock': True},
|
||||
)
|
||||
assert resp_booking.json['err'] == 1
|
||||
assert resp_booking.json['reason'] == 'no more desk available'
|
||||
|
||||
# ... unless with MYLOCK (aka "confirm")
|
||||
resp_booking = app.post(
|
||||
'/api/agenda/%s/fillslot/%s/?resources=re1' % (agenda_id, event_id),
|
||||
params={'lock_code': 'MYLOCK', 'confirm_after_lock': True},
|
||||
)
|
||||
assert resp_booking.json['err'] == 0
|
||||
assert Booking.objects.count() == 1
|
||||
assert Lease.objects.count() == 0
|
|
@ -289,7 +289,7 @@ def test_datetimes_api_meetings_agenda_with_resources(app):
|
|||
)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(api_url)
|
||||
assert len(ctx.captured_queries) == 12
|
||||
assert len(ctx.captured_queries) == 10
|
||||
assert len(resp.json['data']) == 32
|
||||
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
||||
'%s 09:00:00' % tomorrow_str,
|
||||
|
@ -501,7 +501,7 @@ def test_datetimes_api_meetings_agenda_exclude_slots(app):
|
|||
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '42'},
|
||||
)
|
||||
assert len(ctx.captured_queries) == 11
|
||||
assert len(ctx.captured_queries) == 9
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is True
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
|
@ -1476,7 +1476,7 @@ def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, mock_now):
|
|||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(api_url)
|
||||
assert len(resp.json['data']) == 12
|
||||
assert len(ctx.captured_queries) == 12
|
||||
assert len(ctx.captured_queries) == 10
|
||||
|
||||
# simulate booking
|
||||
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
||||
|
@ -1605,7 +1605,7 @@ def test_virtual_agendas_meetings_datetimes_exclude_slots(app):
|
|||
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '42'},
|
||||
)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert len(ctx.captured_queries) == 11
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is True
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
|
@ -1658,7 +1658,7 @@ def test_unavailabilitycalendar_meetings_datetimes(app, user):
|
|||
# 2 slots are gone
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp2 = app.get(datetimes_url)
|
||||
assert len(ctx.captured_queries) == 12
|
||||
assert len(ctx.captured_queries) == 10
|
||||
assert len(resp.json['data']) == len(resp2.json['data']) + 2
|
||||
|
||||
# add a standard desk exception
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
import datetime
|
||||
from argparse import Namespace
|
||||
|
||||
import pytest
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.utils.timezone import now
|
||||
|
||||
from chrono.agendas.models import Agenda, Desk, Lease, MeetingType, Resource
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lock(db):
|
||||
agenda = Agenda.objects.create(
|
||||
label=u'Foo bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
|
||||
)
|
||||
meeting_type = MeetingType(agenda=agenda, label='Blah', duration=30)
|
||||
meeting_type.save()
|
||||
desk1 = Desk.objects.create(agenda=agenda, label='Desk 1')
|
||||
desk2 = Desk.objects.create(agenda=agenda, label='Desk 2')
|
||||
resource = Resource.objects.create(label='re', description='re')
|
||||
return Namespace(**locals())
|
||||
|
||||
|
||||
def test_lock_constraint_desk(lock):
|
||||
Lease.objects.create(
|
||||
agenda=lock.agenda,
|
||||
desk=lock.desk1,
|
||||
lock_code='1',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
Lease.objects.create(
|
||||
agenda=lock.agenda,
|
||||
desk=lock.desk2,
|
||||
lock_code='2',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
Lease.objects.create(
|
||||
resource=lock.resource,
|
||||
lock_code='3',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
# prevent IntegrityError to break the current transaction
|
||||
with transaction.atomic():
|
||||
Lease.objects.create(
|
||||
agenda=lock.agenda,
|
||||
desk=lock.desk1,
|
||||
lock_code='4',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
# interval overlaps interval of first lock
|
||||
start_datetime=now() + datetime.timedelta(minutes=4),
|
||||
end_datetime=now() + datetime.timedelta(minutes=6),
|
||||
)
|
||||
|
||||
|
||||
def test_lock_constraint_resource(lock):
|
||||
Lease.objects.create(
|
||||
agenda=lock.agenda,
|
||||
desk=lock.desk1,
|
||||
lock_code='1',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
Lease.objects.create(
|
||||
agenda=lock.agenda,
|
||||
desk=lock.desk2,
|
||||
lock_code='2',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
Lease.objects.create(
|
||||
resource=lock.resource,
|
||||
lock_code='3',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
start_datetime=now(),
|
||||
end_datetime=now() + datetime.timedelta(minutes=5),
|
||||
)
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
# prevent IntegrityError to break the current transaction
|
||||
with transaction.atomic():
|
||||
Lease.objects.create(
|
||||
resource=lock.resource,
|
||||
lock_code='4',
|
||||
lock_expiration_datetime=now() + datetime.timedelta(minutes=5),
|
||||
# interval overlaps interval of first lock
|
||||
start_datetime=now() + datetime.timedelta(minutes=4),
|
||||
end_datetime=now() + datetime.timedelta(minutes=6),
|
||||
)
|
Loading…
Reference in New Issue