Projet

Général

Profil

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

Valentin Deniaud, 04 novembre 2021 14:47

Télécharger (38 ko)

Voir les différences:

Subject: [PATCH] api: make APIError less verbose (#58014)

 chrono/api/utils.py                           |  20 +-
 chrono/api/views.py                           | 408 ++++--------------
 .../management/commands/makemessages.py       |   2 +
 tests/test_api_utils.py                       |  18 +
 4 files changed, 113 insertions(+), 335 deletions(-)
chrono/api/utils.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17

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

  
......
29 29

  
30 30

  
31 31
class APIError(Exception):
32
    err = 1
33 32
    http_status = 200
34 33

  
35
    def __init__(self, *args, **kwargs):
36
        self.__dict__.update(kwargs)
37
        super().__init__(*args)
34
    def __init__(self, message, *args, err=1, err_class=None, errors=None):
35
        self.err_desc = _(message) % args
36
        self.err = err
37
        self.err_class = err_class or message % args
38
        self.errors = errors
39
        super().__init__(self.err_desc)
38 40

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

  
49 51

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

  
55

  
50 56
def exception_handler(exc, context):
51 57
    if isinstance(exc, APIError):
52 58
        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'),
618
                    event.slug,
629 619
                    err_class='event is already booked by user',
630 620
                )
631 621
        if event.recurrence_days:
632 622
            raise APIError(
633
                _('event %s is recurrent, direct booking is forbidden') % event.slug,
623
                N_('event %s is recurrent, direct booking is forbidden'),
624
                event.slug,
634 625
                err_class='event is recurrent',
635 626
            )
636 627

  
637 628
    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
        )
629
        raise APIErrorBadRequest(N_('unknown event identifiers or slugs'))
643 630
    return events
644 631

  
645 632

  
......
656 643
    if len(objects) != len(slugs):
657 644
        unknown_slugs = sorted(slugs - {obj.slug for obj in objects})
658 645
        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
        )
646
        raise APIErrorBadRequest(N_('invalid slugs: %s'), unknown_slugs)
664 647
    return objects
665 648

  
666 649

  
667 650
def get_start_and_end_datetime_from_request(request):
668 651
    serializer = serializers.DateRangeSerializer(data=request.query_params)
669 652
    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
        )
653
        raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
676 654

  
677 655
    return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end')
678 656

  
......
680 658
def get_agendas_from_request(request):
681 659
    serializer = serializers.AgendaSlugsSerializer(data=request.query_params)
682 660
    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
        )
661
        raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
689 662

  
690 663
    return serializer.validated_data.get('agendas')
691 664

  
......
757 730
    def post(self, request, format=None):
758 731
        serializer = self.serializer_class(data=request.data)
759 732
        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
            )
733
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
766 734
        agenda = serializer.save()
767 735
        return Response({'err': 0, 'data': [get_agenda_detail(request, agenda)]})
768 736

  
......
803 771

  
804 772
        serializer = self.serializer_class(data=request.query_params)
805 773
        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
            )
774
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
812 775
        payload = serializer.validated_data
813 776

  
814 777
        user_external_id = payload.get('user_external_id') or payload.get('exclude_user_external_id')
......
876 839
    def get(self, request):
877 840
        serializer = self.serializer_class(data=request.query_params)
878 841
        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
            )
842
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
885 843
        payload = serializer.validated_data
886 844

  
887 845
        agenda_slugs = payload['agendas']
......
969 927
            and excluded_user_external_id
970 928
            and booked_user_external_id != excluded_user_external_id
971 929
        ):
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,
930
            raise APIErrorBadRequest(
931
                N_('user_external_id and exclude_user_external_id have different values')
976 932
            )
977 933

  
978 934
        # Generate an unique slot for each possible meeting [start_datetime,
......
1244 1200
        )
1245 1201
        if known_body_params:
1246 1202
            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,
1203
            raise APIErrorBadRequest(
1204
                N_('parameters "%s" must be included in request body, not query'), params
1251 1205
            )
