api: make APIError less verbose (#58014)

This commit is contained in:
Valentin Deniaud 2021-11-04 11:05:01 +01:00
parent 629b512836
commit 1c8c5f447b
4 changed files with 113 additions and 335 deletions

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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
@ -29,24 +29,30 @@ class Response(DRFResponse):
class APIError(Exception):
err = 1
http_status = 200
def __init__(self, *args, **kwargs):
self.__dict__.update(kwargs)
super().__init__(*args)
def __init__(self, message, *args, err=1, err_class=None, errors=None):
self.err_desc = _(message) % args
self.err = err
self.err_class = err_class or message % args
self.errors = errors
super().__init__(self.err_desc)
def to_response(self):
data = {
'err': self.err,
'err_class': self.err_class,
'err_desc': force_text(self),
'err_desc': self.err_desc,
}
if hasattr(self, 'errors'):
if 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()

View File

@ -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
@ -588,27 +589,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):
@ -634,30 +623,28 @@ 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
@ -674,23 +661,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')
@ -698,12 +676,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')
@ -775,12 +748,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)]})
@ -821,12 +789,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')
@ -899,12 +862,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']
@ -994,10 +952,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,
@ -1269,20 +1225,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:
@ -1295,20 +1244,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
@ -1316,30 +1257,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():
@ -1360,25 +1297,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)
@ -1390,11 +1317,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,
@ -1464,10 +1387,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)
@ -1501,10 +1421,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 (
@ -1516,16 +1433,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:
@ -1643,12 +1554,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)
@ -1725,12 +1631,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']
@ -1748,7 +1649,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(
@ -1852,25 +1753,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})
@ -1891,25 +1779,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
@ -1922,37 +1801,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 {}
@ -1972,9 +1833,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}
@ -1997,19 +1856,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)
@ -2032,26 +1881,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 = {
@ -2079,26 +1913,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)
@ -2137,33 +1956,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')
@ -2171,19 +1972,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
@ -2207,20 +1998,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):
@ -2270,12 +2051,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:
@ -2286,12 +2062,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 = []
@ -2307,30 +2078,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(
@ -2417,12 +2179,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'],
@ -2522,12 +2279,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

View File

@ -18,6 +18,8 @@ from django.core.management.commands import makemessages
class Command(makemessages.Command):
xgettext_options = makemessages.Command.xgettext_options + ['--keyword=N_']
def handle(self, *args, **options):
if not options.get('add_location') and self.gettext_version >= (0, 19):
options['add_location'] = 'file'

View File

@ -1,5 +1,6 @@
import pytest
from chrono.agendas.models import Agenda
from chrono.api.utils import Response
@ -16,3 +17,20 @@ 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'
resp = app.get('/api/agendas/datetimes/?agendas=hop', status=400)
assert resp.json['err_desc'] == 'slugs invalides : hop'
assert resp.json['err_class'] == 'invalid slugs: hop'