[Fixed]-Django Rest Framework Database Error Exception Handling


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):
            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.


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.'

    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.



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):
            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


This is how I solve this problem for a CreateView

class MyModelCreate(CreateView):

    model = MyModel
    fields = '__all__'
    success_url = reverse_lazy('MyModels')  # In case of success the user will be redirect to the listview. This is defined on your urls.py

    def form_valid(self, form):        
            return super(MyModelCreate, self).form_valid(form)
        except IntegrityError as e:
            messages.error(self.request, 'Integrity error. The name already exists. ' + str(e)) # You don't need to use the str(e). It is just for you to know what is going on.
            return HttpResponseRedirect(reverse('MyModel_create',))

On your template, all you have to do is add the following lines

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
{% endif %}


Leave a comment