1252 1206

  
1253 1207
        serializer = self.serializer_class(data=request.data, partial=True)
1254 1208
        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
            )
1209
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1261 1210
        payload = serializer.validated_data
1262 1211

  
1263 1212
        if 'slots' in payload:
......
1270 1219
            try:
1271 1220
                places_count = int(request.query_params['count'])
1272 1221
            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
                )
1222
                raise APIErrorBadRequest(N_('invalid value for count (%s)'), request.query_params['count'])
1278 1223
        else:
1279 1224
            places_count = 1
1280 1225

  
1281 1226
        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
            )
1227
            raise APIErrorBadRequest(N_('count cannot be less than or equal to zero'))
1287 1228

  
1288 1229
        to_cancel_booking = None
1289 1230
        cancel_booking_id = None
......
1291 1232
            try:
1292 1233
                cancel_booking_id = int(payload.get('cancel_booking_id'))
1293 1234
            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
                )
1235
                raise APIErrorBadRequest(N_('cancel_booking_id is not an integer'))
1299 1236

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

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

  
1319 1256
        extra_data = {}
1320 1257
        for k, v in request.data.items():
......
1335 1272
                try:
1336 1273
                    meeting_type_id_, datetime_str = slot.split(':')
1337 1274
                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
                    )
1275
                    raise APIErrorBadRequest(N_('invalid slot: %s'), slot)
1343 1276
                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,
1277
                    raise APIErrorBadRequest(
1278
                        N_('all slots must have the same meeting type id (%s)'), meeting_type_id
1348 1279
                    )
1349 1280
                try:
1350 1281
                    datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
1351 1282
                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
                    )
1283
                    raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
1357 1284

  
1358 1285
            resources = get_resources_from_request(request, agenda)
1359 1286

  
......
1365 1292
                    # legacy access by id
1366 1293
                    meeting_type = agenda.get_meetingtype(id_=meeting_type_id)
1367 1294
            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
                )
1295
                raise APIErrorBadRequest(N_('invalid meeting type id: %s'), meeting_type_id)
1373 1296
            all_slots = sorted(
1374 1297
                get_all_slots(
1375 1298
                    agenda,
......
1439 1362
                        break
1440 1363

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

  
1447 1367
            # all datetimes are free, book them in order
1448 1368
            datetimes = list(datetimes)
......
1476 1396
        for event in events:
1477 1397
            if event.start_datetime > now():
1478 1398
                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
                    )
1399
                    raise APIError(N_('no waiting list'))
1483 1400

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

  
1505 1416
        with transaction.atomic():
1506 1417
            if to_cancel_booking:
......
1618 1529
        context = {'allowed_agenda_slugs': agenda_slugs, 'agendas': Agenda.prefetch_recurring_events(agendas)}
1619 1530
        serializer = self.serializer_class(data=request.data, partial=True, context=context)
1620 1531
        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
            )
1532
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1627 1533
        payload = serializer.validated_data
1628 1534
        user_external_id = payload['user_external_id']
1629 1535
        agendas = Agenda.prefetch_events_and_exceptions(agendas, user_external_id=user_external_id)
......
1700 1606
            data=request.data, partial=True, context=self.serializer_extra_context
1701 1607
        )
1702 1608
        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
            )
1609
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
1709 1610
        payload = serializer.validated_data
1710 1611
        user_external_id = payload['user_external_id']
1711 1612

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

  
1729 1630
        events = events.annotate(
......
1827 1728

  
1828 1729
    def get(self, request, *args, **kwargs):
1829 1730
        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)
1731
            raise APIError(N_('missing param user_external_id'))
1836 1732

  
1837 1733
        try:
1838 1734
            response = super().get(request, *args, **kwargs)
1839 1735
        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
            )
1736
            raise APIErrorBadRequest(N_('invalid payload'), errors=e.detail)
1849 1737

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

  
......
1866 1754

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

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

  
1876 1762
        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)
