0001-api-APIError-handling-51181.patch
chrono/api/utils.py | ||
---|---|---|
15 | 15 |
# You should have received a copy of the GNU Affero General Public License |
16 | 16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | |
18 | ||
19 |
from django.utils.encoding import force_text |
|
20 | ||
18 | 21 |
from rest_framework.response import Response as DRFResponse |
22 |
from rest_framework.views import exception_handler as DRF_exception_handler |
|
19 | 23 | |
20 | 24 | |
21 | 25 |
class Response(DRFResponse): |
... | ... | |
24 | 28 |
if data is not None and 'err_class' in data: |
25 | 29 |
data['reason'] = data['err_class'] |
26 | 30 |
super(Response, self).__init__(data=data, *args, **kwargs) |
31 | ||
32 | ||
33 |
class APIError(Exception): |
|
34 |
err = 1 |
|
35 |
http_status = 200 |
|
36 | ||
37 |
def __init__(self, *args, **kwargs): |
|
38 |
self.__dict__.update(kwargs) |
|
39 |
super(APIError, self).__init__(*args) |
|
40 | ||
41 |
def to_response(self): |
|
42 |
data = { |
|
43 |
'err': self.err, |
|
44 |
'err_class': self.err_class, |
|
45 |
'err_desc': force_text(self), |
|
46 |
} |
|
47 |
if hasattr(self, 'errors'): |
|
48 |
data['errors'] = self.errors |
|
49 |
return Response(data, status=self.http_status) |
|
50 | ||
51 | ||
52 |
def exception_handler(exc, context): |
|
53 |
if isinstance(exc, APIError): |
|
54 |
return exc.to_response() |
|
55 | ||
56 |
return DRF_exception_handler(exc, context) |
chrono/api/views.py | ||
---|---|---|
36 | 36 | |
37 | 37 |
from rest_framework.views import APIView |
38 | 38 | |
39 |
from chrono.api.utils import Response |
|
39 |
from chrono.api.utils import Response, APIError
|
|
40 | 40 |
from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriodException, Desk, BookingColor |
41 | 41 |
from ..interval import IntervalSet |
42 | 42 | |
43 | 43 | |
44 |
class APIError(Exception): |
|
45 |
err = 1 |
|
46 |
http_status = 200 |
|
47 | ||
48 |
def __init__(self, *args, **kwargs): |
|
49 |
self.__dict__.update(kwargs) |
|
50 |
super(APIError, self).__init__(*args) |
|
51 | ||
52 |
def to_response(self): |
|
53 |
data = { |
|
54 |
'err': self.err, |
|
55 |
'err_class': self.err_class, |
|
56 |
'err_desc': force_text(self), |
|
57 |
} |
|
58 |
if hasattr(self, 'errors'): |
|
59 |
data['errors'] = self.errors |
|
60 |
return Response(data, status=self.http_status) |
|
61 | ||
62 | ||
63 | 44 |
def format_response_datetime(dt): |
64 | 45 |
return localtime(dt).strftime('%Y-%m-%d %H:%M:%S') |
65 | 46 | |
... | ... | |
552 | 533 | |
553 | 534 |
date_start, date_end = request.GET.get('date_start'), request.GET.get('date_end') |
554 | 535 |
if date_start: |
555 |
date_start = make_aware(datetime.datetime.combine(parse_date(date_start), datetime.time(0, 0))) |
|
536 |
try: |
|
537 |
date_start = make_aware( |
|
538 |
datetime.datetime.combine(parse_date(date_start), datetime.time(0, 0)) |
|
539 |
) |
|
540 |
except TypeError: |
|
541 |
raise APIError( |
|
542 |
_('date_start format must be YYYY-MM-DD'), |
|
543 |
err_class='date_start format must be YYYY-MM-DD', |
|
544 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
545 |
) |
|
556 | 546 |
if date_end: |
557 |
date_end = make_aware(datetime.datetime.combine(parse_date(date_end), datetime.time(0, 0))) |
|
547 |
try: |
|
548 |
date_end = make_aware(datetime.datetime.combine(parse_date(date_end), datetime.time(0, 0))) |
|
549 |
except TypeError: |
|
550 |
raise APIError( |
|
551 |
_('date_end format must be YYYY-MM-DD'), |
|
552 |
err_class='date_end format must be YYYY-MM-DD', |
|
553 |
http_status=status.HTTP_400_BAD_REQUEST, |
|
554 |
) |
|
558 | 555 | |
559 | 556 |
entries = agenda.get_open_events(annotate_queryset=True, min_start=date_start, max_start=date_end) |
560 | 557 | |
... | ... | |
586 | 583 | |
587 | 584 |
now_datetime = now() |
588 | 585 | |
589 |
try: |
|
590 |
resources = get_resources_from_request(request, agenda) |
|
591 |
except APIError as e: |
|
592 |
return e.to_response() |
|
586 |
resources = get_resources_from_request(request, agenda) |
|
593 | 587 | |
594 | 588 |
start_datetime = None |
595 | 589 |
if 'date_start' in request.GET: |
... | ... | |
597 | 591 |
start_datetime = make_aware( |
598 | 592 |
datetime.datetime.combine(parse_date(request.GET['date_start']), datetime.time(0, 0)) |
599 | 593 |
) |
600 |
except TypeError as e:
|
|
594 |
except TypeError: |
|
601 | 595 |
raise APIError( |
602 | 596 |
_('date_start format must be YYYY-MM-DD'), |
603 | 597 |
err_class='date_start format must be YYYY-MM-DD', |
... | ... | |
610 | 604 |
end_datetime = make_aware( |
611 | 605 |
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0)) |
612 | 606 |
) |
613 |
except TypeError as e:
|
|
607 |
except TypeError: |
|
614 | 608 |
raise APIError( |
615 | 609 |
_('date_end format must be YYYY-MM-DD'), |
616 | 610 |
err_class='date_end format must be YYYY-MM-DD', |
... | ... | |
836 | 830 |
serializer_class = SlotsSerializer |
837 | 831 | |
838 | 832 |
def post(self, request, agenda_identifier=None, event_identifier=None, format=None): |
839 |
try: |
|
840 |
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format) |
|
841 |
except APIError as e: |
|
842 |
return e.to_response() |
|
833 |
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format) |
|
843 | 834 | |
844 | 835 |
def fillslot(self, request, agenda_identifier=None, slots=[], format=None): |
845 | 836 |
multiple_booking = bool(not slots) |
... | ... | |
961 | 952 |
http_status=status.HTTP_400_BAD_REQUEST, |
962 | 953 |
) |
963 | 954 | |
964 |
try: |
|
965 |
resources = get_resources_from_request(request, agenda) |
|
966 |
except APIError as e: |
|
967 |
return e.to_response() |
|
955 |
resources = get_resources_from_request(request, agenda) |
|
968 | 956 | |
969 | 957 |
# get all free slots and separate them by desk |
970 | 958 |
try: |
... | ... | |
1224 | 1212 |
serializer_class = SlotSerializer |
1225 | 1213 | |
1226 | 1214 |
def post(self, request, agenda_identifier=None, event_identifier=None, format=None): |
1227 |
try: |
|
1228 |
return self.fillslot( |
|
1229 |
request=request, |
|
1230 |
agenda_identifier=agenda_identifier, |
|
1231 |
slots=[event_identifier], # fill a "list on one slot" |
|
1232 |
format=format, |
|
1233 |
) |
|
1234 |
except APIError as e: |
|
1235 |
return e.to_response() |
|
1215 |
return self.fillslot( |
|
1216 |
request=request, |
|
1217 |
agenda_identifier=agenda_identifier, |
|
1218 |
slots=[event_identifier], # fill a "list on one slot" |
|
1219 |
format=format, |
|
1220 |
) |
|
1236 | 1221 | |
1237 | 1222 | |
1238 | 1223 |
fillslot = Fillslot.as_view() |
chrono/settings.py | ||
---|---|---|
184 | 184 |
SMS_URL = '' |
185 | 185 |
SMS_SENDER = '' |
186 | 186 | |
187 |
REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'chrono.api.utils.exception_handler'} |
|
188 | ||
187 | 189 |
local_settings_file = os.environ.get( |
188 | 190 |
'CHRONO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py') |
189 | 191 |
) |
tests/settings.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
REST_FRAMEWORK = { |
7 | 7 |
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.BasicAuthentication'], |
8 |
'EXCEPTION_HANDLER': 'chrono.api.utils.exception_handler', |
|
8 | 9 |
} |
9 | 10 | |
10 | 11 |
DATABASES = { |
tests/test_api.py | ||
---|---|---|
3176 | 3176 |
event_dt = datetime.datetime.combine(day, datetime.datetime.strptime(event, '%H:%M').time()) |
3177 | 3177 |
Event.objects.create(agenda=agenda2, start_datetime=make_aware(event_dt), places=2) |
3178 | 3178 | |
3179 |
params = {'date_start': 'foo'} |
|
3180 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400) |
|
3181 |
assert resp.json['err'] == 1 |
|
3182 |
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD' |
|
3183 |
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD' |
|
3184 | ||
3185 |
params = {'date_end': 'foo'} |
|
3186 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400) |
|
3187 |
assert resp.json['err'] == 1 |
|
3188 |
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD' |
|
3189 |
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD' |
|
3190 | ||
3179 | 3191 |
params = {'date_start': base_date.isoformat()} |
3180 | 3192 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params) |
3181 | 3193 |
assert len(resp.json['data']) == 6 |
... | ... | |
4977 | 4989 |
resp = app.get(virtual_api_url) |
4978 | 4990 |
assert len(resp.json['data']) == 24 |
4979 | 4991 | |
4992 |
params = {'date_start': 'foo'} |
|
4993 |
resp = app.get(foo_api_url, params=params, status=400) |
|
4994 |
assert resp.json['err'] == 1 |
|
4995 |
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD' |
|
4996 |
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD' |
|
4997 | ||
4998 |
params = {'date_end': 'foo'} |
|
4999 |
resp = app.get(foo_api_url, params=params, status=400) |
|
5000 |
assert resp.json['err'] == 1 |
|
5001 |
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD' |
|
5002 |
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD' |
|
5003 | ||
4980 | 5004 |
# exclude weekday1 through date_start, 4 slots each day * 5 days |
4981 | 5005 |
params = {'date_start': (localtime(now()) + datetime.timedelta(days=2)).date().isoformat()} |
4982 | 5006 |
resp = app.get(foo_api_url, params=params) |
4983 |
- |