Projet

Général

Profil

0001-api-make-APIError-less-verbose-58014.patch

Valentin Deniaud, 04 novembre 2021 12:03

Télécharger (36,8 ko)

Voir les différences:

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(-)
chrono/api/utils.py
16 16

  
17 17

  
18 18
from django.utils.encoding import force_text
19
from django.utils.translation import gettext_lazy as _
19 20
from rest_framework.response import Response as DRFResponse
20 21
from rest_framework.views import exception_handler as DRF_exception_handler
21 22

  
......
31 32
class APIError(Exception):
32 33
    err = 1
33 34
    http_status = 200
35
    err_class = None
34 36

  
35 37
    def __init__(self, *args, **kwargs):
36 38
        self.__dict__.update(kwargs)
37 39
        super().__init__(*args)
38 40

  
39 41
    def to_response(self):
42
        err_desc = force_text(self)
40 43
        data = {
41 44
            'err': self.err,
42
            'err_class': self.err_class,
43
            'err_desc': force_text(self),
45
            'err_class': self.err_class or err_desc,
46
            'err_desc': _(err_desc),
44 47
        }
45 48
        if hasattr(self, 'errors'):
46 49
            data['errors'] = self.errors
47 50
        return Response(data, status=self.http_status)
48 51

  
49 52

  
53
class APIErrorBadRequest(APIError):
54
    http_status = 400
55

  
56

  
50 57
def exception_handler(exc, context):
51 58
    if isinstance(exc, APIError):
52 59
        return exc.to_response()
chrono/api/views.py
32 32
from django.utils.encoding import force_text
33 33
from django.utils.formats import date_format
34 34
from django.utils.timezone import localtime, make_aware, now
35
from django.utils.translation import gettext, gettext_noop
35
from django.utils.translation import gettext
36
from django.utils.translation import gettext_noop as N_
36 37
from django.utils.translation import ugettext_lazy as _
37 38
from django_filters import rest_framework as filters
38
from rest_framework import permissions, status
39
from rest_framework import permissions
39 40
from rest_framework.exceptions import ValidationError
40 41
from rest_framework.generics import ListAPIView
41 42
from rest_framework.views import APIView
......
51 52
    TimePeriodException,
52 53
)
53 54
from chrono.api import serializers
54
from chrono.api.utils import APIError, Response
55
from chrono.api.utils import APIError, APIErrorBadRequest, Response
55 56
from chrono.interval import IntervalSet
56 57
from chrono.utils.publik_urls import translate_to_publik_url
57 58

  
......
570 571
    try:
571 572
        start_datetime = make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M'))
572 573
    except ValueError:
573
        raise APIError(
574
            _('bad datetime format: %s') % datetime_str,
575
            err_class='bad datetime format: %s' % datetime_str,
576
            http_status=status.HTTP_400_BAD_REQUEST,
577
        )
574
        raise APIErrorBadRequest(N_('bad datetime format: %s') % datetime_str)
578 575
    try:
579 576
        event = agenda.event_set.get(slug=event_slug)
580 577
    except Event.DoesNotExist:
581
        raise APIError(
582
            _('unknown recurring event slug: %s') % event_slug,
583
            err_class='unknown recurring event slug: %s' % event_slug,
584
            http_status=status.HTTP_400_BAD_REQUEST,
585
        )
578
        raise APIErrorBadRequest(N_('unknown recurring event slug: %s') % event_slug)
586 579
    try:
587 580
        return event.get_or_create_event_recurrence(start_datetime)
588 581
    except ValueError:
589
        raise APIError(
590
            _('invalid datetime for event %s') % event_identifier,
591
            err_class='invalid datetime for event %s' % event_identifier,
592
            http_status=status.HTTP_400_BAD_REQUEST,
593
        )
582
        raise APIErrorBadRequest(N_('invalid datetime for event %s') % event_identifier)
594 583

  
595 584

  
596 585
def get_events_from_slots(slots, request, agenda, payload):
......
616 605
    for event in events:
617 606
        if event.start_datetime >= now():
618 607
            if not book_future or not event.in_bookable_period(bypass_delays=bypass_delays):
619
                raise APIError(_('event %s is not bookable') % event.slug, err_class='event not bookable')
608
                raise APIError(N_('event %s is not bookable') % event.slug, err_class='event not bookable')
620 609
        else:
621 610
            if not book_past:
