From f8dc777f9956a51c20dbee0ad031138c4327fbea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 19 May 2020 17:53:34 +0200
Subject: [PATCH 2/5] api: use APIError in fillslots view (#43077)

---
 chrono/api/views.py | 166 ++++++++++++++++++++++----------------------
 1 file changed, 83 insertions(+), 83 deletions(-)

diff --git a/chrono/api/views.py b/chrono/api/views.py
index 364db1a..232d9d4 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -39,6 +39,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')
 
@@ -562,7 +581,10 @@ class Fillslots(APIView):
     serializer_class = SlotsSerializer
 
     def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
-        return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
+        try:
+            return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
+        except APIError as e:
+            return e.to_response()
 
     def fillslot(self, request, agenda_identifier=None, slots=[], format=None):
         multiple_booking = bool(not slots)
@@ -577,27 +599,21 @@ class Fillslots(APIView):
 
         serializer = self.serializer_class(data=request.data, partial=True)
         if not serializer.is_valid():
-            return Response(
-                {
-                    'err': 1,
-                    'err_class': 'invalid payload',
-                    'err_desc': _('invalid payload'),
-                    'errors': serializer.errors,
-                },
-                status=status.HTTP_400_BAD_REQUEST,
+            raise APIError(
+                _('invalid payload'),
+                err_class='invalid payload',
+                errors=serializer.errors,
+                http_status=status.HTTP_400_BAD_REQUEST,
             )
         payload = serializer.validated_data
 
         if 'slots' in payload:
             slots = payload['slots']
         if not slots:
-            return Response(
-                {
-                    'err': 1,
-                    'err_class': 'slots list cannot be empty',
-                    'err_desc': _('slots list cannot be empty'),
-                },
-                status=status.HTTP_400_BAD_REQUEST,
+            raise APIError(
+                _('slots list cannot be empty'),
+                err_class='slots list cannot be empty',
+                http_status=status.HTTP_400_BAD_REQUEST,
             )
 
         if 'count' in payload:
@@ -607,25 +623,19 @@ class Fillslots(APIView):
             try:
                 places_count = int(request.query_params['count'])
             except ValueError:
-                return Response(
-                    {
-                        'err': 1,
-                        'err_class': 'invalid value for count (%s)' % request.query_params['count'],
-                        'err_desc': _('invalid value for count (%s)') % request.query_params['count'],
-                    },
-                    status=status.HTTP_400_BAD_REQUEST,
+                raise APIError(
+                    _('invalid value for count (%s)') % request.query_params['count'],
+                    err_class='invalid value for count (%s)' % request.query_params['count'],
+                    http_status=status.HTTP_400_BAD_REQUEST,
                 )
         else:
             places_count = 1
 
         if places_count <= 0:
-            return Response(
-                {
-                    'err': 1,
-                    'err_class': 'count cannot be less than or equal to zero',
-                    'err_desc': _('count cannot be less than or equal to zero'),
-                },
-                status=status.HTTP_400_BAD_REQUEST,
+            raise APIError(
+                _('count cannot be less than or equal to zero'),
+                err_class='count cannot be less than or equal to zero',
+                http_status=status.HTTP_400_BAD_REQUEST,
             )
 
         to_cancel_booking = None
@@ -634,13 +644,10 @@ class Fillslots(APIView):
             try:
                 cancel_booking_id = int(payload.get('cancel_booking_id'))
             except (ValueError, TypeError):
-                return Response(
-                    {
-                        'err': 1,
-                        'err_class': 'cancel_booking_id is not an integer',
-                        'err_desc': _('cancel_booking_id is not an integer'),
-                    },
-                    status=status.HTTP_400_BAD_REQUEST,
+                raise APIError(
+                    _('cancel_booking_id is not an integer'),
+                    err_class='cancel_booking_id is not an integer',
+                    http_status=status.HTTP_400_BAD_REQUEST,
                 )
 
         if cancel_booking_id is not None:
@@ -660,7 +667,9 @@ class Fillslots(APIView):
                 cancel_error = gettext_noop('cancel booking: booking does no exist')
 
             if cancel_error:
-                return Response({'err': 1, 'err_class': cancel_error, 'err_desc': _(cancel_error),})
+                raise APIError(
+                    _(cancel_error), err_class=cancel_error,
+                )
 
         extra_data = {}
         for k, v in request.data.items():
@@ -678,24 +687,16 @@ class Fillslots(APIView):
                 try:
                     meeting_type_id_, datetime_str = slot.split(':')
                 except ValueError:
-                    return Response(
-                        {
-                            'err': 1,
-                            'err_class': 'invalid slot: %s' % slot,
-                            'err_desc': _('invalid slot: %s') % slot,
-                        },
-                        status=status.HTTP_400_BAD_REQUEST,
+                    raise APIError(
+                        _('invalid slot: %s') % slot,
+                        err_class='invalid slot: %s' % slot,
+                        http_status=status.HTTP_400_BAD_REQUEST,
                     )
                 if meeting_type_id_ != meeting_type_id:
-                    return Response(
-                        {
-                            'err': 1,
-                            'err_class': 'all slots must have the same meeting type id (%s)'
-                            % meeting_type_id,
-                            'err_desc': _('all slots must have the same meeting type id (%s)')
-                            % meeting_type_id,
-                        },
-                        status=status.HTTP_400_BAD_REQUEST,
+                    raise APIError(
+                        _('all slots must have the same meeting type id (%s)') % meeting_type_id,
+                        err_class='all slots must have the same meeting type id (%s)' % meeting_type_id,
+                        http_status=status.HTTP_400_BAD_REQUEST,
                     )
                 datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
 
@@ -706,13 +707,10 @@ class Fillslots(APIView):
                     key=lambda slot: slot.start_datetime,
                 )
             except (MeetingType.DoesNotExist, ValueError):
-                return Response(
-                    {
-                        'err': 1,
-                        'err_class': 'invalid meeting type id: %s' % meeting_type_id,
-                        'err_desc': _('invalid meeting type id: %s') % meeting_type_id,
-                    },
-                    status=status.HTTP_400_BAD_REQUEST,
+                raise APIError(
+                    _('invalid meeting type id: %s') % meeting_type_id,
+                    err_class='invalid meeting type id: %s' % meeting_type_id,
+                    http_status=status.HTTP_400_BAD_REQUEST,
                 )
 
             all_free_slots = [slot for slot in all_slots if not slot.full]
@@ -768,12 +766,8 @@ class Fillslots(APIView):
                         break
 
             if available_desk is None:
-                return Response(
-                    {
-                        'err': 1,
-                        'err_class': 'no more desk available',
-                        'err_desc': _('no more desk available'),
-                    }
+                raise APIError(
+                    _('no more desk available'), err_class='no more desk available',
                 )
 
             # all datetimes are free, book them in order
@@ -807,20 +801,19 @@ class Fillslots(APIView):
                 events = Event.objects.filter(slug__in=slots).order_by('start_datetime')
 
             if not events.count():
-                return Response(
-                    {
-                        'err': 1,
-                        'err_class': 'unknown event identifiers or slugs',
-                        'err_desc': _('unknown event identifiers or slugs'),
-                    },
-                    status=status.HTTP_400_BAD_REQUEST,
+                raise APIError(
+                    _('unknown event identifiers or slugs'),
+                    err_class='unknown event identifiers or slugs',
+                    http_status=status.HTTP_400_BAD_REQUEST,
                 )
 
         # search free places. Switch to waiting list if necessary.
         in_waiting_list = False
         for event in events:
             if payload.get('force_waiting_list') and not event.waiting_list_places:
-                return Response({'err': 1, 'err_class': 'no waiting list', 'err_desc': _('no waiting list')})
+                raise APIError(
+                    _('no waiting list'), err_class='no waiting list',
+                )
 
             if event.waiting_list_places:
                 if (
@@ -832,10 +825,14 @@ class Fillslots(APIView):
                     # in the waiting list.
                     in_waiting_list = True
                     if (event.waiting_list + places_count) > event.waiting_list_places:
-                        return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out')})
+                        raise APIError(
+                            _('sold out'), err_class='sold out',
+                        )
             else:
                 if (event.booked_places + places_count) > event.places:
-                    return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out')})
+                    raise APIError(
+                        _('sold out'), err_class='sold out',
+                    )
 
         with transaction.atomic():
             if to_cancel_booking:
@@ -921,12 +918,15 @@ class Fillslot(Fillslots):
     serializer_class = SlotSerializer
 
     def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
-        return self.fillslot(
-            request=request,
-            agenda_identifier=agenda_identifier,
-            slots=[event_identifier],  # fill a "list on one slot"
-            format=format,
-        )
+        try:
+            return self.fillslot(
+                request=request,
+                agenda_identifier=agenda_identifier,
+                slots=[event_identifier],  # fill a "list on one slot"
+                format=format,
+            )
+        except APIError as e:
+            return e.to_response()
 
 
 fillslot = Fillslot.as_view()
-- 
2.20.1

