api: book also resources in fillslot(s) endpoint (#38942)

This commit is contained in:
Lauréline Guérin 2020-05-26 15:11:39 +02:00
parent a1d38b0a88
commit 4cc127c09e
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
2 changed files with 169 additions and 25 deletions

View File

@ -40,6 +40,25 @@ from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriodExce
from ..interval import IntervalSet
class APIError(Exception):
err = 1
http_status = 200
def __init__(self, *args, **kwargs):
self.__dict__.update(kwargs)
super(APIError, self).__init__(*args)
def to_response(self):
data = {
'err': self.err,
'err_class': self.err_class,
'err_desc': force_text(self),
}
if hasattr(self, 'errors'):
data['errors'] = self.errors
return Response(data, status=self.http_status)
def format_response_datetime(dt):
return localtime(dt).strftime('%Y-%m-%d %H:%M:%S')
@ -310,6 +329,22 @@ def get_event_places(event):
return places
def get_resources_from_request(request, agenda):
if agenda.kind != 'meetings' or 'resources' not in request.GET:
return []
resources_slugs = [s for s in request.GET['resources'].split(',') if s]
resources = list(agenda.resources.filter(slug__in=resources_slugs))
if len(resources) != len(resources_slugs):
unknown_slugs = set(resources_slugs) - set([r.slug for r in resources])
unknown_slugs = sorted(list(unknown_slugs))
raise APIError(
_('invalid resource: %s') % ', '.join(unknown_slugs),
err_class='invalid resource: %s' % ', '.join(unknown_slugs),
http_status=status.HTTP_400_BAD_REQUEST,
)
return resources
class Agendas(APIView):
permission_classes = ()
@ -447,21 +482,10 @@ class MeetingDatetimes(APIView):
now_datetime = now()
resources = None
if agenda.kind == 'meetings' and 'resources' in request.GET:
resources_slugs = [s for s in request.GET['resources'].split(',') if s]
resources = list(agenda.resources.filter(slug__in=resources_slugs))
if len(resources) != len(resources_slugs):
unknown_slugs = set(resources_slugs) - set([r.slug for r in resources])
unknown_slugs = sorted(list(unknown_slugs))
return Response(
{
'err': 1,
'err_class': 'invalid resource: %s' % ', '.join(unknown_slugs),
'err_desc': _('invalid resource: %s') % ', '.join(unknown_slugs),
},
status=status.HTTP_400_BAD_REQUEST,
)
try:
resources = get_resources_from_request(request, agenda)
except APIError as e:
return e.to_response()
# Generate an unique slot for each possible meeting [start_datetime,
# end_datetime] range.
@ -756,10 +780,15 @@ class Fillslots(APIView):
)
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
try:
resources = get_resources_from_request(request, agenda)
except APIError as e:
return e.to_response()
# get all free slots and separate them by desk
try:
all_slots = sorted(
get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id)),
get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id), resources=resources),
key=lambda slot: slot.start_datetime,
)
except (MeetingType.DoesNotExist, ValueError):
@ -847,16 +876,17 @@ class Fillslots(APIView):
# create them now, with data from the slots and the desk we found.
events = []
for start_datetime in datetimes:
events.append(
Event.objects.create(
agenda=available_desk.agenda,
meeting_type_id=meeting_type_id,
start_datetime=start_datetime,
full=False,
places=1,
desk=available_desk,
)
event = Event.objects.create(
agenda=available_desk.agenda,
meeting_type_id=meeting_type_id,
start_datetime=start_datetime,
full=False,
places=1,
desk=available_desk,
)
if resources:
event.resources.add(*resources)
events.append(event)
else:
try:
events = Event.objects.filter(id__in=[int(s) for s in slots]).order_by('start_datetime')
@ -973,6 +1003,8 @@ class Fillslots(APIView):
}
for x in events
]
if agenda.kind == 'meetings':
response['resources'] = [r.slug for r in resources]
return Response(response)

View File

@ -1080,6 +1080,118 @@ def test_booking_api_meeting(app, meetings_agenda, user):
assert Booking.objects.count() == 2
def test_booking_api_meeting_with_resources(app, user):
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
tomorrow_str = tomorrow.isoformat()
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
resource1 = Resource.objects.create(label='Resource 1')
resource2 = Resource.objects.create(label='Resource 2')
resource3 = Resource.objects.create(label='Resource 3')
agenda.resources.add(resource1, resource2, resource3)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
TimePeriod.objects.create(
weekday=tomorrow.weekday(), start_time=datetime.time(9, 0), end_time=datetime.time(17, 00), desk=desk,
)
app.authorization = ('Basic', ('john.doe', 'password'))
# make a booking without resource
resp = app.post('/api/agenda/%s/fillslot/%s:%s-1000/' % (agenda.pk, meeting_type.pk, tomorrow_str))
assert resp.json['datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:30:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == []
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == []
# now try to book also a resource - slot not free
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-1000/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
)
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'
# slot is free
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
)
assert resp.json['datetime'] == '%s 09:00:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug]
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == [resource1]
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
)
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug, resource2.slug]
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == [resource1, resource2]
# resource is unknown or not valid for this agenda
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid resource: foobarblah' # legacy
assert resp.json['err_class'] == 'invalid resource: foobarblah'
assert resp.json['err_desc'] == 'invalid resource: foobarblah'
agenda.resources.remove(resource3)
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid resource: resource-3' # legacy
assert resp.json['err_class'] == 'invalid resource: resource-3'
assert resp.json['err_desc'] == 'invalid resource: resource-3'
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid resource: foobarblah, resource-3' # legacy
assert resp.json['err_class'] == 'invalid resource: foobarblah, resource-3'
assert resp.json['err_desc'] == 'invalid resource: foobarblah, resource-3'
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid resource: foobarblah' # legacy
assert resp.json['err_class'] == 'invalid resource: foobarblah'
assert resp.json['err_desc'] == 'invalid resource: foobarblah'
# booking is canceled: slot is free
booking.cancel()
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
)
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug, resource2.slug]
booking = Booking.objects.latest('pk')
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)