Global Exception Handling in Django-rest-framework - django

Is there a way to handle all the exceptions globally without using try-except block in django rest framework.
I want to convert html error page that django is throwing to a customised json object response.
I have created an exception.py file in my app
def custom_exception_handler(exc, context=None):
response = exception_handler(exc)
if isinstance(exc, HttpResponseServerError):
custom_response_data = {
'detail': 'Internal Server Error' # custom exception message
}
response.data = custom_response_data
return response
i have configured this in settings.py.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'EXCEPTION_HANDLER':'my_project.my_app.exceptions.custom_exception_handler'}

Since I came across with a similar situation that lead me to this question, I'll answer following the original question that is related to Django Rest Framework specifically and not just Django.
I understand that you want to handle raised exceptions from your views, globally, without having to define try/except blocks on each view module.
DRF allows you to define your own Custom Exception Handling mechanism (docs).
Here is an example:
At my_custom_except_handler.py:
import logging
from rest_framework.views import exception_handler
from django.http import JsonResponse
from requests import ConnectionError
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first
response = exception_handler(exc, context)
# checks if the raised exception is of the type you want to handle
if isinstance(exc, ConnectionError):
# defines custom response data
err_data = {'MSG_HEADER': 'some custom error messaging'}
# logs detail data from the exception being handled
logging.error(f"Original error detail and callstack: {exc}")
# returns a JsonResponse
return JsonResponse(err_data, safe=False, status=503)
# returns response as handled normally by the framework
return response
As stated in the docs, the defined response object refers to:
The exception handler function should either return a Response object, or return None if the exception cannot be handled. If the handler returns None then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
In other words, 'response' won't be None only when handling these exceptions docs:
Subclasses of APIException.
Django's Http404 exception.
Django's PermissionDenied exception.
If your custom handler returns None, then the exception will be handled 'normally' by the framework, returning typical 500 server error.
Finally remember to set the required key at settings.py:
REST_FRAMEWORK = {'EXCEPTION_HANDLER':
'my_project.my_app.my_custom_except_handler.custom_exception_handler'}
Hope it helps!

The definite answer to your question is no.
At least I don't know how to do it globally in Django, whereas global includes middleware exceptions).
Further, as request by #Shubham Kumar, the hook you need is process_exception and for an implementation check this SO post with the offical docs on how to activate it.
As stated in the Django docs:
request is an HttpRequest object. exception is an Exception object raised by the view function.
Django calls process_exception() when a view raises an exception. process_exception() should return either None or an HttpResponse object. If it returns an HttpResponse object, the template response and response middleware will be applied and the resulting response returned to the browser. Otherwise, default exception handling kicks in.
Again, middleware are run in reverse order during the response phase, which includes process_exception. If an exception middleware returns a response, the process_exception methods of the middleware classes above that middleware won’t be called at all.
Meaning that you will merely be able to hook into the view function and catch all those exceptions.

Related

Django, propagate exceptions to DjangoRestFramework

