From acd3b4c229991fae26d8224ca1d06aa495b7be7c Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 4 Nov 2021 11:05:01 +0100 Subject: [PATCH] api: make APIError less verbose (#58014) --- chrono/api/utils.py | 11 +- chrono/api/views.py | 406 ++++++++-------------------------------- tests/test_api_utils.py | 14 ++ 3 files changed, 101 insertions(+), 330 deletions(-) diff --git a/chrono/api/utils.py b/chrono/api/utils.py index 37fde6f0..d5d4c1d1 100644 --- a/chrono/api/utils.py +++ b/chrono/api/utils.py @@ -16,6 +16,7 @@ from django.utils.encoding import force_text +from django.utils.translation import gettext_lazy as _ from rest_framework.response import Response as DRFResponse from rest_framework.views import exception_handler as DRF_exception_handler @@ -31,22 +32,28 @@ class Response(DRFResponse): class APIError(Exception): err = 1 http_status = 200 + err_class = None def __init__(self, *args, **kwargs): self.__dict__.update(kwargs) super().__init__(*args) def to_response(self): + err_desc = force_text(self) data = { 'err': self.err, - 'err_class': self.err_class, - 'err_desc': force_text(self), + 'err_class': self.err_class or err_desc, + 'err_desc': _(err_desc), } if hasattr(self, 'errors'): data['errors'] = self.errors return Response(data, status=self.http_status) +class APIErrorBadRequest(APIError): + http_status = 400 + + def exception_handler(exc, context): if isinstance(exc, APIError): return exc.to_response() diff --git a/chrono/api/views.py b/chrono/api/views.py index 3b7eee00..c8e98110 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -32,10 +32,11 @@ from django.utils.dates import WEEKDAYS from django.utils.encoding import force_text from django.utils.formats import date_format from django.utils.timezone import localtime, make_aware, now -from django.utils.translation import gettext, gettext_noop +from django.utils.translation import gettext +from django.utils.translation import gettext_noop as N_ from django.utils.translation import ugettext_lazy as _ from django_filters import rest_framework as filters -from rest_framework import permissions, status +from rest_framework import permissions from rest_framework.exceptions import ValidationError from rest_framework.generics import ListAPIView from rest_framework.views import APIView @@ -51,7 +52,7 @@ from chrono.agendas.models import ( TimePeriodException, ) from chrono.api import serializers -from chrono.api.utils import APIError, Response +from chrono.api.utils import APIError, APIErrorBadRequest, Response from chrono.interval import IntervalSet from chrono.utils.publik_urls import translate_to_publik_url @@ -570,27 +571,15 @@ def get_event_recurrence(agenda, event_identifier): try: start_datetime = make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')) except ValueError: - raise APIError( - _('bad datetime format: %s') % datetime_str, - err_class='bad datetime format: %s' % datetime_str, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('bad datetime format: %s') % datetime_str) try: event = agenda.event_set.get(slug=event_slug) except Event.DoesNotExist: - raise APIError( - _('unknown recurring event slug: %s') % event_slug, - err_class='unknown recurring event slug: %s' % event_slug, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('unknown recurring event slug: %s') % event_slug) try: return event.get_or_create_event_recurrence(start_datetime) except ValueError: - raise APIError( - _('invalid datetime for event %s') % event_identifier, - err_class='invalid datetime for event %s' % event_identifier, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid datetime for event %s') % event_identifier) def get_events_from_slots(slots, request, agenda, payload): @@ -616,30 +605,26 @@ def get_events_from_slots(slots, request, agenda, payload): for event in events: if event.start_datetime >= now(): if not book_future or not event.in_bookable_period(bypass_delays=bypass_delays): - raise APIError(_('event %s is not bookable') % event.slug, err_class='event not bookable') + raise APIError(N_('event %s is not bookable') % event.slug, err_class='event not bookable') else: if not book_past: - raise APIError(_('event %s is not bookable') % event.slug, err_class='event not bookable') + raise APIError(N_('event %s is not bookable') % event.slug, err_class='event not bookable') if event.cancelled: - raise APIError(_('event %s is cancelled') % event.slug, err_class='event is cancelled') + raise APIError(N_('event %s is cancelled') % event.slug, err_class='event is cancelled') if exclude_user and user_external_id: if event.booking_set.filter(user_external_id=user_external_id).exists(): raise APIError( - _('event %s is already booked by user') % event.slug, + N_('event %s is already booked by user') % event.slug, err_class='event is already booked by user', ) if event.recurrence_days: raise APIError( - _('event %s is recurrent, direct booking is forbidden') % event.slug, + N_('event %s is recurrent, direct booking is forbidden') % event.slug, err_class='event is recurrent', ) if slots and not events.exists(): - raise APIError( - _('unknown event identifiers or slugs'), - err_class='unknown event identifiers or slugs', - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('unknown event identifiers or slugs')) return events @@ -656,23 +641,14 @@ def get_objects_from_slugs(slugs, qs): if len(objects) != len(slugs): unknown_slugs = sorted(slugs - {obj.slug for obj in objects}) unknown_slugs = ', '.join(unknown_slugs) - raise APIError( - _('invalid slugs: %s' % unknown_slugs), - err_class='invalid slugs: %s' % unknown_slugs, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid slugs: %s' % unknown_slugs)) return objects def get_start_and_end_datetime_from_request(request): serializer = serializers.DateRangeSerializer(data=request.query_params) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end') @@ -680,12 +656,7 @@ def get_start_and_end_datetime_from_request(request): def get_agendas_from_request(request): serializer = serializers.AgendaSlugsSerializer(data=request.query_params) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) return serializer.validated_data.get('agendas') @@ -757,12 +728,7 @@ class Agendas(APIView): def post(self, request, format=None): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) agenda = serializer.save() return Response({'err': 0, 'data': [get_agenda_detail(request, agenda)]}) @@ -803,12 +769,7 @@ class Datetimes(APIView): serializer = self.serializer_class(data=request.query_params) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data user_external_id = payload.get('user_external_id') or payload.get('exclude_user_external_id') @@ -876,12 +837,7 @@ class MultipleAgendasDatetimes(APIView): def get(self, request): serializer = self.serializer_class(data=request.query_params) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data agenda_slugs = payload['agendas'] @@ -969,10 +925,8 @@ class MeetingDatetimes(APIView): and excluded_user_external_id and booked_user_external_id != excluded_user_external_id ): - raise APIError( - _('user_external_id and exclude_user_external_id have different values'), - err_class='user_external_id and exclude_user_external_id have different values', - http_status=status.HTTP_400_BAD_REQUEST, + raise APIErrorBadRequest( + N_('user_external_id and exclude_user_external_id have different values') ) # Generate an unique slot for each possible meeting [start_datetime, @@ -1244,20 +1198,13 @@ class Fillslots(APIView): ) if known_body_params: params = ', '.join(sorted(list(known_body_params))) - raise APIError( - _('parameters "%s" must be included in request body, not query') % params, - err_class='parameters "%s" must be included in request body, not query' % params, - http_status=status.HTTP_400_BAD_REQUEST, + raise APIErrorBadRequest( + N_('parameters "%s" must be included in request body, not query') % params ) serializer = self.serializer_class(data=request.data, partial=True) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data if 'slots' in payload: @@ -1270,20 +1217,12 @@ class Fillslots(APIView): try: places_count = int(request.query_params['count']) except ValueError: - 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, - ) + raise APIErrorBadRequest(N_('invalid value for count (%s)') % request.query_params['count']) else: places_count = 1 if places_count <= 0: - 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, - ) + raise APIErrorBadRequest(N_('count cannot be less than or equal to zero')) to_cancel_booking = None cancel_booking_id = None @@ -1291,30 +1230,26 @@ class Fillslots(APIView): try: cancel_booking_id = int(payload.get('cancel_booking_id')) except (ValueError, TypeError): - 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, - ) + raise APIErrorBadRequest(N_('cancel_booking_id is not an integer')) if cancel_booking_id is not None: cancel_error = None try: to_cancel_booking = Booking.objects.get(pk=cancel_booking_id) if to_cancel_booking.cancellation_datetime: - cancel_error = gettext_noop('cancel booking: booking already cancelled') + cancel_error = N_('cancel booking: booking already cancelled') else: to_cancel_places_count = ( to_cancel_booking.secondary_booking_set.filter(event=to_cancel_booking.event).count() + 1 ) if places_count != to_cancel_places_count: - cancel_error = gettext_noop('cancel booking: count is different') + cancel_error = N_('cancel booking: count is different') except Booking.DoesNotExist: - cancel_error = gettext_noop('cancel booking: booking does no exist') + cancel_error = N_('cancel booking: booking does no exist') if cancel_error: - raise APIError(_(cancel_error), err_class=cancel_error) + raise APIError(N_(cancel_error)) extra_data = {} for k, v in request.data.items(): @@ -1335,25 +1270,15 @@ class Fillslots(APIView): try: meeting_type_id_, datetime_str = slot.split(':') except ValueError: - raise APIError( - _('invalid slot: %s') % slot, - err_class='invalid slot: %s' % slot, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid slot: %s') % slot) if meeting_type_id_ != meeting_type_id: - 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, + raise APIErrorBadRequest( + N_('all slots must have the same meeting type id (%s)') % meeting_type_id ) try: datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M'))) except ValueError: - raise APIError( - _('bad datetime format: %s') % datetime_str, - err_class='bad datetime format: %s' % datetime_str, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('bad datetime format: %s') % datetime_str) resources = get_resources_from_request(request, agenda) @@ -1365,11 +1290,7 @@ class Fillslots(APIView): # legacy access by id meeting_type = agenda.get_meetingtype(id_=meeting_type_id) except (MeetingType.DoesNotExist, ValueError): - 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, - ) + raise APIErrorBadRequest(N_('invalid meeting type id: %s') % meeting_type_id) all_slots = sorted( get_all_slots( agenda, @@ -1439,10 +1360,7 @@ class Fillslots(APIView): break if available_desk is None: - raise APIError( - _('no more desk available'), - err_class='no more desk available', - ) + raise APIError(N_('no more desk available')) # all datetimes are free, book them in order datetimes = list(datetimes) @@ -1476,10 +1394,7 @@ class Fillslots(APIView): for event in events: if event.start_datetime > now(): if payload.get('force_waiting_list') and not event.waiting_list_places: - raise APIError( - _('no waiting list'), - err_class='no waiting list', - ) + raise APIError(N_('no waiting list')) if event.waiting_list_places: if ( @@ -1491,16 +1406,10 @@ class Fillslots(APIView): # in the waiting list. in_waiting_list = True if (event.booked_waiting_list_places + places_count) > event.waiting_list_places: - raise APIError( - _('sold out'), - err_class='sold out', - ) + raise APIError(N_('sold out')) else: if (event.booked_places + places_count) > event.places: - raise APIError( - _('sold out'), - err_class='sold out', - ) + raise APIError(N_('sold out')) with transaction.atomic(): if to_cancel_booking: @@ -1618,12 +1527,7 @@ class RecurringFillslots(APIView): context = {'allowed_agenda_slugs': agenda_slugs, 'agendas': Agenda.prefetch_recurring_events(agendas)} serializer = self.serializer_class(data=request.data, partial=True, context=context) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data user_external_id = payload['user_external_id'] agendas = Agenda.prefetch_events_and_exceptions(agendas, user_external_id=user_external_id) @@ -1700,12 +1604,7 @@ class EventsFillslots(APIView): data=request.data, partial=True, context=self.serializer_extra_context ) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data user_external_id = payload['user_external_id'] @@ -1723,7 +1622,7 @@ class EventsFillslots(APIView): full_events = [str(event) for event in events.filter(full=True)] if full_events: raise APIError( - _('some events are full: %s') % ', '.join(full_events), err_class='some events are full' + N_('some events are full: %s') % ', '.join(full_events), err_class='some events are full' ) events = events.annotate( @@ -1827,25 +1726,12 @@ class BookingsAPI(ListAPIView): def get(self, request, *args, **kwargs): if not request.GET.get('user_external_id'): - response = { - 'err': 1, - 'err_class': 'missing param user_external_id', - 'err_desc': _('missing param user_external_id'), - } - return Response(response) + raise APIError(N_('missing param user_external_id')) try: response = super().get(request, *args, **kwargs) except ValidationError as e: - return Response( - { - 'err': 1, - 'err_class': 'invalid payload', - 'err_desc': _('invalid payload'), - 'errors': e.detail, - }, - status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=e.detail) return Response({'err': 0, 'data': response.data}) @@ -1866,25 +1752,16 @@ class BookingAPI(APIView): def check_booking(self, check_waiting_list=False): if self.booking.cancellation_datetime: - return Response( - {'err': 1, 'err_class': 'booking is cancelled', 'err_desc': _('booking is cancelled')} - ) + raise APIError(N_('booking is cancelled')) if self.booking.primary_booking is not None: - return Response({'err': 2, 'err_class': 'secondary booking', 'err_desc': _('secondary booking')}) + raise APIError(N_('secondary booking'), err=2) if check_waiting_list and self.booking.in_waiting_list: - response = { - 'err': 3, - 'err_class': 'booking is in waiting list', - 'err_desc': _('booking is in waiting list'), - } - return Response(response) + raise APIError(N_('booking is in waiting list'), err=3) def get(self, request, *args, **kwargs): - response = self.check_booking() - if response: - return response + self.check_booking() serializer = self.serializer_class(self.booking) response = serializer.data @@ -1897,37 +1774,19 @@ class BookingAPI(APIView): return Response(response) def patch(self, request, *args, **kwargs): - response = self.check_booking(check_waiting_list=True) - if response: - return response + self.check_booking(check_waiting_list=True) serializer = self.serializer_class(self.booking, data=request.data, partial=True) if not serializer.is_valid(): - return Response( - { - 'err': 4, - 'err_class': 'invalid payload', - 'err_desc': _('invalid payload'), - 'errors': serializer.errors, - }, - status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=4) if ( self.booking.event.checked and self.booking.event.agenda.disable_check_update and ('user_was_present' in request.data or 'user_absence_reason' in request.data) ): - return Response( - { - 'err': 5, - 'err_class': 'event is marked as checked', - 'err_desc': _('event is marked as checked'), - 'errors': serializer.errors, - }, - status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('event is marked as checked'), err=5) if 'extra_data' in serializer.validated_data: extra_data = self.booking.extra_data or {} @@ -1947,9 +1806,7 @@ class BookingAPI(APIView): return Response(response) def delete(self, request, *args, **kwargs): - response = self.check_booking() - if response: - return response + self.check_booking() self.booking.cancel() response = {'err': 0, 'booking_id': self.booking.pk} @@ -1972,19 +1829,9 @@ class CancelBooking(APIView): def post(self, request, booking_pk=None, format=None): booking = get_object_or_404(Booking, id=booking_pk) if booking.cancellation_datetime: - response = { - 'err': 1, - 'err_class': 'already cancelled', - 'err_desc': _('already cancelled'), - } - return Response(response) + raise APIError(N_('already cancelled')) if booking.primary_booking is not None: - response = { - 'err': 2, - 'err_class': 'secondary booking', - 'err_desc': _('secondary booking'), - } - return Response(response) + raise APIError(N_('secondary booking'), err=2) booking.cancel() response = {'err': 0, 'booking_id': booking.id} return Response(response) @@ -2007,26 +1854,11 @@ class AcceptBooking(APIView): def post(self, request, booking_pk=None, format=None): booking = get_object_or_404(Booking, id=booking_pk, event__agenda__kind='events') if booking.cancellation_datetime: - response = { - 'err': 1, - 'err_class': 'booking is cancelled', - 'err_desc': _('booking is cancelled'), - } - return Response(response) + raise APIError(N_('booking is cancelled')) if booking.primary_booking is not None: - response = { - 'err': 2, - 'err_class': 'secondary booking', - 'err_desc': _('secondary booking'), - } - return Response(response) + raise APIError(N_('secondary booking'), err=2) if not booking.in_waiting_list: - response = { - 'err': 3, - 'err_class': 'booking is not in waiting list', - 'err_desc': _('booking is not in waiting list'), - } - return Response(response) + raise APIError(N_('booking is not in waiting list'), err=3) booking.accept() event = booking.event response = { @@ -2054,26 +1886,11 @@ class SuspendBooking(APIView): def post(self, request, booking_pk=None, format=None): booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events') if booking.cancellation_datetime: - response = { - 'err': 1, - 'err_class': 'booking is cancelled', - 'err_desc': _('booking is cancelled'), - } - return Response(response) + raise APIError(N_('booking is cancelled')) if booking.primary_booking is not None: - response = { - 'err': 2, - 'err_class': 'secondary booking', - 'err_desc': _('secondary booking'), - } - return Response(response) + raise APIError(N_('secondary booking'), err=2) if booking.in_waiting_list: - response = { - 'err': 3, - 'err_class': 'booking is already in waiting list', - 'err_desc': _('booking is already in waiting list'), - } - return Response(response) + raise APIError(N_('booking is already in waiting list'), err=3) booking.suspend() response = {'err': 0, 'booking_id': booking.pk} return Response(response) @@ -2112,33 +1929,15 @@ class ResizeBooking(APIView): def post(self, request, booking_pk=None, format=None): serializer = self.serializer_class(data=request.data) 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 APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events') event = booking.event if booking.cancellation_datetime: - response = { - 'err': 1, - 'err_class': 'booking is cancelled', - 'err_desc': _('booking is cancelled'), - } - return Response(response) + raise APIError(N_('booking is cancelled')) if booking.primary_booking is not None: - response = { - 'err': 2, - 'err_class': 'secondary booking', - 'err_desc': _('secondary booking'), - } - return Response(response) + raise APIError(N_('secondary booking'), err=2) event_ids = {event.pk} in_waiting_list = {booking.in_waiting_list} secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime') @@ -2146,19 +1945,9 @@ class ResizeBooking(APIView): event_ids.add(secondary.event_id) in_waiting_list.add(secondary.in_waiting_list) if len(event_ids) > 1: - response = { - 'err': 4, - 'err_class': 'can not resize multi event booking', - 'err_desc': _('can not resize multi event booking'), - } - return Response(response) + raise APIError(N_('can not resize multi event booking'), err=4) if len(in_waiting_list) > 1: - response = { - 'err': 5, - 'err_class': 'can not resize booking: waiting list inconsistency', - 'err_desc': _('can not resize booking: waiting list inconsistency'), - } - return Response(response) + raise APIError(N_('can not resize booking: waiting list inconsistency'), err=5) # total places for the event (in waiting or main list, depending on the primary booking location) places = event.waiting_list_places if booking.in_waiting_list else event.places @@ -2182,20 +1971,10 @@ class ResizeBooking(APIView): # oversized request if booking.in_waiting_list: # booking in waiting list: can not be overbooked - response = { - 'err': 3, - 'err_class': 'sold out', - 'err_desc': _('sold out'), - } - return Response(response) + raise APIError(N_('sold out'), err=3) if event.booked_places <= event.places: # in main list and no overbooking for the moment: can not be overbooked - response = { - 'err': 3, - 'err_class': 'sold out', - 'err_desc': _('sold out'), - } - return Response(response) + raise APIError(N_('sold out'), err=3) return self.increase(booking, secondary_bookings, primary_booked_places, primary_wanted_places) def increase(self, booking, secondary_bookings, primary_booked_places, primary_wanted_places): @@ -2245,12 +2024,7 @@ class Events(APIView): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data event = Event.objects.create(agenda=agenda, **payload) if event.recurrence_days and event.recurrence_end_date: @@ -2261,12 +2035,7 @@ class Events(APIView): event = self.get_object(agenda_identifier, event_identifier) serializer = self.serializer_class(event, data=request.data, partial=True) if not serializer.is_valid(): - raise APIError( - _('invalid payload'), - err_class='invalid payload', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) payload = serializer.validated_data changed_data = [] @@ -2282,30 +2051,21 @@ class Events(APIView): 'recurrence_days', 'recurrence_week_interval', ): - raise APIError( - _('%s cannot be modified on an event recurrence') % field, - err_class='%s cannot be modified on an event recurrence' % field, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('%s cannot be modified on an event recurrence') % field) protected_fields = ['start_datetime', 'recurrence_days', 'recurrence_week_interval'] if event.recurrence_days and event.has_recurrences_booked(): for field in changed_data: if field in protected_fields: - raise APIError( - _('%s cannot be modified because some recurrences have bookings attached to them.') - % field, - err_class='%s cannot be modified because some recurrences have bookings attached to them.' - % field, - http_status=status.HTTP_400_BAD_REQUEST, + raise APIErrorBadRequest( + N_('%s cannot be modified because some recurrences have bookings attached to them.') + % field ) if 'recurrence_end_date' in changed_data and event.has_recurrences_booked( after=payload['recurrence_end_date'] ): - raise APIError( - _('recurrence_end_date cannot be modified because bookings exist after this date.'), - err_class='recurrence_end_date cannot be modified because bookings exist after this date.', - http_status=status.HTTP_400_BAD_REQUEST, + raise APIErrorBadRequest( + N_('recurrence_end_date cannot be modified because bookings exist after this date.') ) with event.update_recurrences( @@ -2392,12 +2152,7 @@ class EventBookings(APIView): def get(self, request, agenda_identifier=None, event_identifier=None, format=None): if not request.GET.get('user_external_id'): - response = { - 'err': 1, - 'err_class': 'missing param user_external_id', - 'err_desc': _('missing param user_external_id'), - } - return Response(response) + raise APIError(N_('missing param user_external_id')) event = self.get_object(agenda_identifier, event_identifier) booking_queryset = event.booking_set.filter( user_external_id=request.GET['user_external_id'], @@ -2497,12 +2252,7 @@ class BookingsStatistics(APIView): def get(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.query_params) if not serializer.is_valid(): - raise APIError( - _('invalid statistics filters'), - err_class='invalid statistics filters', - errors=serializer.errors, - http_status=status.HTTP_400_BAD_REQUEST, - ) + raise APIErrorBadRequest(N_('invalid statistics filters'), errors=serializer.errors) data = serializer.validated_data bookings = Booking.objects diff --git a/tests/test_api_utils.py b/tests/test_api_utils.py index 07ce4a90..5c6d7b2b 100644 --- a/tests/test_api_utils.py +++ b/tests/test_api_utils.py @@ -1,5 +1,6 @@ import pytest +from chrono.agendas.models import Agenda from chrono.api.utils import Response @@ -16,3 +17,16 @@ from chrono.api.utils import Response def test_response_data(data, expected): resp = Response(data=data) assert resp.data == expected + + +def test_err_desc_translation(db, app, settings): + settings.LANGUAGE_CODE = 'fr-fr' + agenda = Agenda.objects.create(label='Foo bar', kind='events') + + resp = app.get( + '/api/agenda/%s/datetimes/' % agenda.slug, + params={'user_external_id': '42', 'exclude_user_external_id': '35'}, + status=400, + ) + assert resp.json['err_desc'] == 'contenu de requĂȘte invalide' + assert resp.json['err_class'] == 'invalid payload' -- 2.30.2