api: book also resources in fillslot(s) endpoint (#38942)
This commit is contained in:
parent
a1d38b0a88
commit
4cc127c09e
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue