Skip to content

Commit c0a4250

Browse files
authored
Merge pull request #61 from HackSoftware/exception-handling
Exception handling
2 parents 0a78cde + a099298 commit c0a4250

File tree

22 files changed

+275
-543
lines changed

22 files changed

+275
-543
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PYTHONBREAKPOINT=ipdb.set_trace

config/settings/base.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212

1313
import os
1414

15-
from .env_reader import env
15+
from .env_reader import env, environ
1616

1717
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
18-
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18+
BASE_DIR = environ.Path(__file__) - 3
1919

20+
env.read_env(os.path.join(BASE_DIR, ".env"))
2021

2122
# Quick-start development settings - unsuitable for production
2223
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
@@ -33,10 +34,12 @@
3334
# Application definition
3435

3536
LOCAL_APPS = [
37+
'styleguide_example.core.apps.CoreConfig',
3638
'styleguide_example.common.apps.CommonConfig',
3739
'styleguide_example.tasks.apps.TasksConfig',
3840
'styleguide_example.api.apps.ApiConfig',
3941
'styleguide_example.users.apps.UsersConfig',
42+
'styleguide_example.errors.apps.ErrorsConfig',
4043
]
4144

4245
THIRD_PARTY_APPS = [
@@ -155,7 +158,8 @@
155158
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
156159

157160
REST_FRAMEWORK = {
158-
'EXCEPTION_HANDLER': 'styleguide_example.api.errors.custom_exception_handler',
161+
'EXCEPTION_HANDLER': 'styleguide_example.api.exception_handlers.drf_default_with_modifications_exception_handler',
162+
# 'EXCEPTION_HANDLER': 'styleguide_example.api.exception_handlers.hacksoft_proposed_exception_handler',
159163
'DEFAULT_FILTER_BACKENDS': (
160164
'django_filters.rest_framework.DjangoFilterBackend',
161165
),

styleguide_example/api/errors.py

Lines changed: 0 additions & 161 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from django.core.exceptions import ValidationError as DjangoValidationError, PermissionDenied
2+
from django.http import Http404
3+
4+
from rest_framework.views import exception_handler
5+
from rest_framework import exceptions
6+
from rest_framework.serializers import as_serializer_error
7+
from rest_framework.response import Response
8+
9+
from styleguide_example.core.exceptions import ApplicationError
10+
11+
12+
def drf_default_with_modifications_exception_handler(exc, ctx):
13+
if isinstance(exc, DjangoValidationError):
14+
exc = exceptions.ValidationError(as_serializer_error(exc))
15+
16+
if isinstance(exc, Http404):
17+
exc = exceptions.NotFound()
18+
19+
if isinstance(exc, PermissionDenied):
20+
exc = exceptions.PermissionDenied()
21+
22+
response = exception_handler(exc, ctx)
23+
24+
# If unexpected error occurs (server error, etc.)
25+
if response is None:
26+
return response
27+
28+
if isinstance(exc.detail, (list, dict)):
29+
response.data = {
30+
"detail": response.data
31+
}
32+
33+
return response
34+
35+
36+
def hacksoft_proposed_exception_handler(exc, ctx):
37+
"""
38+
{
39+
"message": "Error message",
40+
"extra": {}
41+
}
42+
"""
43+
if isinstance(exc, DjangoValidationError):
44+
exc = exceptions.ValidationError(as_serializer_error(exc))
45+
46+
if isinstance(exc, Http404):
47+
exc = exceptions.NotFound()
48+
49+
if isinstance(exc, PermissionDenied):
50+
exc = exceptions.PermissionDenied()
51+
52+
response = exception_handler(exc, ctx)
53+
54+
# If unexpected error occurs (server error, etc.)
55+
if response is None:
56+
if isinstance(exc, ApplicationError):
57+
data = {
58+
"message": exc.message,
59+
"extra": exc.extra
60+
}
61+
return Response(data, status=400)
62+
63+
return response
64+
65+
if isinstance(exc.detail, (list, dict)):
66+
response.data = {
67+
"detail": response.data
68+
}
69+
70+
if isinstance(exc, exceptions.ValidationError):
71+
response.data["message"] = "Validation error"
72+
response.data["extra"] = {
73+
"fields": response.data["detail"]
74+
}
75+
else:
76+
response.data["message"] = response.data["detail"]
77+
response.data["extra"] = {}
78+
79+
del response.data["detail"]
80+
81+
return response

styleguide_example/api/mixins.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
from importlib import import_module
22

3-
from django.core.exceptions import ValidationError
43
from django.conf import settings
54

65
from django.contrib import auth
76

8-
from rest_framework import exceptions as rest_exceptions
97
from rest_framework.permissions import IsAuthenticated
108
from rest_framework.authentication import SessionAuthentication, BaseAuthentication
119

12-
from styleguide_example.api.errors import get_error_message
13-
1410

1511
def get_auth_header(headers):
1612
value = headers.get('Authorization')
@@ -65,24 +61,3 @@ def enforce_csrf(self, request):
6561
class ApiAuthMixin:
6662
authentication_classes = (CsrfExemptedSessionAuthentication, SessionAsHeaderAuthentication)
6763
permission_classes = (IsAuthenticated, )
68-
69-
70-
class ApiErrorsMixin:
71-
"""
72-
Mixin that transforms Django and Python exceptions into rest_framework ones.
73-
without the mixin, they return 500 status code which is not desired.
74-
"""
75-
expected_exceptions = {
76-
ValueError: rest_exceptions.ValidationError,
77-
ValidationError: rest_exceptions.ValidationError,
78-
PermissionError: rest_exceptions.PermissionDenied
79-
}
80-
81-
def handle_exception(self, exc):
82-
if isinstance(exc, tuple(self.expected_exceptions.keys())):
83-
drf_exception_class = self.expected_exceptions[exc.__class__]
84-
drf_exception = drf_exception_class(get_error_message(exc))
85-
86-
return super().handle_exception(drf_exception)
87-
88-
return super().handle_exception(exc)

0 commit comments

Comments
 (0)