622
                raise APIError(_('event %s is not bookable') % event.slug, err_class='event not bookable')
611
                raise APIError(N_('event %s is not bookable') % event.slug, err_class='event not bookable')
623 612
        if event.cancelled:
624
            raise APIError(_('event %s is cancelled') % event.slug, err_class='event is cancelled')
613
            raise APIError(N_('event %s is cancelled') % event.slug, err_class='event is cancelled')
625 614
        if exclude_user and user_external_id:
626 615
            if event.booking_set.filter(user_external_id=user_external_id).exists():
627 616
                raise APIError(
628
                    _('event %s is already booked by user') % event.slug,
617
                    N_('event %s is already booked by user') % event.slug,
629 618
                    err_class='event is already booked by user',
630 619
                )
631 620
        if event.recurrence_days:
632 621
            raise APIError(
633
                _('event %s is recurrent, direct booking is forbidden') % event.slug,
622
                N_('event %s is recurrent, direct booking is forbidden') % event.slug,
634 623
                err_class='event is recurrent',
635 624
            )
636 625

  
637 626
    if slots and not events.exists():
638
        raise APIError(
639
            _('unknown event identifiers or slugs'),
640
            err_class='unknown event identifiers or slugs',
641
            http_status=status.HTTP_400_BAD_REQUEST,
642
        )
627
        raise APIErrorBadRequest(N_('unknown event identifiers or slugs'))
643 628
    return events
644 629

  
645 630

  
......
656 641
    if len(objects) != len(slugs):
657 642
        unknown_slugs = sorted(slugs - {obj.slug for obj in objects})
658 643
        unknown_slugs = ', '.join(unknown_slugs)
659
        raise APIError(
660
            _('invalid slugs: %s' % unknown_slugs),
661
            err_class='invalid slugs: %s' % unknown_slugs,
662
            http_status=status.HTTP_400_BAD_REQUEST,
663
        )
644
        raise APIErrorBadRequest(N_('invalid slugs: %s' % unknown_slugs))
664 645
    return objects
665 646

  
666 647

  
667 648
def get_start_and_end_datetime_from_request(request):
668 649
    serializer = serializers.DateRangeSerializer(data=request.query_params)
669 650
    if not serializer.is_valid():
670
        raise APIError(
671
            _('invalid payload'),
672
            err_class='invalid payload',
673
            errors=serializer.errors,
674
            http_status=status.HTTP_400_BAD_REQUEST,
675
        )
651
        raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
676 652

  
677 653
    return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end')
678 654

  
......
680 656
def get_agendas_from_request(request):
681 657
    serializer = serializers.AgendaSlugsSerializer(data=request.query_params)
682 658
    if not serializer.is_valid():
683
        raise APIError(
684
            _('invalid payload'),
685
            err_class='invalid payload',
686
            errors=serializer.errors,
687
            http_status=status.HTTP_400_BAD_REQUEST,
688
        )
659
        raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
689 660

  
690 661
    return serializer.validated_data.get('agendas')
691 662

  
......
757 728
    def post(self, request, format=None):
758 729
        serializer = self.serializer_class(data=request.data)
759 730
        if not serializer.is_valid():
760
            raise APIError(
761
                _('invalid payload'),
762
                err_class='invalid payload',
763
                errors=serializer.errors,
764
                http_status=status.HTTP_400_BAD_REQUEST,
765
            )
731
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
766 732
        agenda = serializer.save()
767 733
        return Response({'err': 0, 'data': [get_agenda_detail(request, agenda)]})
768 734

  
......
803 769

  
804 770
        serializer = self.serializer_class(data=request.query_params)
805 771
        if not serializer.is_valid():
806
            raise APIError(
807
                _('invalid payload'),
808
                err_class='invalid payload',
809
                errors=serializer.errors,
810
                http_status=status.HTTP_400_BAD_REQUEST,
811
            )
772
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
812 773
        payload = serializer.validated_data
813 774

  
814 775
        user_external_id = payload.get('user_external_id') or payload.get('exclude_user_external_id')
......
876 837
    def get(self, request):
877 838
        serializer = self.serializer_class(data=request.query_params)
878 839
        if not serializer.is_valid():
879
            raise APIError(
880
                _('invalid payload'),
881
                err_class='invalid payload',
882
                errors=serializer.errors,
883
                http_status=status.HTTP_400_BAD_REQUEST,
884
            )
