39 |
39 |
from ..interval import IntervalSet
|
40 |
40 |
|
41 |
41 |
|
|
42 |
class APIError(Exception):
|
|
43 |
err = 1
|
|
44 |
http_status = 200
|
|
45 |
|
|
46 |
def __init__(self, *args, **kwargs):
|
|
47 |
self.__dict__.update(kwargs)
|
|
48 |
super(APIError, self).__init__(*args)
|
|
49 |
|
|
50 |
def to_response(self):
|
|
51 |
data = {
|
|
52 |
'err': self.err,
|
|
53 |
'err_class': self.err_class,
|
|
54 |
'err_desc': force_text(self),
|
|
55 |
}
|
|
56 |
if hasattr(self, 'errors'):
|
|
57 |
data['errors'] = self.errors
|
|
58 |
return Response(data, status=self.http_status)
|
|
59 |
|
|
60 |
|
42 |
61 |
def format_response_datetime(dt):
|
43 |
62 |
return localtime(dt).strftime('%Y-%m-%d %H:%M:%S')
|
44 |
63 |
|
... | ... | |
562 |
581 |
serializer_class = SlotsSerializer
|
563 |
582 |
|
564 |
583 |
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
|
565 |
|
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
|
|
584 |
try:
|
|
585 |
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
|
|
586 |
except APIError as e:
|
|
587 |
return e.to_response()
|
566 |
588 |
|
567 |
589 |
def fillslot(self, request, agenda_identifier=None, slots=[], format=None):
|
568 |
590 |
multiple_booking = bool(not slots)
|
... | ... | |
577 |
599 |
|
578 |
600 |
serializer = self.serializer_class(data=request.data, partial=True)
|
579 |
601 |
if not serializer.is_valid():
|
580 |
|
return Response(
|
581 |
|
{
|
582 |
|
'err': 1,
|
583 |
|
'err_class': 'invalid payload',
|
584 |
|
'err_desc': _('invalid payload'),
|
585 |
|
'errors': serializer.errors,
|
586 |
|
},
|
587 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
602 |
raise APIError(
|
|
603 |
_('invalid payload'),
|
|
604 |
err_class='invalid payload',
|
|
605 |
errors=serializer.errors,
|
|
606 |
http_status=status.HTTP_400_BAD_REQUEST,
|
588 |
607 |
)
|
589 |
608 |
payload = serializer.validated_data
|
590 |
609 |
|
591 |
610 |
if 'slots' in payload:
|
592 |
611 |
slots = payload['slots']
|
593 |
612 |
if not slots:
|
594 |
|
return Response(
|
595 |
|
{
|
596 |
|
'err': 1,
|
597 |
|
'err_class': 'slots list cannot be empty',
|
598 |
|
'err_desc': _('slots list cannot be empty'),
|
599 |
|
},
|
600 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
613 |
raise APIError(
|
|
614 |
_('slots list cannot be empty'),
|
|
615 |
err_class='slots list cannot be empty',
|
|
616 |
http_status=status.HTTP_400_BAD_REQUEST,
|
601 |
617 |
)
|
602 |
618 |
|
603 |
619 |
if 'count' in payload:
|
... | ... | |
607 |
623 |
try:
|
608 |
624 |
places_count = int(request.query_params['count'])
|
609 |
625 |
except ValueError:
|
610 |
|
return Response(
|
611 |
|
{
|
612 |
|
'err': 1,
|
613 |
|
'err_class': 'invalid value for count (%s)' % request.query_params['count'],
|
614 |
|
'err_desc': _('invalid value for count (%s)') % request.query_params['count'],
|
615 |
|
},
|
616 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
626 |
raise APIError(
|
|
627 |
_('invalid value for count (%s)') % request.query_params['count'],
|
|
628 |
err_class='invalid value for count (%s)' % request.query_params['count'],
|
|
629 |
http_status=status.HTTP_400_BAD_REQUEST,
|
617 |
630 |
)
|
618 |
631 |
else:
|
619 |
632 |
places_count = 1
|
620 |
633 |
|
621 |
634 |
if places_count <= 0:
|
622 |
|
return Response(
|
623 |
|
{
|
624 |
|
'err': 1,
|
625 |
|
'err_class': 'count cannot be less than or equal to zero',
|
626 |
|
'err_desc': _('count cannot be less than or equal to zero'),
|
627 |
|
},
|
628 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
635 |
raise APIError(
|
|
636 |
_('count cannot be less than or equal to zero'),
|
|
637 |
err_class='count cannot be less than or equal to zero',
|
|
638 |
http_status=status.HTTP_400_BAD_REQUEST,
|
629 |
639 |
)
|
630 |
640 |
|
631 |
641 |
to_cancel_booking = None
|
... | ... | |
634 |
644 |
try:
|
635 |
645 |
cancel_booking_id = int(payload.get('cancel_booking_id'))
|
636 |
646 |
except (ValueError, TypeError):
|
637 |
|
return Response(
|
638 |
|
{
|
639 |
|
'err': 1,
|
640 |
|
'err_class': 'cancel_booking_id is not an integer',
|
641 |
|
'err_desc': _('cancel_booking_id is not an integer'),
|
642 |
|
},
|
643 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
647 |
raise APIError(
|
|
648 |
_('cancel_booking_id is not an integer'),
|
|
649 |
err_class='cancel_booking_id is not an integer',
|
|
650 |
http_status=status.HTTP_400_BAD_REQUEST,
|
644 |
651 |
)
|
645 |
652 |
|
646 |
653 |
if cancel_booking_id is not None:
|
... | ... | |
660 |
667 |
cancel_error = gettext_noop('cancel booking: booking does no exist')
|
661 |
668 |
|
662 |
669 |
if cancel_error:
|
663 |
|
return Response({'err': 1, 'err_class': cancel_error, 'err_desc': _(cancel_error),})
|
|
670 |
raise APIError(
|
|
671 |
_(cancel_error), err_class=cancel_error,
|
|
672 |
)
|
664 |
673 |
|
665 |
674 |
extra_data = {}
|
666 |
675 |
for k, v in request.data.items():
|
... | ... | |
678 |
687 |
try:
|
679 |
688 |
meeting_type_id_, datetime_str = slot.split(':')
|
680 |
689 |
except ValueError:
|
681 |
|
return Response(
|
682 |
|
{
|
683 |
|
'err': 1,
|
684 |
|
'err_class': 'invalid slot: %s' % slot,
|
685 |
|
'err_desc': _('invalid slot: %s') % slot,
|
686 |
|
},
|
687 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
690 |
raise APIError(
|
|
691 |
_('invalid slot: %s') % slot,
|
|
692 |
err_class='invalid slot: %s' % slot,
|
|
693 |
http_status=status.HTTP_400_BAD_REQUEST,
|
688 |
694 |
)
|
689 |
695 |
if meeting_type_id_ != meeting_type_id:
|
690 |
|
return Response(
|
691 |
|
{
|
692 |
|
'err': 1,
|
693 |
|
'err_class': 'all slots must have the same meeting type id (%s)'
|
694 |
|
% meeting_type_id,
|
695 |
|
'err_desc': _('all slots must have the same meeting type id (%s)')
|
696 |
|
% meeting_type_id,
|
697 |
|
},
|
698 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
696 |
raise APIError(
|
|
697 |
_('all slots must have the same meeting type id (%s)') % meeting_type_id,
|
|
698 |
err_class='all slots must have the same meeting type id (%s)' % meeting_type_id,
|
|
699 |
http_status=status.HTTP_400_BAD_REQUEST,
|
699 |
700 |
)
|
700 |
701 |
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
|
701 |
702 |
|
... | ... | |
706 |
707 |
key=lambda slot: slot.start_datetime,
|
707 |
708 |
)
|
708 |
709 |
except (MeetingType.DoesNotExist, ValueError):
|
709 |
|
return Response(
|
710 |
|
{
|
711 |
|
'err': 1,
|
712 |
|
'err_class': 'invalid meeting type id: %s' % meeting_type_id,
|
713 |
|
'err_desc': _('invalid meeting type id: %s') % meeting_type_id,
|
714 |
|
},
|
715 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
710 |
raise APIError(
|
|
711 |
_('invalid meeting type id: %s') % meeting_type_id,
|
|
712 |
err_class='invalid meeting type id: %s' % meeting_type_id,
|
|
713 |
http_status=status.HTTP_400_BAD_REQUEST,
|
716 |
714 |
)
|
717 |
715 |
|
718 |
716 |
all_free_slots = [slot for slot in all_slots if not slot.full]
|
... | ... | |
768 |
766 |
break
|
769 |
767 |
|
770 |
768 |
if available_desk is None:
|
771 |
|
return Response(
|
772 |
|
{
|
773 |
|
'err': 1,
|
774 |
|
'err_class': 'no more desk available',
|
775 |
|
'err_desc': _('no more desk available'),
|
776 |
|
}
|
|
769 |
raise APIError(
|
|
770 |
_('no more desk available'), err_class='no more desk available',
|
777 |
771 |
)
|
778 |
772 |
|
779 |
773 |
# all datetimes are free, book them in order
|
... | ... | |
807 |
801 |
events = Event.objects.filter(slug__in=slots).order_by('start_datetime')
|
808 |
802 |
|
809 |
803 |
if not events.count():
|
810 |
|
return Response(
|
811 |
|
{
|
812 |
|
'err': 1,
|
813 |
|
'err_class': 'unknown event identifiers or slugs',
|
814 |
|
'err_desc': _('unknown event identifiers or slugs'),
|
815 |
|
},
|
816 |
|
status=status.HTTP_400_BAD_REQUEST,
|
|
804 |
raise APIError(
|
|
805 |
_('unknown event identifiers or slugs'),
|
|
806 |
err_class='unknown event identifiers or slugs',
|
|
807 |
http_status=status.HTTP_400_BAD_REQUEST,
|
817 |
808 |
)
|
818 |
809 |
|
819 |
810 |
# search free places. Switch to waiting list if necessary.
|
820 |
811 |
in_waiting_list = False
|
821 |
812 |
for event in events:
|
822 |
813 |
if payload.get('force_waiting_list') and not event.waiting_list_places:
|
823 |
|
return Response({'err': 1, 'err_class': 'no waiting list', 'err_desc': _('no waiting list')})
|
|
814 |
raise APIError(
|
|
815 |
_('no waiting list'), err_class='no waiting list',
|
|
816 |
)
|
824 |
817 |
|
825 |
818 |
if event.waiting_list_places:
|
826 |
819 |
if (
|
... | ... | |
832 |
825 |
# in the waiting list.
|
833 |
826 |
in_waiting_list = True
|
834 |
827 |
if (event.waiting_list + places_count) > event.waiting_list_places:
|
835 |
|
return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out')})
|
|
828 |
raise APIError(
|
|
829 |
_('sold out'), err_class='sold out',
|
|
830 |
)
|
836 |
831 |
else:
|
837 |
832 |
if (event.booked_places + places_count) > event.places:
|
838 |
|
return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out')})
|
|
833 |
raise APIError(
|
|
834 |
_('sold out'), err_class='sold out',
|
|
835 |
)
|
839 |
836 |
|
840 |
837 |
with transaction.atomic():
|
841 |
838 |
if to_cancel_booking:
|
... | ... | |
921 |
918 |
serializer_class = SlotSerializer
|
922 |
919 |
|
923 |
920 |
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
|
924 |
|
return self.fillslot(
|
925 |
|
request=request,
|
926 |
|
agenda_identifier=agenda_identifier,
|
927 |
|
slots=[event_identifier], # fill a "list on one slot"
|
928 |
|
format=format,
|
929 |
|
)
|
|
921 |
try:
|
|
922 |
return self.fillslot(
|
|
923 |
request=request,
|
|
924 |
agenda_identifier=agenda_identifier,
|
|
925 |
slots=[event_identifier], # fill a "list on one slot"
|
|
926 |
format=format,
|
|
927 |
)
|
|
928 |
except APIError as e:
|
|
929 |
return e.to_response()
|
930 |
930 |
|
931 |
931 |
|
932 |
932 |
fillslot = Fillslot.as_view()
|
933 |
|
-
|