Reputation: 4630
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
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
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
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