4

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.

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

Comments

2

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
    )

Comments

2

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.