Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework Database Error Exception Handling

Is there any way to have Django Rest Framework automatically respond with HTTP_400_STATUS's when there are database exceptions?

(IntegrityError and so on)

Example: I have a model with a unique username field and I'm trying to use a generic rest_framework.ListCreateAPIView. HTTP_400_STATUS's are normally thrown automatically if serializer validation fails but this is actually valid input, just not valid in the db. What should I do here?

like image 515
aroooo Avatar asked Oct 31 '15 10:10

aroooo


People also ask

How does Django handle db exception?

Database Exceptions are documented, check this answer to see an example of how to use them. If you are encountering this error while processing a form you should probably handle the exception when validating your form. So in case an exception is raised you redisplay the form with the appropriate error message.

How do I increase error in Django REST framework?

The generic views use the raise_exception=True flag, which means that you can override the style of validation error responses globally in your API. To do so, use a custom exception handler, as described above. By default this exception results in a response with the HTTP status code "400 Bad Request".

What is renderers in Django REST framework?

The rendering process takes the intermediate representation of template and context, and turns it into the final byte stream that can be served to the client. REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types.


3 Answers

While overriding the generic view is a completely valid solution, I think that a better solution is to make use of Django REST Frameworks's option to implement custom exception handling. You do this by creating a handler function that converts exceptions raised in your API views into response objects. To do this, all you have to do is tell Django REST Framework where your custom handler is by overriding it in the settings:

REST_FRAMEWORK = {'EXCEPTION_HANDLER':'my_project.my_app.utils.custom_exception_handler'}

Inside the file specified (my_project/my_app/utils.py in this case) you would then do something like the following:

from __future__ import unicode_literals
from django.db import IntegrityError
from rest_framework.views import Response, exception_handler
from rest_framework import status


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)

    # if there is an IntegrityError and the error response hasn't already been generated
    if isinstance(exc, IntegrityError) and not response:
        response = Response(
            {
                'message': 'It seems there is a conflict between the data you are trying to save and your current '
                           'data. Please review your entries and try again.'
            },
            status=status.HTTP_400_BAD_REQUEST
        )

    return response

As the docs say, it's worth noting "that the exception handler will only be called for responses generated by raised exceptions." (i.e. only when you do the following: serializer.is_valid(raise_exception=True)). However, this only matters if you are calling serializer.is_valid() yourself since "the generic views use the raise_exception=True flag, which means that you can override the style of validation error responses globally in your API. To do so, use a custom exception handler, as described above." Also, I just want to point out that if you wish to specify a custom IntegrityError message in a given view later on then you can always override the generic view as the other answers demonstrate and the custom exception handler will not insert the default message since response will no longer be None.

like image 182
Kal Avatar answered Oct 11 '22 01:10

Kal


to do this using rest_framework proper (with a rest framework style response):

from django.db import IntegrityError
from rest_framework import status
from rest_framework.generics import ListCreateAPIView
from rest_framework.response import Response

class MyListCreateAPIView(ListCreateAPIView):
    def create(self, request, *args, **kwargs):
        try:
            return super(ListCreateAPIView, self).create(request, *args, **kwargs)
        except IntegrityError:
            content = {'error': 'IntegrityError'}
            return Response(content, status=status.HTTP_400_BAD_REQUEST)

Here is a list of available HTTP 400 status codes

like image 22
punkrockpolly Avatar answered Oct 11 '22 01:10

punkrockpolly


You should extend ListCreateAPIView and catch the IntegrityError and handle it by returning a bad_request:

from django.views.defaults import bad_request
from rest_framework.generics import ListCreateAPIView

class MyListCreateAPIView(ListCreateAPIView):

    def create(self, request, *args, **kwargs):
        try:
            return super(ListCreateAPIView,self).create(request, *args, **kwargs)
        except IntegrityError:
            return bad_request(request)

Interestingly you could raise a SuspiciousOperation instead of returning the bad_request explicitly:

        except IntegrityError:
            from django.core.exceptions import SuspiciousOperation
            raise SuspiciousOperation

Then django will return a 400 BAD REQUEST.

like image 43
Sebastian Wozny Avatar answered Oct 11 '22 00:10

Sebastian Wozny