840
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
885 841
        payload = serializer.validated_data
886 842

  
887 843
        agenda_slugs = payload['agendas']
......
969 925
            and excluded_user_external_id
970 926
            and booked_user_external_id != excluded_user_external_id
971 927
        ):
972
            raise APIError(
973
                _('user_external_id and exclude_user_external_id have different values'),
974
                err_class='user_external_id and exclude_user_external_id have different values',
975
                http_status=status.HTTP_400_BAD_REQUEST,
928
            raise APIErrorBadRequest(
929
                N_('user_external_id and exclude_user_external_id have different values')
976 930
            )
977 931

  
978 932
        # Generate an unique slot for each possible meeting [start_datetime,
......
1244 1198
        )
1245 1199
        if known_body_params:
1246 1200
            params = ', '.join(sorted(list(known_body_params)))
1247
            raise APIError(
1248
                _('parameters "%s" must be included in request body, not query') % params,
1249
                err_class='parameters "%s" must be included in request body, not query' % params,
1250
                http_status=status.HTTP_400_BAD_REQUEST,
1201
            raise APIErrorBadRequest(
1202
                N_('parameters "%s" must be included in request body, not query') % params
1251 1203
            )
1252 1204

  
1253 1205
        serializer = self.serializer_class(data=request.data, partial=True)
1254 1206
        if not serializer.is_valid():
1255
            raise APIError(
1256
                _('invalid payload'),
1257
                err_class='invalid payload',
1258
                errors=serializer.errors,
1259
                http_status=status.HTTP_400_BAD_REQUEST,
1260
            )
1207
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1261 1208
        payload = serializer.validated_data
1262 1209

  
1263 1210
        if 'slots' in payload:
......
1270 1217
            try:
1271 1218
                places_count = int(request.query_params['count'])
1272 1219
            except ValueError:
1273
                raise APIError(
1274
                    _('invalid value for count (%s)') % request.query_params['count'],
1275
                    err_class='invalid value for count (%s)' % request.query_params['count'],
1276
                    http_status=status.HTTP_400_BAD_REQUEST,
1277
                )
1220
                raise APIErrorBadRequest(N_('invalid value for count (%s)') % request.query_params['count'])
1278 1221
        else:
1279 1222
            places_count = 1
1280 1223

  
1281 1224
        if places_count <= 0:
1282
            raise APIError(
1283
                _('count cannot be less than or equal to zero'),
1284
                err_class='count cannot be less than or equal to zero',
1285
                http_status=status.HTTP_400_BAD_REQUEST,
1286
            )
1225
            raise APIErrorBadRequest(N_('count cannot be less than or equal to zero'))
1287 1226

  
1288 1227
        to_cancel_booking = None
1289 1228
        cancel_booking_id = None
......
1291 1230
            try:
1292 1231
                cancel_booking_id = int(payload.get('cancel_booking_id'))
1293 1232
            except (ValueError, TypeError):
1294
                raise APIError(
1295
                    _('cancel_booking_id is not an integer'),
1296
                    err_class='cancel_booking_id is not an integer',
1297
                    http_status=status.HTTP_400_BAD_REQUEST,
1298
                )
1233
                raise APIErrorBadRequest(N_('cancel_booking_id is not an integer'))
1299 1234

  
1300 1235
        if cancel_booking_id is not None:
1301 1236
            cancel_error = None
1302 1237
            try:
1303 1238
                to_cancel_booking = Booking.objects.get(pk=cancel_booking_id)
1304 1239
                if to_cancel_booking.cancellation_datetime:
1305
                    cancel_error = gettext_noop('cancel booking: booking already cancelled')
1240
                    cancel_error = N_('cancel booking: booking already cancelled')
1306 1241
                else:
1307 1242
                    to_cancel_places_count = (
1308 1243
                        to_cancel_booking.secondary_booking_set.filter(event=to_cancel_booking.event).count()
1309 1244
                        + 1
1310 1245
                    )
1311 1246
                    if places_count != to_cancel_places_count:
1312
                        cancel_error = gettext_noop('cancel booking: count is different')
1247
                        cancel_error = N_('cancel booking: count is different')
1313 1248
            except Booking.DoesNotExist:
1314
                cancel_error = gettext_noop('cancel booking: booking does no exist')
1249
                cancel_error = N_('cancel booking: booking does no exist')
1315 1250

  
1316 1251
            if cancel_error:
1317
                raise APIError(_(cancel_error), err_class=cancel_error)
1252
                raise APIError(N_(cancel_error))
1318 1253

  
1319 1254
        extra_data = {}
1320 1255
        for k, v in request.data.items():
......
1335 1270
                try:
1336 1271
                    meeting_type_id_, datetime_str = slot.split(':')
1337 1272
                except ValueError:
1338
                    raise APIError(
1339
                        _('invalid slot: %s') % slot,
1340
                        err_class='invalid slot: %s' % slot,
1341
                        http_status=status.HTTP_400_BAD_REQUEST,
1342
                    )
1273
                    raise APIErrorBadRequest(N_('invalid slot: %s') % slot)
1343 1274
                if meeting_type_id_ != meeting_type_id:
1344
                    raise APIError(
1345
                        _('all slots must have the same meeting type id (%s)') % meeting_type_id,
1346
                        err_class='all slots must have the same meeting type id (%s)' % meeting_type_id,
1347
                        http_status=status.HTTP_400_BAD_REQUEST,
1275
                    raise APIErrorBadRequest(
1276
                        N_('all slots must have the same meeting type id (%s)') % meeting_type_id
1348 1277
                    )
1349 1278
                try:
1350 1279
                    datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
1351 1280
                except ValueError:
1352
                    raise APIError(
1353
                        _('bad datetime format: %s') % datetime_str,
1354
                        err_class='bad datetime format: %s' % datetime_str,
1355
                        http_status=status.HTTP_400_BAD_REQUEST,
1356
                    )
1281
                    raise APIErrorBadRequest(N_('bad datetime format: %s') % datetime_str)
1357 1282

  
1358 1283
            resources = get_resources_from_request(request, agenda)
1359 1284

  
......
1365 1290
                    # legacy access by id
1366 1291
                    meeting_type = agenda.get_meetingtype(id_=meeting_type_id)
1367 1292
            except (MeetingType.DoesNotExist, ValueError):
1368
                raise APIError(
1369
                    _('invalid meeting type id: %s') % meeting_type_id,
1370
                    err_class='invalid meeting type id: %s' % meeting_type_id,
1371
                    http_status=status.HTTP_400_BAD_REQUEST,
1372
                )
1293
                raise APIErrorBadRequest(N_('invalid meeting type id: %s') % meeting_type_id)
1373 1294
            all_slots = sorted(
1374 1295
                get_all_slots(
1375 1296
                    agenda,
......
1439 1360
                        break
1440 1361

  
1441 1362
            if available_desk is None:
1442
                raise APIError(
1443
                    _('no more desk available'),
1444
                    err_class='no more desk available',
1445
                )
1363
                raise APIError(N_('no more desk available'))
1446 1364

  
1447 1365
            # all datetimes are free, book them in order
1448 1366
            datetimes = list(datetimes)
......
1476 1394
        for event in events:
1477 1395
            if event.start_datetime > now():
1478 1396
                if payload.get('force_waiting_list') and not event.waiting_list_places:
1479
                    raise APIError(
1480
                        _('no waiting list'),
1481
                        err_class='no waiting list',
1482
                    )
1397
                    raise APIError(N_('no waiting list'))
1483 1398

  
1484 1399
                if event.waiting_list_places:
1485 1400
                    if (
......
1491 1406
                        # in the waiting list.
1492 1407
                        in_waiting_list = True
1493 1408
                        if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
1494
                            raise APIError(
1495
                                _('sold out'),
1496
                                err_class='sold out',
1497
                            )
1409
                            raise APIError(N_('sold out'))
1498 1410
                else:
1499 1411
                    if (event.booked_places + places_count) > event.places:
1500
                        raise APIError(
1501
                            _('sold out'),
1502
                            err_class='sold out',
1503
                        )
1412
                        raise APIError(N_('sold out'))
1504 1413

  
1505 1414
        with transaction.atomic():
1506 1415
            if to_cancel_booking:
......
1618 1527
        context = {'allowed_agenda_slugs': agenda_slugs, 'agendas': Agenda.prefetch_recurring_events(agendas)}
1619 1528
        serializer = self.serializer_class(data=request.data, partial=True, context=context)
1620 1529
        if not serializer.is_valid():
1621
            raise APIError(
1622
                _('invalid payload'),
1623
                err_class='invalid payload',
1624
                errors=serializer.errors,
1625
                http_status=status.HTTP_400_BAD_REQUEST,
1626
            )
1530
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1627 1531
        payload = serializer.validated_data
1628 1532
        user_external_id = payload['user_external_id']
1629 1533
        agendas = Agenda.prefetch_events_and_exceptions(agendas, user_external_id=user_external_id)
......
1700 1604
            data=request.data, partial=True, context=self.serializer_extra_context
1701 1605
        )
1702 1606
        if not serializer.is_valid():
1703
            raise APIError(
1704
                _('invalid payload'),
1705
                err_class='invalid payload',
1706
                errors=serializer.errors,
1707
                http_status=status.HTTP_400_BAD_REQUEST,
1708
            )
1607
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1709 1608
        payload = serializer.validated_data
1710 1609
        user_external_id = payload['user_external_id']
1711 1610

  
......
1723 1622
        full_events = [str(event) for event in events.filter(full=True)]
1724 1623
        if full_events:
1725 1624
            raise APIError(
1726
                _('some events are full: %s') % ', '.join(full_events), err_class='some events are full'
1625
                N_('some events are full: %s') % ', '.join(full_events), err_class='some events are full'
1727 1626
            )
1728 1627

  
1729 1628
        events = events.annotate(
......
1827 1726

  
1828 1727
    def get(self, request, *args, **kwargs):
1829 1728
        if not request.GET.get('user_external_id'):
1830
            response = {
1831
                'err': 1,
1832
                'err_class': 'missing param user_external_id',
1833
                'err_desc': _('missing param user_external_id'),
1834
            }
1835
            return Response(response)
1729
            raise APIError(N_('missing param user_external_id'))
1836 1730

  
1837 1731
        try:
1838 1732
            response = super().get(request, *args, **kwargs)
1839 1733
        except ValidationError as e:
1840
            return Response(
1841
                {
1842
                    'err': 1,
1843
                    'err_class': 'invalid payload',
1844
                    'err_desc': _('invalid payload'),
1845
                    'errors': e.detail,
1846
                },
1847
                status=status.HTTP_400_BAD_REQUEST,
1848
            )
1734
            raise APIErrorBadRequest(N_('invalid payload'), errors=e.detail)
1849 1735

  
1850 1736
        return Response({'err': 0, 'data': response.data})
1851 1737

  
......
1866 1752

  
1867 1753
    def check_booking(self, check_waiting_list=False):
1868 1754
        if self.booking.cancellation_datetime:
1869
            return Response(
1870
                {'err': 1, 'err_class': 'booking is cancelled', 'err_desc': _('booking is cancelled')}
1871
            )
1755
            raise APIError(N_('booking is cancelled'))
1872 1756

  
1873 1757
        if self.booking.primary_booking is not None:
1874
            return Response({'err': 2, 'err_class': 'secondary booking', 'err_desc': _('secondary booking')})
1758
            raise APIError(N_('secondary booking'), err=2)
1875 1759

  
1876 1760
        if check_waiting_list and self.booking.in_waiting_list:
1877
            response = {
1878
                'err': 3,
1879
                'err_class': 'booking is in waiting list',
1880
                'err_desc': _('booking is in waiting list'),
1881
            }
1882
            return Response(response)
1761
            raise APIError(N_('booking is in waiting list'), err=3)
1883 1762

  
1884 1763
    def get(self, request, *args, **kwargs):
1885
        response = self.check_booking()
1886
        if response:
1887
            return response
1764
        self.check_booking()
1888 1765

  
1889 1766
        serializer = self.serializer_class(self.booking)
1890 1767
        response = serializer.data
......
1897 1774
        return Response(response)
1898 1775

  
1899 1776
    def patch(self, request, *args, **kwargs):
1900
        response = self.check_booking(check_waiting_list=True)
1901
        if response:
1902
            return response
1777
        self.check_booking(check_waiting_list=True)
1903 1778

  
1904 1779
        serializer = self.serializer_class(self.booking, data=request.data, partial=True)
1905 1780

  
1906 1781
        if not serializer.is_valid():
1907
            return Response(
1908
                {
1909
                    'err': 4,
1910
                    'err_class': 'invalid payload',
1911
                    'err_desc': _('invalid payload'),
1912
                    'errors': serializer.errors,
1913
                },
1914
                status=status.HTTP_400_BAD_REQUEST,
1915
            )
1782
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=4)
1916 1783

  
1917 1784
        if (
1918 1785
            self.booking.event.checked
1919 1786
            and self.booking.event.agenda.disable_check_update
1920 1787
            and ('user_was_present' in request.data or 'user_absence_reason' in request.data)
1921 1788
        ):
1922
            return Response(
1923
                {
1924
                    'err': 5,
1925
                    'err_class': 'event is marked as checked',
1926
                    'err_desc': _('event is marked as checked'),
1927
                    'errors': serializer.errors,
1928
                },
1929
                status=status.HTTP_400_BAD_REQUEST,
1930
            )
1789
            raise APIErrorBadRequest(N_('event is marked as checked'), err=5)
1931 1790

  
1932 1791
        if 'extra_data' in serializer.validated_data:
1933 1792
            extra_data = self.booking.extra_data or {}
......
1947 1806
        return Response(response)
1948 1807

  
1949 1808
    def delete(self, request, *args, **kwargs):
1950
        response = self.check_booking()
1951
        if response:
1952
            return response
1809
        self.check_booking()
1953 1810

  
1954 1811
        self.booking.cancel()
1955 1812
        response = {'err': 0, 'booking_id': self.booking.pk}
......
1972 1829
    def post(self, request, booking_pk=None, format=None):
1973 1830
        booking = get_object_or_404(Booking, id=booking_pk)
1974 1831
        if booking.cancellation_datetime:
1975
            response = {
1976
                'err': 1,
1977
                'err_class': 'already cancelled',
1978
                'err_desc': _('already cancelled'),
1979
            }
1980
            return Response(response)
1832
            raise APIError(N_('already cancelled'))
1981 1833
        if booking.primary_booking is not None:
1982
            response = {
1983
                'err': 2,
1984
                'err_class': 'secondary booking',
1985
                'err_desc': _('secondary booking'),
1986
            }
1987
            return Response(response)
1834
            raise APIError(N_('secondary booking'), err=2)
1988 1835
        booking.cancel()
1989 1836
        response = {'err': 0, 'booking_id': booking.id}
1990 1837
        return Response(response)
......
2007 1854
    def post(self, request, booking_pk=None, format=None):
2008 1855
        booking = get_object_or_404(Booking, id=booking_pk, event__agenda__kind='events')
2009 1856
        if booking.cancellation_datetime:
2010
            response = {
2011
                'err': 1,
2012
                'err_class': 'booking is cancelled',
2013
                'err_desc': _('booking is cancelled'),
2014
            }
2015
            return Response(response)
1857
            raise APIError(N_('booking is cancelled'))
2016 1858
        if booking.primary_booking is not None:
2017
            response = {
2018
                'err': 2,
2019
                'err_class': 'secondary booking',
2020
                'err_desc': _('secondary booking'),
2021
            }
2022
            return Response(response)
1859
            raise APIError(N_('secondary booking'), err=2)
2023 1860
        if not booking.in_waiting_list:
2024
            response = {
2025
                'err': 3,
2026
                'err_class': 'booking is not in waiting list',
2027
                'err_desc': _('booking is not in waiting list'),
2028
            }
2029
            return Response(response)
1861
            raise APIError(N_('booking is not in waiting list'), err=3)
2030 1862
        booking.accept()
2031 1863
        event = booking.event
2032 1864
        response = {
......
2054 1886
    def post(self, request, booking_pk=None, format=None):
2055 1887
        booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events')
2056 1888
        if booking.cancellation_datetime:
2057
            response = {
2058
                'err': 1,
2059
                'err_class': 'booking is cancelled',
2060
                'err_desc': _('booking is cancelled'),
2061
            }
2062
            return Response(response)
1889
            raise APIError(N_('booking is cancelled'))
2063 1890
        if booking.primary_booking is not None:
2064
            response = {
2065
                'err': 2,
2066
                'err_class': 'secondary booking',
2067
                'err_desc': _('secondary booking'),
2068
            }
2069
            return Response(response)
1891
            raise APIError(N_('secondary booking'), err=2)
2070 1892
        if booking.in_waiting_list:
2071
            response = {
2072
                'err': 3,
2073
                'err_class': 'booking is already in waiting list',
2074
                'err_desc': _('booking is already in waiting list'),
2075
            }
2076
            return Response(response)
1893
            raise APIError(N_('booking is already in waiting list'), err=3)
2077 1894
        booking.suspend()
2078 1895
        response = {'err': 0, 'booking_id': booking.pk}
2079 1896
        return Response(response)
......
2112 1929
    def post(self, request, booking_pk=None, format=None):
2113 1930
        serializer = self.serializer_class(data=request.data)
2114 1931
        if not serializer.is_valid():
2115
            return Response(
2116
                {
2117
                    'err': 1,
2118
                    'err_class': 'invalid payload',
2119
                    'err_desc': _('invalid payload'),
2120
                    'errors': serializer.errors,
2121
                },
2122
                status=status.HTTP_400_BAD_REQUEST,
2123
            )
1932
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2124 1933
        payload = serializer.validated_data
2125 1934

  
2126 1935
        booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events')
2127 1936
        event = booking.event
2128 1937
        if booking.cancellation_datetime:
2129
            response = {
2130
                'err': 1,
2131
                'err_class': 'booking is cancelled',
2132
                'err_desc': _('booking is cancelled'),
2133
            }
2134
            return Response(response)
1938
            raise APIError(N_('booking is cancelled'))
2135 1939
        if booking.primary_booking is not None:
2136
            response = {
2137
                'err': 2,
2138
                'err_class': 'secondary booking',
2139
                'err_desc': _('secondary booking'),
2140
            }
2141
            return Response(response)
1940
            raise APIError(N_('secondary booking'), err=2)
2142 1941
        event_ids = {event.pk}
2143 1942
        in_waiting_list = {booking.in_waiting_list}
2144 1943
        secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime')
......
2146 1945
            event_ids.add(secondary.event_id)
2147 1946
            in_waiting_list.add(secondary.in_waiting_list)
2148 1947
        if len(event_ids) > 1:
2149
            response = {
2150
                'err': 4,
2151
                'err_class': 'can not resize multi event booking',
2152
                'err_desc': _('can not resize multi event booking'),
2153
            }
2154
            return Response(response)
1948
            raise APIError(N_('can not resize multi event booking'), err=4)
2155 1949
        if len(in_waiting_list) > 1:
2156
            response = {
2157
                'err': 5,
2158
                'err_class': 'can not resize booking: waiting list inconsistency',
2159
                'err_desc': _('can not resize booking: waiting list inconsistency'),
2160
            }
2161
            return Response(response)
1950
            raise APIError(N_('can not resize booking: waiting list inconsistency'), err=5)
2162 1951

  
2163 1952
        # total places for the event (in waiting or main list, depending on the primary booking location)
2164 1953
        places = event.waiting_list_places if booking.in_waiting_list else event.places
......
2182 1971
            # oversized request
2183 1972
            if booking.in_waiting_list:
2184 1973
                # booking in waiting list: can not be overbooked
2185
                response = {
2186
                    'err': 3,
2187
                    'err_class': 'sold out',
2188
                    'err_desc': _('sold out'),
2189
                }
2190
                return Response(response)
1974
                raise APIError(N_('sold out'), err=3)
2191 1975
            if event.booked_places <= event.places:
2192 1976
                # in main list and no overbooking for the moment: can not be overbooked
2193
                response = {
2194
                    'err': 3,
2195
                    'err_class': 'sold out',
2196
                    'err_desc': _('sold out'),
2197
                }
2198
                return Response(response)
1977
                raise APIError(N_('sold out'), err=3)
2199 1978
        return self.increase(booking, secondary_bookings, primary_booked_places, primary_wanted_places)
2200 1979

  
2201 1980
    def increase(self, booking, secondary_bookings, primary_booked_places, primary_wanted_places):
......
2245 2024

  
2246 2025
        serializer = self.serializer_class(data=request.data)
2247 2026
        if not serializer.is_valid():
2248
            raise APIError(
2249
                _('invalid payload'),
2250
                err_class='invalid payload',
2251
                errors=serializer.errors,
2252
                http_status=status.HTTP_400_BAD_REQUEST,
2253
            )
2027
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2254 2028
        payload = serializer.validated_data
2255 2029
        event = Event.objects.create(agenda=agenda, **payload)
2256 2030
        if event.recurrence_days and event.recurrence_end_date:
......
2261 2035
        event = self.get_object(agenda_identifier, event_identifier)
2262 2036
        serializer = self.serializer_class(event, data=request.data, partial=True)
2263 2037
        if not serializer.is_valid():
2264
            raise APIError(
2265
                _('invalid payload'),
2266
                err_class='invalid payload',
2267
                errors=serializer.errors,
2268
                http_status=status.HTTP_400_BAD_REQUEST,
2269
            )
2038
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2270 2039

  
2271 2040
        payload = serializer.validated_data
2272 2041
        changed_data = []
......
2282 2051
                    'recurrence_days',
2283 2052
                    'recurrence_week_interval',
2284 2053
                ):
2285
                    raise APIError(
2286
                        _('%s cannot be modified on an event recurrence') % field,
2287
                        err_class='%s cannot be modified on an event recurrence' % field,
2288
                        http_status=status.HTTP_400_BAD_REQUEST,
2289
                    )
2054
                    raise APIErrorBadRequest(N_('%s cannot be modified on an event recurrence') % field)
2290 2055

  
2291 2056
        protected_fields = ['start_datetime', 'recurrence_days', 'recurrence_week_interval']
2292 2057
        if event.recurrence_days and event.has_recurrences_booked():
2293 2058
            for field in changed_data:
2294 2059
                if field in protected_fields:
2295
                    raise APIError(
2296
                        _('%s cannot be modified because some recurrences have bookings attached to them.')
2297
                        % field,
2298
                        err_class='%s cannot be modified because some recurrences have bookings attached to them.'
2299
                        % field,
2300
                        http_status=status.HTTP_400_BAD_REQUEST,
2060
                    raise APIErrorBadRequest(
2061
                        N_('%s cannot be modified because some recurrences have bookings attached to them.')
2062
                        % field
2301 2063
                    )
2302 2064
            if 'recurrence_end_date' in changed_data and event.has_recurrences_booked(
2303 2065
                after=payload['recurrence_end_date']
2304 2066
            ):
2305
                raise APIError(
2306
                    _('recurrence_end_date cannot be modified because bookings exist after this date.'),
2307
                    err_class='recurrence_end_date cannot be modified because bookings exist after this date.',
2308
                    http_status=status.HTTP_400_BAD_REQUEST,
2067
                raise APIErrorBadRequest(
2068
                    N_('recurrence_end_date cannot be modified because bookings exist after this date.')
2309 2069
                )
2310 2070

  
2311 2071
        with event.update_recurrences(
......
2392 2152

  
2393 2153
    def get(self, request, agenda_identifier=None, event_identifier=None, format=None):
2394 2154
        if not request.GET.get('user_external_id'):
2395
            response = {
2396
                'err': 1,
2397
                'err_class': 'missing param user_external_id',
2398
                'err_desc': _('missing param user_external_id'),
2399
            }
2400
            return Response(response)
2155
            raise APIError(N_('missing param user_external_id'))
2401 2156
        event = self.get_object(agenda_identifier, event_identifier)
2402 2157
        booking_queryset = event.booking_set.filter(
2403 2158
            user_external_id=request.GET['user_external_id'],
......
2497 2252
    def get(self, request, *args, **kwargs):
2498 2253
        serializer = self.serializer_class(data=request.query_params)
2499 2254
        if not serializer.is_valid():
2500
            raise APIError(
2501
                _('invalid statistics filters'),
2502
                err_class='invalid statistics filters',
2503
                errors=serializer.errors,
2504
                http_status=status.HTTP_400_BAD_REQUEST,
2505
            )
2255
            raise APIErrorBadRequest(N_('invalid statistics filters'), errors=serializer.errors)
2506 2256
        data = serializer.validated_data
2507 2257

  
2508 2258
        bookings = Booking.objects
tests/test_api_utils.py
1 1
import pytest
2 2

  
3
from chrono.agendas.models import Agenda
3 4
from chrono.api.utils import Response
4 5

  
5 6

  
......
16 17
def test_response_data(data, expected):
17 18
    resp = Response(data=data)
18 19
    assert resp.data == expected
20

  
21

  
22
def test_err_desc_translation(db, app, settings):
23
    settings.LANGUAGE_CODE = 'fr-fr'
24
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
25

  
26
    resp = app.get(
27
        '/api/agenda/%s/datetimes/' % agenda.slug,
28
        params={'user_external_id': '42', 'exclude_user_external_id': '35'},
29
        status=400,
30
    )
31
    assert resp.json['err_desc'] == 'contenu de requête invalide'
32
    assert resp.json['err_class'] == 'invalid payload'
19
-