Custom Exceptions with Django Rest Framework - django

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

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

Global Exception Handling in Django-rest-framework

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.

Over-ride DRF custom exception response

I want to send error_codes while sending the response back to the client, in case of an error!!
So, I've a form where params a and b is required. If any of the param is not POSTed, then DRF serializer sends back a response saying This field is required. I want to add the error code to the response as well for the client to identify. Different errors different error codes.
So, I wrote my own custom exception handler. It goes like this.
response = exception_handler(exc, context)
if response is not None:
error = {
'error_code': error_code, # Need to identify the error code, based on the type of fail response.
'errors': response.data
}
return Response(error, status=http_code)
return response
The problem I'm facing is that I need to identify the type of exception received, so that I can send the error_code accordingly. How can I achieve this?
REST framework's views handle various exceptions, and deal with returning appropriate error responses.
The handled exceptions are:
APIException raised inside REST framework.
Django's Http404
Django's PermissionDenied exception.
You can identify the type of exception received by status
from rest_framework import exceptions, status
response = exception_handler(exc, context)
if response is not None:
if response.status == status.HTTP_404_NOT_FOUND:
# Django's Http404
elif response.status == status.HTTP_403_FORBIDDEN:
# PermissionDenied exception
else:
# APIException raised
Also most error responses will include a key detail in the body of the response.
You can check it and set custom error codes.
response = exception_handler(exc, context)
if response is not None:
# identify the type of exception received, detail can be text, list or dictionary of items.
if response.data.detail == 'some text':
error_code = 111
elif more_conditions:
...
Reference: http://www.django-rest-framework.org/api-guide/exceptions/

Show request.DATA in Django 500 error caused by Rest Framework

I have built an API using Django Rest Framework, but I'm facing a problem with error logging, which won't displayed the original POST data sent from the AJAX call to the API. Now I know Rest Framework uses request.DATA to parse the POST data, but I'm struggling to get this data in the Django error log, which makes it hard to debug the live application.
As an example, I send the following POST call:
...
Request Payloadview source
{start:2014-07-16, end:2014-08-18}
end: "2014-08-18"
start: "2014-07-16"
And in the Django error log I get:
...
GET: No GET data
POST: No POST data
FILES: No FILES data
...
It would great if I could see in that error log which data was originally submitted via the AJAX call.
I have tried to customise the Rest Framework exception handler, but it doesn't to catch the 500 errors happening in Django.
There is a version which works for Django 1.9+ and Rest Framework 3.4
from rest_framework import permissions
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is None and context['request'].method not in permissions.SAFE_METHODS:
context['request']._request.POST = context['request'].data
return response
In settings.py :
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'core.exceptions.exception_handler',
}
The handler:
def exception_handler(exc, drf_request=None):
"""
Replace request.POST with drf_request.DATA so Sentry
can log request properly.
#param exc: Exception
#param drf_request: Request
#return: None
"""
response = views.exception_handler(exc)
if response is None and drf_request is not None \
and drf_request.method not in ['GET', 'HEAD', 'OPTIONS']:
request = get_request()
request.POST = drf_request.DATA
return response

How do I customise the default 404 response for a URL not found error using Django Rest Framework?

I want to know how to display an error page like this when there is a 404 error for a not-found resource on my Django Rest Framework API:
{
'detail': 'The resource was not found'
}
I can't find out how to do this, because most of the tutorial is just assuming that you are setting up a specific URL.
Django Rest Framework overrides Django Http404 in exception_handler so you need to write your own exception. Example:
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
default_detail = 'The resource was not found.'
def __init__(self, detail=None):
self.detail = detail or self.default_detail