Home > Software engineering >  Django ModelViewSet returning full HTML error page instead of JSON response
Django ModelViewSet returning full HTML error page instead of JSON response

Time:10-01

I have been trying to fix this all morning but can't seem to find the issue. I have a specific API returning an IntegrityError duplicate key error in the form of the Django HTML error traceback instead of returning a detail error (JSON) on the form field.

I expect an error response to be in JSON form such that I can update the form using error.response.data but instead it is returning the classic Django error page with this snippet:

Exception Type: IntegrityError at /api/chats/ Exception Value: duplicate key value violates unique constraint "chats_chat_title_853c3234_uniq" DETAIL: Key (title)=(New Chat) already exists.

Model with a title field set to unique:

class Chat(TimestampedModel):
    """
    A chat between multiple users.
    """
    uuid = models.UUIDField(default=uuid4, null=False)
    title = models.CharField(
        max_length=255, null=False, unique=True, blank=False)

Serializer for the chat:

class ChatSerializer(serializers.ModelSerializer):
    title = serializers.CharField(max_length=255)

    def create(self, validated_data):
        """
        Creates a new Chat and adds the m2m employees to it
        """
        user = self.context['request'].user
        title = validated_data['title']

        # Add the user to the chat
        employee_ids = validated_data.pop("employee_ids")
        employee_ids.append(user.id)

        # Create and save the chat
        # Add the employees to the chat
        # Add the sender to the chat
        chat = Chat.objects.create(created_by=user, title=title)
        chat.employees.set(employee_ids)
        chat.employees.add(user)
        chat.save()
        return chat

And the ViewSet:

class ChatViewSet(MixedPermissionModelViewSet):
    lookup_field = 'uuid'
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer
    permission_classes_by_action = {
        'list': [IsAuthenticated],
        'create': [IsAuthenticated],
        'update': [IsAuthenticated],
        'retrieve': [IsAuthenticated],
        'partial_update': [IsAuthenticated],
        'destroy': [IsAuthenticated]
    }

    def add_user_has_viewed(self, chat):
        if self.request.user in chat.employees.all():
            chat.has_viewed.add(self.request.user)
            chat.save()
        return chat

    def perform_create(self, serializer):
        chat = serializer.save()
        self.add_user_has_viewed(chat)

It is specific to this API during creation of an object. What am I missing?

CodePudding user response:

This probably happens because the IntegrityError exception is rised at the ORM level and is not handled by Django Rest Framework's default exception handler. The best way I can think to fix this without rewriting the insides of ViewSet is to define a custom exception handler as described here.

from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.db import IntegrityError


def integrity_error_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if isinstance(exc, IntegrityError) and not response:
        response = Response({'detail': 'Your error message'}, status=status.HTTP_400_BAD_REQUEST)

    return response

Then add this handler in settings.py

REST_FRAMEWORK = {
...
'EXCEPTION_HANDLER': 'utils.exceptions.integrity_error_exception_handler'
}

Also, you can use exc and context params to acces the original exception and view object in order to return more generic responses.

  • Related