Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch all exceptions in django rest framework

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.

like image 307
Michaela Ervin Avatar asked Feb 13 '18 00:02

Michaela Ervin


1 Answers

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.

like image 152
Michaela Ervin Avatar answered Sep 29 '22 18:09

Michaela Ervin