1763
            raise APIError(N_('booking is in waiting list'), err=3)
1883 1764

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

  
1889 1768
        serializer = self.serializer_class(self.booking)
1890 1769
        response = serializer.data
......
1897 1776
        return Response(response)
1898 1777

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

  
1904 1781
        serializer = self.serializer_class(self.booking, data=request.data, partial=True)
1905 1782

  
1906 1783
        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
            )
1784
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=4)
1916 1785

  
1917 1786
        if (
1918 1787
            self.booking.event.checked
1919 1788
            and self.booking.event.agenda.disable_check_update
1920 1789
            and ('user_was_present' in request.data or 'user_absence_reason' in request.data)
1921 1790
        ):
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
            )
1791
            raise APIErrorBadRequest(N_('event is marked as checked'), err=5)
1931 1792

  
1932 1793
        if 'extra_data' in serializer.validated_data:
1933 1794
            extra_data = self.booking.extra_data or {}
......
1947 1808
        return Response(response)
1948 1809

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

  
1954 1813
        self.booking.cancel()
1955 1814
        response = {'err': 0, 'booking_id': self.booking.pk}
......
1972 1831
    def post(self, request, booking_pk=None, format=None):
1973 1832
        booking = get_object_or_404(Booking, id=booking_pk)
1974 1833
        if booking.cancellation_datetime:
1975
            response = {
1976
                'err': 1,
1977
                'err_class': 'already cancelled',
1978
                'err_desc': _('already cancelled'),
1979
            }
1980
            return Response(response)
1834
            raise APIError(N_('already cancelled'))
1981 1835
        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)
1836
            raise APIError(N_('secondary booking'), err=2)
1988 1837
        booking.cancel()
1989 1838
        response = {'err': 0, 'booking_id': booking.id}
1990 1839
        return Response(response)
......
2007 1856
    def post(self, request, booking_pk=None, format=None):
2008 1857
        booking = get_object_or_404(Booking, id=booking_pk, event__agenda__kind='events')
2009 1858
        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)
1859
            raise APIError(N_('booking is cancelled'))
2016 1860
        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)
1861
            raise APIError(N_('secondary booking'), err=2)
2023 1862
        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)
1863
            raise APIError(N_('booking is not in waiting list'), err=3)
2030 1864
        booking.accept()
2031 1865
        event = booking.event
2032 1866
        response = {
......
2054 1888
    def post(self, request, booking_pk=None, format=None):
2055 1889
        booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events')
2056 1890
        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)
1891
            raise APIError(N_('booking is cancelled'))
2063 1892
        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)
1893
            raise APIError(N_('secondary booking'), err=2)
2070 1894
        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)
1895
            raise APIError(N_('booking is already in waiting list'), err=3)
2077 1896
        booking.suspend()
2078 1897
        response = {'err': 0, 'booking_id': booking.pk}
2079 1898
        return Response(response)
......
2112 1931
    def post(self, request, booking_pk=None, format=None):
2113 1932
        serializer = self.serializer_class(data=request.data)
2114 1933
        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
            )
1934
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2124 1935
        payload = serializer.validated_data
2125 1936

  
2126 1937
        booking = get_object_or_404(Booking, pk=booking_pk, event__agenda__kind='events')
2127 1938
        event = booking.event
2128 1939
        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)
1940
            raise APIError(N_('booking is cancelled'))
2135 1941
        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)
1942
            raise APIError(N_('secondary booking'), err=2)
2142 1943
        event_ids = {event.pk}
2143 1944
        in_waiting_list = {booking.in_waiting_list}
2144 1945
        secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime')
......
2146 1947
            event_ids.add(secondary.event_id)
2147 1948
            in_waiting_list.add(secondary.in_waiting_list)
2148 1949
        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)
1950
            raise APIError(N_('can not resize multi event booking'), err=4)
2155 1951
        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)
1952
            raise APIError(N_('can not resize booking: waiting list inconsistency'), err=5)