I'm using Django and DjangoRestFramework for my project and I'm facing some kind of "issue".
While DRF exceptions are properly returned through the response of the HTTP request, Django ones aren't.
For instance if I raise an IntegrityError from the Django part with, for instance, a duplicate key error (let's say I create 2 records with same unique primary key), I only get a "500 Server Error" in the response of my HTTP request.
How can we propagate exceptions from Django to DRF by default ? With an appropriate HTTP status code ?
The only way I found is to write a custom exception handler for DRF which tests the exception type like so :
from django.db import IntegrityError
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if isinstance(exc, IntegrityError):
# Do some custom thing on the Response object before returning it
return response

Custom Exceptions with Django Rest Framework

I want to create custom exceptions in DRF. As the DRF documentation states that i can do that by creating a user defined class and inheriting from APIException.
That works well but the issue is my application require me to send some type of custom codes along with the HTTP status codes.
For example, when an error occurred while creating a ProductCatalog it should return something like this along with HTTP status code.
{"code": 4026, "message": "Unable to create catalog"}
Such thing is required because although my API worked correctly so the http status code will be 200 but there were some business logic that was not fulfilled hence i need to return some sort of custom code and message to let the client application handle it accordingly.
Any kind of help will be appreciated.
You can consider creating a custom exception handler, along with a custom exception. Like this:
First create exception handler which will handle errors for DRF:
# custom handler
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.default_code
return response
Then update the settings.py with that error
# settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Finally, create a custom exception and use that when you need to raise Catalog creation error:
# Custom exception
from rest_framework.exceptions import APIException
class CatalogExeption(APIException):
status_code = 503
default_detail = 'Unable to create catalog'
default_code = '4026'
More information can be found regarding custom exception in documentation.
I figured it out myself. By going through the code i found that if you set default_detail to a dictionary, it will return that as it is.
In my case it would be something like this.
class ProductCatalogExeption(APIException):
status_code = 200 #or whatever you want
default_code = '4026'
# Custom response below
default_detail = {"code": 4026, "message": "Unable to create catalog"}
So when ProductCatalogException is raised it will return
{"code": 4026, "message": "Unable to create catalog"}
with HTTP Response code 200
For reference: https://github.com/encode/django-rest-framework/blob/master/rest_framework/exceptions.py

Using ParseError Django REST Framework to return invalid data

I have a similar issue as this question about validating data in the Django REST Framework outside of a serializer:
Raise Validation Error In Pre_Save Using Django Rest Framework
My code:
def pre_save(self, obj):
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError('An unpermitted user has been included')
From the trace it looks like it's trying to send the response but it fails with:
"" needs to have a value for field before this many-to-many relationship can be used.
UPDATE:
I moved raising the ParseError into a get_serializer_class() method like so:
def get_serializer_class(self):
if 'users' in self.request.DATA:
# make sure the users are allowed
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError(detail='Unpermitted user')
return serializer
And this raises the exception, however, it does not return it using the REST framework's JSON response. Rather I get the django stack trace and a 500 error, which is not good.
Thanks!
Have a look at APIView's handle_exception — this is where DRF processes exceptions raised during the request.
From the docs:
The default implementation handles any subclass of rest_framework.exceptions.APIException, as well as Django's Http404 and PermissionDenied exceptions, and returns an appropriate error response.
If you need to customize the error responses your API returns you should subclass this method.
So you need to override this to handle ParseError exceptions too.
Also check out the DRF docs on Exceptions.
I hope that helps.
When the exception is raised in the pre_save method(), post_save(), or even in a post() method for the viewclass, it was being handled correctly by Django-REST-Framework. Had I been using curl or similar, the error would have been returned correctly.
This actually is a bug in the browsable API, which is what I was using to test - sending the data using the "Raw data" form. When trying to render the html response, DRF apparently tries to capture the "context" of the post. In this case, it wanted the saved/completed post.
That did not exist, so a Django rendering error was being thrown and that confused me.
When testing using curl, the response was accurate.
Note that putting it in the get_serializer_class() like I did caused it to go outside of the DRF exception handler so Django rendered it correctly and showed the error was being thrown correctly.

How to properly handle an aborted HTTP request in Django

Whenever a lengthy HTTP requests is aborted by the client (e.g. Browser is closed) Django views seem to raise a IOError exception.
What's the proper way to detect such an aborted request if I just want to ignore them? Just catching IOError seems too wide.. might accidentally ignore other IO problems.
In django 1.3 and up, you can use a logging filter class to suppress the exceptions which you aren't interested in. Here's the logging filter class I'm using to narrowly suppress IOError exceptions raised from _get_raw_post_data():
import sys, traceback
class _SuppressUnreadablePost(object):
def filter(self, record):
_, exception, tb = sys.exc_info()
if isinstance(exception, IOError):
for _, _, function, _ in traceback.extract_tb(tb):
if function == '_get_raw_post_data':
return False
return True
In Django 1.4, you will be able to do away with most of the complexity and suppress the new exception class UnreadablePostError. (See this patch).
The best way to do it would be to use a custom middleware class that implements process_exception() to return a custom HTTP response, say a rendered errors/request_aborted.html template, if an IOException is caught.
Raven now connects itself to the got_request_exception() signal to catch unhandled exceptions, bypassing the logging system entirely, so the solution proposed by dlowe does not work anymore.
However raven looks for a skip_sentry attribute on the exception instance, so you can use a middleware to set it on the errors you want to ignore:
import sys
import traceback
class FilterPostErrorsMiddleware(object):
"""
A middleware that prevents unreadable POST errors to reach Sentry.
"""
def process_exception(self, request, exception):
if isinstance(exception, IOError):
tb = sys.exc_info()[2]
for _, _, function, _ in traceback.extract_tb(tb):
if function == '_get_raw_post_data':
exception.skip_sentry = True
break
Note: you have to use a recent version of raven (e.g. 1.8.4), as previous versions mistakenly checked for the skip_sentry attribute on the exception type rather than instance.
If you want to ignore the IOError, then just let it be. You don't need to catch it. If you absolutely must catch it, you can do what #Filip Dupanović suggested, and maybe return a django.http.HttpResponseServerError to set the response code to 500.

getting access to the exception in Django 500.html?

How can I get access to the exception details in 500.html?
Easiest way is to write a middleware that overrides process_exception.
http://docs.djangoproject.com/en/dev/topics/http/middleware/#process-exception
class ProcessExceptionMiddleware(object):
def process_exception(self, request, exception):
response = direct_to_template(request, "my_500_template", {'exception': exception})
response.status_code = 500
return response
You can subclass django.core.handlers.base.BaseHandler, or better one of the implementations like django.core.handlers.wsgi.WSGIHandler, and change the handle_uncaught_exception(self, request, resolver, exc_info) method. The last argument is the exception info as returned by sys.exc_info. In the case of WSGI, you would define the custom handler in your WSGI file, for instance.
Simply overwriting handler500 in your URLconf won't work because that function does not receive any information about the actual exception.