Django REST Framework如何在序列化程序中引发验证错误时指定错误代码

时间:2015-11-02 10:19:52

标签: django django-rest-framework

我有一个API端点,允许用户注册帐户。我想为重复的用户名返回HTTP 409而不是400.

这是我的序列化器:

from django.contrib.auth.models import User
from rest_framework.serializers import ModelSerializer

class UserSerializer(ModelSerializer):
    username = CharField()

    def validate_username(self, value):
        if User.objects.filter(username=value).exists():
            raise NameDuplicationError()
        return value


class NameDuplicationError(APIException):
    status_code = status.HTTP_409_CONFLICT
    default_detail = u'Duplicate Username'

触发错误时,响应为:{"detail":"Duplicate Username"}。我意识到如果我将APIException子类化,则使用键detail而不是username

我希望此回复{"username":"Duplicate Username"}

或者我想在引发ValidationError时指定状态代码:

def validate_username(self, value):
    if User.objects.filter(username=value).exists():
        raise serializers.ValidationError('Duplicate Username', 
                                          status_code=status.HTTP_409_CONFLICT)
    return value

但这不起作用,因为ValidationError只返回400。

还有其他方法可以实现这个目标吗?

4 个答案:

答案 0 :(得分:21)

您可以提出不同的例外情况,例如:

from rest_framework.exceptions import APIException
from django.utils.encoding import force_text
from rest_framework import status


class CustomValidation(APIException):
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    default_detail = 'A server error occurred.'

    def __init__(self, detail, field, status_code):
        if status_code is not None:self.status_code = status_code
        if detail is not None:
            self.detail = {field: force_text(detail)}
        else: self.detail = {'detail': force_text(self.default_detail)}

您可以在序列化程序中使用它,例如:

raise CustomValidation('Duplicate Username','username', status_code=status.HTTP_409_CONFLICT)

raise CustomValidation('Access denied','username', status_code=status.HTTP_403_FORBIDDEN)

答案 1 :(得分:3)

使用django-rest-framework自定义异常处理程序http://www.django-rest-framework.org/api-guide/exceptions/

def custom_exception_handler(exc, context=None):
    response = exception_handler(exc, context)
    if response is not None:
         if response.data['detail'] == 'Duplicate Username':
            response.data['username'] = response.data.pop('detail')
        response.status_code = status.HTTP_409_CONFLICT
    return response

答案 2 :(得分:1)

默认情况下,加注serializers.ValidationError将返回HTTP_400_BAD_REQUEST

但是有时候我们想返回带有正常ValidationError状态代码的200,因为客户端的some库无法在响应代码时解析json响应数据不是200

我尝试了这个。但这不起作用:

raise serializers.ValidationError({'message':'Invalid  email address'}, code=200)

所以我们可以做到这一点,并且有效:

res = serializers.ValidationError({'message':'Invalid  email address'})
res.status_code = 200
raise res

答案 3 :(得分:0)

为了补充 Anush Devendra 的回答,似乎提出除 ValidationError 以外的任何内容都将绕过 DRF 在其他领域所做的处理。

考虑来自 exceptions.py 中 DRF 的这段代码:

def to_internal_value(self, data):
        [...]

        for field in fields:
            [...]
            try:
                validated_value = field.run_validation(primitive_value)
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            [...]
            else:
                set_value(ret, field.source_attrs, validated_value)

        if errors:
            raise ValidationError(errors)

        return ret

如果你想得到这样的答案:

{
    "my_first_field": [
        "The first field had an error."
    ],
    "my_second_field": [
        "The second field had an error."
    ],
}

您需要在 validate_() 方法中引发 ValidationError

请注意,这样做您将无法从 ValidationError 继承自定义错误并且 status_code 与 400 不同。您的 detail 消息将被提取并生成一个新的ValidationError(默认为 400 status_code)加注。