2162 1953

  
2163 1954
        # total places for the event (in waiting or main list, depending on the primary booking location)
2164 1955
        places = event.waiting_list_places if booking.in_waiting_list else event.places
......
2182 1973
            # oversized request
2183 1974
            if booking.in_waiting_list:
2184 1975
                # 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)
1976
                raise APIError(N_('sold out'), err=3)
2191 1977
            if event.booked_places <= event.places:
2192 1978
                # 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)
1979
                raise APIError(N_('sold out'), err=3)
2199 1980
        return self.increase(booking, secondary_bookings, primary_booked_places, primary_wanted_places)
2200 1981

  
2201 1982
    def increase(self, booking, secondary_bookings, primary_booked_places, primary_wanted_places):
......
2245 2026

  
2246 2027
        serializer = self.serializer_class(data=request.data)
2247 2028
        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
            )
2029
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2254 2030
        payload = serializer.validated_data
2255 2031
        event = Event.objects.create(agenda=agenda, **payload)
2256 2032
        if event.recurrence_days and event.recurrence_end_date:
......
2261 2037
        event = self.get_object(agenda_identifier, event_identifier)
2262 2038
        serializer = self.serializer_class(event, data=request.data, partial=True)
2263 2039
        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
            )
2040
            raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
2270 2041

  
2271 2042
        payload = serializer.validated_data
2272 2043
        changed_data = []
......
2282 2053
                    'recurrence_days',
2283 2054
                    'recurrence_week_interval',
2284 2055
                ):
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
                    )
2056
                    raise APIErrorBadRequest(N_('%s cannot be modified on an event recurrence'), field)
2290 2057

  
2291 2058
        protected_fields = ['start_datetime', 'recurrence_days', 'recurrence_week_interval']
2292 2059
        if event.recurrence_days and event.has_recurrences_booked():
2293 2060
            for field in changed_data:
2294 2061
                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,
2062
                    raise APIErrorBadRequest(
2063
                        N_('%s cannot be modified because some recurrences have bookings attached to them.')
2064
                        % field
2301 2065
                    )
2302 2066
            if 'recurrence_end_date' in changed_data and event.has_recurrences_booked(
2303 2067
                after=payload['recurrence_end_date']
2304 2068
            ):
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,
2069
                raise APIErrorBadRequest(
2070
                    N_('recurrence_end_date cannot be modified because bookings exist after this date.')
2309 2071
                )
2310 2072

  
2311 2073
        with event.update_recurrences(
......
2392 2154

  
2393 2155
    def get(self, request, agenda_identifier=None, event_identifier=None, format=None):
2394 2156
        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)
2157
            raise APIError(N_('missing param user_external_id'))
2401 2158
        event = self.get_object(agenda_identifier, event_identifier)
2402 2159
        booking_queryset = event.booking_set.filter(
2403 2160
            user_external_id=request.GET['user_external_id'],
......
2497 2254
    def get(self, request, *args, **kwargs):
2498 2255
        serializer = self.serializer_class(data=request.query_params)
2499 2256
        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
            )
2257
            raise APIErrorBadRequest(N_('invalid statistics filters'), errors=serializer.errors)
2506 2258
        data = serializer.validated_data
2507 2259

  
2508 2260
        bookings = Booking.objects
chrono/manager/management/commands/makemessages.py
18 18

  
19 19

  
20 20
class Command(makemessages.Command):
21
    xgettext_options = makemessages.Command.xgettext_options + ['--keyword=N_']
22

  
21 23
    def handle(self, *args, **options):
22 24
        if not options.get('add_location') and self.gettext_version >= (0, 19):
23 25
            options['add_location'] = 'file'
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'
33

  
34
    resp = app.get('/api/agendas/datetimes/?agendas=hop', status=400)
35
    assert resp.json['err_desc'] == 'slugs invalides : hop'
36
    assert resp.json['err_class'] == 'invalid slugs: hop'
19
-