api: make APIError less verbose (#58014)
This commit is contained in:
parent
629b512836
commit
1c8c5f447b
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue