8

I guess this is more of a code quality question but it does involve handling unhandled exceptions is Django rest framework.

Deleting a protected record just return <h1>500 internal server error<h1> So I added the example custom exception handler. The first line returns a response that is none.

response = exception_handler(exc, context)

from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is None:
        #DRF could not process the exception so we will treat it as a 500 and try to get the user as much info as possible.
        response = Response({'error': str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

return response

So in this case I am treating it as a 500 because the DRF couldn't handle the exc.

I guess my question is is this an appropriate way to handle it and does anyone have experience with this have a better solution?

Update:

Traceback:

File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
34.             response = get_response(request)

File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
115.                 response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python3.6/site-packages/django/views/decorators/csrf.py" in wrapped_view
54.         return view_func(*args, **kwargs)

File "/usr/local/lib/python3.6/site-packages/rest_framework/viewsets.py" in view
116.             return self.dispatch(request, *args, **kwargs)

File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
495.             response = self.handle_exception(exc)

File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in handle_exception
455.             self.raise_uncaught_exception(exc)

File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
492.             response = handler(request, *args, **kwargs)

File "/usr/local/lib/python3.6/site-packages/rest_framework/mixins.py" in create
21.         self.perform_create(serializer)

File "/device_mgmt/selection/views.py" in perform_create
84.             serializer.save(realm=utils.get_realm_from_request(self.request))

File "/usr/local/lib/python3.6/site-packages/rest_framework/serializers.py" in save
214.             self.instance = self.create(validated_data)

File "/usr/local/lib/python3.6/site-packages/rest_framework/serializers.py" in create
943.             instance = ModelClass._default_manager.create(**validated_data)

File "/usr/local/lib/python3.6/site-packages/django/db/models/manager.py" in manager_method
82.                 return getattr(self.get_queryset(), name)(*args, **kwargs)

File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py" in create
422.         obj.save(force_insert=True, using=self.db)

File "/device_mgmt/selection/models.py" in save
123.         self.full_clean()

File "/usr/local/lib/python3.6/site-packages/django/db/models/base.py" in full_clean
1203.             raise ValidationError(errors)

Exception Type: ValidationError at /company/api/company/
Exception Value: {'id': ['Company with this Id already exists.']}

Django models are throwing validation error but rest framework view it calling it uncaught.

2 Answers 2

12

This seems to be the thing I was looking for...

https://gist.github.com/twidi/9d55486c36b6a51bdcb05ce3a763e79f

Basically convert the django exception into a drf exception with the same details.

"""
Sometimes in your Django model you want to raise a ``ValidationError`` 
in the ``save`` method, for
some reason.
This exception is not managed by Django Rest Framework because it 
occurs after its validation 
process. So at the end, you'll have a 500.
Correcting this is as simple as overriding the exception handler, by 
converting the Django
``ValidationError`` to a DRF one.
"""

from django.core.exceptions import ValidationError as 
DjangoValidationError

from rest_framework.exceptions import ValidationError as 
DRFValidationError
from rest_framework.views import exception_handler as 
drf_exception_handler


def exception_handler(exc, context):
    """Handle Django ValidationError as an accepted exception
    Must be set in settings:
    >>> REST_FRAMEWORK = {
    ...     # ...
    ...     'EXCEPTION_HANDLER': 'mtp.apps.common.drf.exception_handler',
    ...     # ...
    ... }
    For the parameters, see ``exception_handler``
    """

    if isinstance(exc, DjangoValidationError):
        if hasattr(exc, 'message_dict'):
            exc = DRFValidationError(detail={'error': exc.message_dict})
        elif hasattr(exc, 'message'):
            exc = DRFValidationError(detail={'error': exc.message})
        elif hasattr(exc, 'messages'):
            exc = DRFValidationError(detail={'error': exc.messages})

    return drf_exception_handler(exc, context)

This worked for me and now instead of a generic 500 response I get a 500 response with the relevant details.

Sign up to request clarification or add additional context in comments.

Comments

0

You can use the Django REST Framework exceptions

In your specific case you'd probably want to use NotFound

from rest_framework.exceptions import NotFound

# something bad happens
raise NotFound('User Message')

NotFound will throw a 404 error by default, to raise a 500 you could add.

raise NotFound('User Message', code=500)

The other exceptions are used the same way.

3 Comments

Sorry, I didn't post the stack trace but it does ever hit my viewset directly. It all happens in the background.
gonna need a stacktrace and more details then -- not clear what/where your issue is
or you could raise exceptions.APIException. And for later versions, we use string value instead of numerical in code attribute, so code=500 would be code="error".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.