Shakil
Shakil

Reputation: 4630

Customize Status code response from Django Rest Framework serializer

The scenario is quite straight-forward:

Say i have a user model where email should be unique. I did a custom validation for this like.

def validate_email(self, value):
    if value is not None:
        exist_email = User.objects.filter(email=value).first()
        if exist_email:
            raise serializers.ValidationError("This Email is already taken")
    return value

from rest_framework response when input validation occur we should return status_code_400 for BAD_REQUEST but in this scenario we should or we need to return status_code_409 for conflicting entry. What is the best way to customize status_code response from serializer_errors validation.

Upvotes: 4

Views: 6400

Answers (3)

John Gilmore
John Gilmore

Reputation: 456

Short answer:

You can't return custom response codes from a serializer.

This is because the serializer is just that: A Serializer. It doesn't, or shouldn't, deal with HTTP at all. It's just for formatting data, usually as JSON, but it'll usually do HTML for showing your API, and one or two other formats.

Long answer:

One way to accomplish this is to raise something (doesn't matter what, but make it descriptive) in your serializer, and add code to your view to catch the error. Your view can return a custom response code with a custom response body as you like it.

Like this:

add something like this to your view class:

def create(self, request, *args, **kwargs):
   try:
       return super().create(request, *args, **kwargs)
   except ValidationError as x:
       return Response(x.args, status=status.HTTP_409_CONFLICT)

Upvotes: 2

Mau
Mau

Reputation: 166

I think is better to define custom exception_handler like:

settings.py

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'myproject.common.custom_classes.handler.exception_handler',
}

handler.py

def exception_handler(exc, context):
    # Custom exception hanfling
    if isinstance(exc, UniqueEmailException):
        set_rollback()
        data = {'detail': exc.detail}
        return Response(data, status=exc.status_code)

    elif isinstance(exc, (exceptions.APIException, ValidationError)):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if hasattr(exc, 'error_dict') and isinstance(exc, ValidationError):
            exc.status_code = HTTP_400_BAD_REQUEST
            data = exc.message_dict
        elif isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    elif isinstance(exc, Http404):
        msg = _('Not found.')
        data = {'detail': six.text_type(msg)}

        set_rollback()
        return Response(data, status=status.HTTP_404_NOT_FOUND)

    return None

exceptions.py

class UniqueEmailException(APIException):
    status_code = status.HTTP_409_CONFLICT
    default_detail = 'Error Message'

And finally the validator:

def validate_email(self, value):
    if value is not None:
        exist_email = User.objects.filter(email=value).first()
        if exist_email:
            raise UniqueEmailException()
    return value

Upvotes: 2

robertu
robertu

Reputation: 116

I would go for intercepting ValidationError exception and return the Response object with 409 status code:

try:
    serializer.is_valid(raise_exception=True)
except ValidationError, msg:
    if str(msg) == "This Email is already taken":
        return Response(
            {'ValidationError': str(msg)},
            status=status.HTTP_409_CONFLICT
        )
    return Response(
        {'ValidationError': str(msg)},
        status=status.HTTP_400_BAD_REQUEST
    )

Upvotes: 2

Related Questions