2016-02-13 16:31:14 +01:00
|
|
|
# chrono - agendas system
|
|
|
|
# Copyright (C) 2016 Entr'ouvert
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of the GNU Affero General Public License as published
|
|
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2017-09-01 15:01:07 +02:00
|
|
|
from collections import defaultdict
|
2017-08-24 14:15:18 +02:00
|
|
|
from copy import deepcopy
|
2016-09-11 11:31:29 +02:00
|
|
|
import datetime
|
2017-09-01 15:01:07 +02:00
|
|
|
import operator
|
|
|
|
|
2016-06-20 09:34:53 +02:00
|
|
|
from django.core.urlresolvers import reverse
|
2016-10-28 17:52:21 +02:00
|
|
|
from django.http import Http404
|
2016-07-20 13:22:58 +02:00
|
|
|
from django.shortcuts import get_object_or_404
|
2018-03-25 11:26:47 +02:00
|
|
|
from django.utils import six
|
2017-05-15 16:37:20 +02:00
|
|
|
from django.utils.dateparse import parse_date
|
2018-03-25 11:26:47 +02:00
|
|
|
from django.utils.encoding import force_text
|
2017-04-25 18:37:36 +02:00
|
|
|
from django.utils.timezone import now, make_aware, localtime
|
2016-02-13 16:31:14 +01:00
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
from rest_framework import permissions, serializers, status
|
2016-02-13 16:31:14 +01:00
|
|
|
from rest_framework.response import Response
|
2016-03-30 00:51:34 +02:00
|
|
|
from rest_framework.views import APIView
|
2016-02-13 16:31:14 +01:00
|
|
|
|
2017-09-01 15:01:07 +02:00
|
|
|
from ..agendas.models import (Agenda, Event, Booking, MeetingType,
|
|
|
|
TimePeriod, Desk)
|
2017-12-16 05:37:00 +01:00
|
|
|
from ..interval import Intervals
|
2017-09-01 15:01:07 +02:00
|
|
|
|
|
|
|
|
2017-08-24 14:15:18 +02:00
|
|
|
def get_exceptions_by_desk(agenda):
|
|
|
|
exceptions_by_desk = {}
|
|
|
|
for desk in Desk.objects.filter(agenda=agenda).prefetch_related('timeperiodexception_set'):
|
2017-09-27 12:01:53 +02:00
|
|
|
exceptions_by_desk[desk.id] = [(exc.start_datetime, exc.end_datetime) for exc in desk.timeperiodexception_set.all()]
|
2017-08-24 14:15:18 +02:00
|
|
|
return exceptions_by_desk
|
|
|
|
|
|
|
|
|
|
|
|
def get_all_slots(agenda, meeting_type):
|
2017-09-01 15:01:07 +02:00
|
|
|
min_datetime = now() + datetime.timedelta(days=agenda.minimal_booking_delay)
|
|
|
|
max_datetime = now() + datetime.timedelta(days=agenda.maximal_booking_delay)
|
2017-09-06 19:24:15 +02:00
|
|
|
min_datetime = min_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
max_datetime = max_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
max_datetime = max_datetime + datetime.timedelta(days=1)
|
|
|
|
|
2017-09-01 15:01:07 +02:00
|
|
|
time_period_filters = {
|
|
|
|
'min_datetime': min_datetime,
|
|
|
|
'max_datetime': max_datetime,
|
|
|
|
'meeting_type': meeting_type
|
|
|
|
}
|
|
|
|
|
2018-01-29 16:28:32 +01:00
|
|
|
base_date = now().date()
|
2017-12-16 05:37:00 +01:00
|
|
|
open_slots_by_desk = defaultdict(lambda: Intervals())
|
2017-09-01 15:01:07 +02:00
|
|
|
for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
|
2018-01-29 16:28:32 +01:00
|
|
|
duration = (datetime.datetime.combine(base_date, time_period.end_time) -
|
|
|
|
datetime.datetime.combine(base_date, time_period.start_time)).seconds / 60
|
|
|
|
if duration < meeting_type.duration:
|
|
|
|
# skip time period that can't even hold a single meeting
|
|
|
|
continue
|
2017-09-01 15:01:07 +02:00
|
|
|
for slot in time_period.get_time_slots(**time_period_filters):
|
2017-12-16 05:37:00 +01:00
|
|
|
slot.full = False
|
|
|
|
open_slots_by_desk[time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
|
2017-08-24 14:15:18 +02:00
|
|
|
|
|
|
|
# remove excluded slot
|
|
|
|
excluded_slot_by_desk = get_exceptions_by_desk(agenda)
|
2018-03-25 11:26:47 +02:00
|
|
|
for desk, excluded_interval in excluded_slot_by_desk.items():
|
2017-09-27 12:01:53 +02:00
|
|
|
for interval in excluded_interval:
|
|
|
|
begin, end = interval
|
2017-12-01 13:21:50 +01:00
|
|
|
open_slots_by_desk[desk].remove_overlap(localtime(begin), localtime(end))
|
2017-09-01 15:01:07 +02:00
|
|
|
|
|
|
|
for event in agenda.event_set.filter(
|
|
|
|
agenda=agenda, start_datetime__gte=min_datetime,
|
|
|
|
start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration)).select_related(
|
2017-12-16 05:37:00 +01:00
|
|
|
'meeting_type').exclude(
|
|
|
|
booking__cancellation_datetime__isnull=False):
|
|
|
|
for slot in open_slots_by_desk[event.desk_id].search_data(event.start_datetime, event.end_datetime):
|
|
|
|
slot.full = True
|
2017-09-01 15:01:07 +02:00
|
|
|
|
2017-12-16 05:37:00 +01:00
|
|
|
slots = []
|
|
|
|
for desk in open_slots_by_desk:
|
|
|
|
slots.extend(open_slots_by_desk[desk].iter_data())
|
|
|
|
slots.sort(key=lambda slot: slot.start_datetime)
|
|
|
|
return slots
|
2016-06-19 21:23:23 +02:00
|
|
|
|
|
|
|
|
2017-06-26 14:51:36 +02:00
|
|
|
def get_agenda_detail(request, agenda):
|
|
|
|
agenda_detail = {
|
2017-10-07 11:38:06 +02:00
|
|
|
'id': agenda.slug,
|
|
|
|
'slug': agenda.slug, # kept for compatibility
|
2017-06-26 14:51:36 +02:00
|
|
|
'text': agenda.label,
|
|
|
|
'kind': agenda.kind,
|
|
|
|
}
|
|
|
|
|
|
|
|
if agenda.kind == 'events':
|
|
|
|
agenda_detail['api'] = {
|
|
|
|
'datetimes_url': request.build_absolute_uri(
|
|
|
|
reverse('api-agenda-datetimes',
|
|
|
|
kwargs={'agenda_identifier': agenda.slug}))
|
|
|
|
}
|
|
|
|
elif agenda.kind == 'meetings':
|
|
|
|
agenda_detail['api'] = {
|
|
|
|
'meetings_url': request.build_absolute_uri(
|
|
|
|
reverse('api-agenda-meetings',
|
2017-10-07 11:33:47 +02:00
|
|
|
kwargs={'agenda_identifier': agenda.slug})),
|
|
|
|
'desks_url': request.build_absolute_uri(
|
|
|
|
reverse('api-agenda-desks',
|
2017-06-26 14:51:36 +02:00
|
|
|
kwargs={'agenda_identifier': agenda.slug}))
|
|
|
|
}
|
|
|
|
|
|
|
|
return agenda_detail
|
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class Agendas(APIView):
|
2017-01-13 14:59:10 +01:00
|
|
|
permission_classes = ()
|
|
|
|
|
2017-06-10 15:52:40 +02:00
|
|
|
def get(self, request, format=None):
|
2017-06-26 14:51:36 +02:00
|
|
|
agendas = [get_agenda_detail(request, agenda)
|
|
|
|
for agenda in Agenda.objects.all().order_by('label')]
|
2017-06-10 15:52:40 +02:00
|
|
|
return Response({'data': agendas})
|
2016-06-19 21:23:23 +02:00
|
|
|
|
|
|
|
agendas = Agendas.as_view()
|
2016-02-13 16:31:14 +01:00
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class AgendaDetail(APIView):
|
2017-06-26 14:51:36 +02:00
|
|
|
'''
|
|
|
|
Retrieve an agenda instance.
|
|
|
|
'''
|
|
|
|
permission_classes = ()
|
|
|
|
|
|
|
|
def get(self, request, agenda_identifier):
|
|
|
|
agenda = get_object_or_404(Agenda, slug=agenda_identifier)
|
|
|
|
return Response({'data': get_agenda_detail(request, agenda)})
|
|
|
|
|
|
|
|
agenda_detail = AgendaDetail.as_view()
|
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class Datetimes(APIView):
|
2017-01-13 14:59:10 +01:00
|
|
|
permission_classes = ()
|
|
|
|
|
2016-10-28 17:52:21 +02:00
|
|
|
def get(self, request, agenda_identifier=None, format=None):
|
|
|
|
try:
|
|
|
|
agenda = Agenda.objects.get(slug=agenda_identifier)
|
|
|
|
except Agenda.DoesNotExist:
|
|
|
|
try:
|
|
|
|
# legacy access by agenda id
|
|
|
|
agenda = Agenda.objects.get(id=int(agenda_identifier))
|
2017-06-09 20:03:21 +02:00
|
|
|
except (ValueError, Agenda.DoesNotExist):
|
2016-10-28 17:52:21 +02:00
|
|
|
raise Http404()
|
2016-09-11 11:31:29 +02:00
|
|
|
if agenda.kind != 'events':
|
2017-06-10 21:22:29 +02:00
|
|
|
raise Http404('agenda found, but it was not an events agenda')
|
2016-09-11 11:31:29 +02:00
|
|
|
|
2017-05-15 16:37:20 +02:00
|
|
|
entries = Event.objects.filter(agenda=agenda)
|
|
|
|
|
|
|
|
# we never want to allow booking for past events.
|
|
|
|
entries = entries.filter(start_datetime__gte=localtime(now()))
|
|
|
|
|
2017-01-20 10:35:44 +01:00
|
|
|
if agenda.minimal_booking_delay:
|
2017-05-15 16:37:20 +02:00
|
|
|
entries = entries.filter(
|
2017-12-30 15:48:48 +01:00
|
|
|
start_datetime__gte=localtime(now() + datetime.timedelta(days=agenda.minimal_booking_delay)).replace(hour=0, minute=0))
|
2017-05-15 16:37:20 +02:00
|
|
|
|
2016-09-11 19:16:15 +02:00
|
|
|
if agenda.maximal_booking_delay:
|
2017-05-15 16:37:20 +02:00
|
|
|
entries = entries.filter(
|
2017-12-30 15:48:48 +01:00
|
|
|
start_datetime__lt=localtime(now() + datetime.timedelta(days=agenda.maximal_booking_delay)).replace(hour=0, minute=0))
|
2017-05-15 16:37:20 +02:00
|
|
|
|
|
|
|
if 'date_start' in request.GET:
|
2017-12-30 15:48:48 +01:00
|
|
|
entries = entries.filter(start_datetime__gte=make_aware(
|
|
|
|
datetime.datetime.combine(parse_date(request.GET['date_start']), datetime.time(0, 0))))
|
2016-09-11 19:16:15 +02:00
|
|
|
|
2017-05-15 16:37:20 +02:00
|
|
|
if 'date_end' in request.GET:
|
2017-12-30 15:48:48 +01:00
|
|
|
entries = entries.filter(start_datetime__lt=make_aware(
|
|
|
|
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))))
|
2016-09-11 11:31:29 +02:00
|
|
|
|
2016-10-09 11:37:51 +02:00
|
|
|
response = {'data': [{'id': x.id,
|
2018-03-25 11:26:47 +02:00
|
|
|
'text': force_text(x),
|
2017-05-05 17:11:07 +02:00
|
|
|
'datetime': localtime(x.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
2017-05-02 15:13:11 +02:00
|
|
|
'disabled': bool(x.full),
|
|
|
|
'api': {
|
|
|
|
'fillslot_url': request.build_absolute_uri(
|
|
|
|
reverse('api-fillslot',
|
|
|
|
kwargs={
|
|
|
|
'agenda_identifier': agenda.slug,
|
|
|
|
'event_pk': x.id,
|
|
|
|
})),
|
2018-03-06 17:04:32 +01:00
|
|
|
'status_url': request.build_absolute_uri(
|
|
|
|
reverse('api-event-status',
|
|
|
|
kwargs={
|
|
|
|
'agenda_identifier': agenda.slug,
|
|
|
|
'event_pk': x.id,
|
|
|
|
}))
|
2017-05-02 15:13:11 +02:00
|
|
|
},
|
|
|
|
} for x in entries]}
|
2016-02-13 16:31:14 +01:00
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
datetimes = Datetimes.as_view()
|
2016-02-13 16:52:04 +01:00
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class MeetingDatetimes(APIView):
|
2017-01-13 14:59:10 +01:00
|
|
|
permission_classes = ()
|
|
|
|
|
2016-10-28 19:36:29 +02:00
|
|
|
def get(self, request, agenda_identifier=None, meeting_identifier=None, format=None):
|
|
|
|
try:
|
|
|
|
if agenda_identifier is None:
|
|
|
|
# legacy access by meeting id
|
|
|
|
meeting_type = MeetingType.objects.get(id=meeting_identifier)
|
|
|
|
else:
|
|
|
|
meeting_type = MeetingType.objects.get(slug=meeting_identifier,
|
|
|
|
agenda__slug=agenda_identifier)
|
2016-10-29 20:39:52 +02:00
|
|
|
except (ValueError, MeetingType.DoesNotExist):
|
2016-10-28 19:36:29 +02:00
|
|
|
raise Http404()
|
|
|
|
|
2016-09-11 11:31:29 +02:00
|
|
|
agenda = meeting_type.agenda
|
|
|
|
|
2016-09-11 19:16:15 +02:00
|
|
|
now_datetime = now()
|
2017-09-01 15:01:07 +02:00
|
|
|
|
2017-12-16 05:37:00 +01:00
|
|
|
slots = get_all_slots(agenda, meeting_type)
|
|
|
|
entries = {}
|
|
|
|
for slot in slots:
|
|
|
|
if slot.start_datetime < now_datetime:
|
2016-09-11 19:16:15 +02:00
|
|
|
continue
|
2017-12-16 05:37:00 +01:00
|
|
|
key = (slot.start_datetime, slot.end_datetime)
|
|
|
|
if key in entries and slot.full:
|
|
|
|
continue
|
|
|
|
entries[key] = slot
|
|
|
|
slots = sorted(entries.values(), key=lambda x: x.start_datetime)
|
2016-09-11 11:31:29 +02:00
|
|
|
|
2018-04-04 10:47:16 +02:00
|
|
|
# create fillslot API URL as a template, to avoid expensive calls
|
|
|
|
# to request.build_absolute_uri()
|
2017-08-10 09:25:14 +02:00
|
|
|
fake_event_pk = '__event_id__'
|
|
|
|
fillslot_url = request.build_absolute_uri(
|
|
|
|
reverse('api-fillslot',
|
|
|
|
kwargs={
|
|
|
|
'agenda_identifier': agenda.slug,
|
|
|
|
'event_pk': fake_event_pk,
|
|
|
|
}))
|
|
|
|
|
2017-01-20 10:50:07 +01:00
|
|
|
response = {'data': [{'id': x.id,
|
2017-05-05 17:11:07 +02:00
|
|
|
'datetime': localtime(x.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
2018-03-25 11:26:47 +02:00
|
|
|
'text': force_text(x),
|
2017-08-09 20:53:45 +02:00
|
|
|
'disabled': bool(x.full),
|
2017-05-02 15:13:11 +02:00
|
|
|
'api': {
|
2017-08-10 09:25:14 +02:00
|
|
|
'fillslot_url': fillslot_url.replace(fake_event_pk, str(x.id)),
|
|
|
|
},
|
2017-12-16 05:37:00 +01:00
|
|
|
} for x in slots]}
|
2016-09-11 11:31:29 +02:00
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
meeting_datetimes = MeetingDatetimes.as_view()
|
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class MeetingList(APIView):
|
2017-06-10 16:04:32 +02:00
|
|
|
permission_classes = ()
|
|
|
|
|
|
|
|
def get(self, request, agenda_identifier=None, format=None):
|
|
|
|
try:
|
|
|
|
agenda = Agenda.objects.get(slug=agenda_identifier)
|
|
|
|
except Agenda.DoesNotExist:
|
|
|
|
raise Http404()
|
|
|
|
if agenda.kind != 'meetings':
|
|
|
|
raise Http404('agenda found, but it was not a meetings agenda')
|
|
|
|
|
|
|
|
meeting_types = []
|
|
|
|
for meeting_type in agenda.meetingtype_set.all():
|
|
|
|
meeting_types.append({
|
|
|
|
'text': meeting_type.label,
|
|
|
|
'id': meeting_type.slug,
|
|
|
|
'api': {
|
|
|
|
'datetimes_url': request.build_absolute_uri(
|
|
|
|
reverse('api-agenda-meeting-datetimes',
|
|
|
|
kwargs={'agenda_identifier': agenda.slug,
|
|
|
|
'meeting_identifier': meeting_type.slug})),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return Response({'data': meeting_types})
|
|
|
|
|
|
|
|
meeting_list = MeetingList.as_view()
|
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class AgendaDeskList(APIView):
|
2017-10-07 11:33:47 +02:00
|
|
|
permission_classes = ()
|
|
|
|
|
|
|
|
def get(self, request, agenda_identifier=None, format=None):
|
|
|
|
try:
|
|
|
|
agenda = Agenda.objects.get(slug=agenda_identifier)
|
|
|
|
except Agenda.DoesNotExist:
|
|
|
|
raise Http404()
|
|
|
|
if agenda.kind != 'meetings':
|
|
|
|
raise Http404('agenda found, but it was not a meetings agenda')
|
|
|
|
|
|
|
|
desks = [{'id': x.slug, 'text': x.label} for x in agenda.desk_set.all()]
|
|
|
|
return Response({'data': desks})
|
|
|
|
|
|
|
|
agenda_desk_list = AgendaDeskList.as_view()
|
|
|
|
|
|
|
|
|
2016-02-13 16:52:04 +01:00
|
|
|
class SlotSerializer(serializers.Serializer):
|
2018-04-04 16:07:00 +02:00
|
|
|
label = serializers.CharField(max_length=150, allow_blank=True, required=False)
|
|
|
|
user_name = serializers.CharField(max_length=250, allow_blank=True, required=False)
|
|
|
|
backoffice_url = serializers.URLField(allow_blank=True, required=False)
|
|
|
|
count = serializers.IntegerField(min_value=1, required=False)
|
2016-02-13 16:52:04 +01:00
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class Fillslot(APIView):
|
2016-06-18 11:59:26 +02:00
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
2016-02-13 16:52:04 +01:00
|
|
|
|
2016-10-28 17:52:21 +02:00
|
|
|
def post(self, request, agenda_identifier=None, event_pk=None, format=None):
|
|
|
|
try:
|
|
|
|
agenda = Agenda.objects.get(slug=agenda_identifier)
|
|
|
|
except Agenda.DoesNotExist:
|
|
|
|
try:
|
|
|
|
# legacy access by agenda id
|
|
|
|
agenda = Agenda.objects.get(id=int(agenda_identifier))
|
2017-06-09 20:03:21 +02:00
|
|
|
except (ValueError, Agenda.DoesNotExist):
|
2016-10-28 17:52:21 +02:00
|
|
|
raise Http404()
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
serializer = SlotSerializer(data=request.data)
|
|
|
|
if not serializer.is_valid():
|
|
|
|
return Response({
|
|
|
|
'err': 1,
|
|
|
|
'reason': 'invalid payload',
|
|
|
|
'errors': serializer.errors
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
label = serializer.validated_data.get('label') or ''
|
|
|
|
user_name = serializer.validated_data.get('user_name') or ''
|
|
|
|
backoffice_url = serializer.validated_data.get('backoffice_url') or ''
|
|
|
|
places_count = serializer.validated_data.get('count') or 1
|
|
|
|
extra_data = {}
|
|
|
|
for k, v in request.data.items():
|
|
|
|
if k not in serializer.validated_data:
|
|
|
|
extra_data[k] = v
|
|
|
|
|
2017-06-11 12:42:10 +02:00
|
|
|
if 'count' in request.GET:
|
2017-10-19 13:21:06 +02:00
|
|
|
try:
|
|
|
|
places_count = int(request.GET['count'])
|
|
|
|
except ValueError:
|
|
|
|
return Response({
|
|
|
|
'err': 1,
|
2018-03-25 11:26:47 +02:00
|
|
|
'reason': 'invalid value for count (%s)' % request.GET['count'],
|
2018-04-04 16:07:00 +02:00
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
2017-06-11 12:42:10 +02:00
|
|
|
|
2017-09-26 16:17:05 +02:00
|
|
|
available_desk = None
|
2016-09-11 11:31:29 +02:00
|
|
|
if agenda.kind == 'meetings':
|
2018-04-04 10:47:55 +02:00
|
|
|
# event_pk is actually a timeslot id (meeting_type:start_datetime);
|
|
|
|
# split it back to get both parts.
|
2016-09-11 11:31:29 +02:00
|
|
|
meeting_type_id, start_datetime_str = event_pk.split(':')
|
|
|
|
start_datetime = make_aware(datetime.datetime.strptime(
|
|
|
|
start_datetime_str, '%Y-%m-%d-%H%M'))
|
2017-09-01 15:01:07 +02:00
|
|
|
|
2017-12-16 05:37:00 +01:00
|
|
|
slots = get_all_slots(agenda, MeetingType.objects.get(id=meeting_type_id))
|
2017-09-26 16:17:05 +02:00
|
|
|
# sort available matching slots by desk id
|
2018-04-04 10:47:55 +02:00
|
|
|
slots = [slot for slot in slots if not slot.full and slot.start_datetime == start_datetime]
|
2017-12-16 05:37:00 +01:00
|
|
|
slots.sort(key=lambda x: x.desk.id)
|
|
|
|
if slots:
|
2017-09-26 16:17:05 +02:00
|
|
|
# book first available desk
|
2017-12-16 05:37:00 +01:00
|
|
|
available_desk = slots[0].desk
|
2017-09-01 15:01:07 +02:00
|
|
|
|
|
|
|
if not available_desk:
|
|
|
|
return Response({'err': 1, 'reason': 'no more desk available'})
|
|
|
|
|
2018-04-04 10:47:55 +02:00
|
|
|
# booking requires a real Event object (not a lazy Timeslot);
|
|
|
|
# create it now, with data from the timeslot and the desk we
|
|
|
|
# found.
|
|
|
|
event = Event.objects.create(agenda=agenda,
|
|
|
|
meeting_type_id=meeting_type_id,
|
|
|
|
start_datetime=start_datetime,
|
|
|
|
full=False, places=1,
|
|
|
|
desk=available_desk)
|
|
|
|
|
2016-09-11 11:31:29 +02:00
|
|
|
event_pk = event.id
|
|
|
|
|
2016-02-13 17:52:32 +01:00
|
|
|
event = Event.objects.filter(id=event_pk)[0]
|
2018-04-04 16:07:00 +02:00
|
|
|
new_booking = Booking(event_id=event_pk, extra_data=extra_data,
|
|
|
|
label=label, user_name=user_name, backoffice_url=backoffice_url)
|
2016-07-07 16:21:55 +02:00
|
|
|
|
|
|
|
if event.waiting_list_places:
|
2017-06-11 12:42:10 +02:00
|
|
|
if (event.booked_places + places_count) > event.places or event.waiting_list:
|
2016-07-07 16:21:55 +02:00
|
|
|
# if this is full or there are people waiting, put new bookings
|
|
|
|
# in the waiting list.
|
|
|
|
new_booking.in_waiting_list = True
|
2017-07-10 18:38:44 +02:00
|
|
|
|
|
|
|
if (event.waiting_list + places_count) > event.waiting_list_places:
|
|
|
|
return Response({'err': 1, 'reason': 'sold out'})
|
|
|
|
|
2016-07-07 16:21:55 +02:00
|
|
|
else:
|
2017-06-11 12:42:10 +02:00
|
|
|
if (event.booked_places + places_count) > event.places:
|
2016-07-20 08:48:56 +02:00
|
|
|
return Response({'err': 1, 'reason': 'sold out'})
|
2016-07-07 16:21:55 +02:00
|
|
|
|
2016-06-20 14:52:56 +02:00
|
|
|
new_booking.save()
|
2017-06-11 12:42:10 +02:00
|
|
|
for i in range(places_count-1):
|
2018-04-04 16:07:00 +02:00
|
|
|
additional_booking = Booking(event_id=event_pk, extra_data=extra_data,
|
|
|
|
label=label, user_name=user_name,
|
|
|
|
backoffice_url=backoffice_url)
|
2017-06-11 12:42:10 +02:00
|
|
|
additional_booking.in_waiting_list = new_booking.in_waiting_list
|
|
|
|
additional_booking.primary_booking = new_booking
|
|
|
|
additional_booking.save()
|
|
|
|
|
2016-07-07 16:21:55 +02:00
|
|
|
response = {
|
|
|
|
'err': 0,
|
|
|
|
'in_waiting_list': new_booking.in_waiting_list,
|
|
|
|
'booking_id': new_booking.id,
|
2017-04-25 18:37:36 +02:00
|
|
|
'datetime': localtime(event.start_datetime),
|
2017-05-02 16:52:50 +02:00
|
|
|
'api': {
|
2017-06-23 11:30:53 +02:00
|
|
|
'cancel_url': request.build_absolute_uri(
|
|
|
|
reverse('api-cancel-booking', kwargs={'booking_pk': new_booking.id}))
|
2017-05-02 16:52:50 +02:00
|
|
|
}
|
2016-07-07 16:21:55 +02:00
|
|
|
}
|
2017-05-02 16:52:50 +02:00
|
|
|
if new_booking.in_waiting_list:
|
2017-06-23 11:30:53 +02:00
|
|
|
response['api']['accept_url'] = request.build_absolute_uri(
|
|
|
|
reverse('api-accept-booking', kwargs={'booking_pk': new_booking.id}))
|
2017-12-11 10:53:36 +01:00
|
|
|
if agenda.kind == 'meetings':
|
|
|
|
response['end_datetime'] = localtime(event.end_datetime)
|
2017-09-26 16:17:05 +02:00
|
|
|
if available_desk:
|
|
|
|
response['desk'] = {
|
|
|
|
'label': available_desk.label,
|
|
|
|
'slug': available_desk.slug}
|
2017-05-02 16:52:50 +02:00
|
|
|
|
2016-02-13 16:52:04 +01:00
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
fillslot = Fillslot.as_view()
|
2016-03-30 00:51:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
class BookingAPI(APIView):
|
2016-06-18 11:59:26 +02:00
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
|
|
|
|
2016-03-30 00:51:34 +02:00
|
|
|
def initial(self, request, *args, **kwargs):
|
|
|
|
super(BookingAPI, self).initial(request, *args, **kwargs)
|
|
|
|
self.booking = Booking.objects.get(id=kwargs.get('booking_pk'),
|
|
|
|
cancellation_datetime__isnull=True)
|
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
2016-06-23 19:31:12 +02:00
|
|
|
self.booking.cancel()
|
2016-03-30 00:51:34 +02:00
|
|
|
response = {'err': 0, 'booking_id': self.booking.id}
|
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
booking = BookingAPI.as_view()
|
2016-06-18 12:24:21 +02:00
|
|
|
|
|
|
|
|
2016-06-23 19:31:12 +02:00
|
|
|
class CancelBooking(APIView):
|
2016-07-20 13:22:58 +02:00
|
|
|
'''
|
|
|
|
Cancel a booking.
|
|
|
|
|
|
|
|
It will return an error (code 1) if the booking was already cancelled.
|
|
|
|
'''
|
2016-06-23 19:31:12 +02:00
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
|
|
|
|
|
|
|
def post(self, request, booking_pk=None, format=None):
|
2016-07-20 13:22:58 +02:00
|
|
|
booking = get_object_or_404(Booking, id=booking_pk)
|
|
|
|
if booking.cancellation_datetime:
|
|
|
|
response = {'err': 1, 'reason': 'already cancelled'}
|
|
|
|
return Response(response)
|
2016-06-23 19:31:12 +02:00
|
|
|
booking.cancel()
|
|
|
|
response = {'err': 0, 'booking_id': booking.id}
|
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
cancel_booking = CancelBooking.as_view()
|
|
|
|
|
|
|
|
|
2016-07-07 16:21:55 +02:00
|
|
|
class AcceptBooking(APIView):
|
2016-07-20 13:22:58 +02:00
|
|
|
'''
|
|
|
|
Accept a booking currently in the waiting list.
|
|
|
|
|
|
|
|
It will return error codes if the booking was cancelled before (code 1) and
|
|
|
|
if the booking was not in waiting list (code 2).
|
|
|
|
'''
|
2016-07-07 16:21:55 +02:00
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
|
|
|
|
|
|
|
def post(self, request, booking_pk=None, format=None):
|
2016-07-20 13:22:58 +02:00
|
|
|
booking = get_object_or_404(Booking, id=booking_pk)
|
|
|
|
if booking.cancellation_datetime:
|
|
|
|
response = {'err': 1, 'reason': 'booking is cancelled'}
|
|
|
|
return Response(response)
|
|
|
|
if not booking.in_waiting_list:
|
|
|
|
response = {'err': 2, 'reason': 'booking is not in waiting list'}
|
|
|
|
return Response(response)
|
2016-07-07 16:21:55 +02:00
|
|
|
booking.accept()
|
|
|
|
response = {'err': 0, 'booking_id': booking.id}
|
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
accept_booking = AcceptBooking.as_view()
|
|
|
|
|
|
|
|
|
2018-04-04 16:07:00 +02:00
|
|
|
class SlotStatus(APIView):
|
2016-06-18 12:24:21 +02:00
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
|
|
|
|
2016-10-28 17:52:21 +02:00
|
|
|
def get(self, request, agenda_identifier=None, event_pk=None, format=None):
|
2016-07-22 13:45:53 +02:00
|
|
|
event = get_object_or_404(Event, id=event_pk)
|
2016-06-18 12:24:21 +02:00
|
|
|
response = {
|
|
|
|
'err': 0,
|
|
|
|
'places': {
|
|
|
|
'total': event.places,
|
|
|
|
'reserved': event.booked_places,
|
|
|
|
'available': event.places - event.booked_places,
|
|
|
|
}
|
|
|
|
}
|
2016-07-22 13:41:23 +02:00
|
|
|
if event.waiting_list_places:
|
|
|
|
response['places']['waiting_list_total'] = event.waiting_list_places
|
|
|
|
response['places']['waiting_list_reserved'] = event.waiting_list
|
|
|
|
response['places']['waiting_list_available'] = (event.waiting_list_places - event.waiting_list)
|
2016-06-18 12:24:21 +02:00
|
|
|
return Response(response)
|
|
|
|
|
|
|
|
slot_status = SlotStatus.as_view()
|