【问题标题】:Django REST Framework how to specify error code when raising validation error in serializerDjango REST Framework如何在序列化程序中引发验证错误时指定错误代码
【发布时间】:2016-02-02 04:50:11
【问题描述】:

我有一个允许用户注册帐户的 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。

有没有其他方法可以做到这一点?

【问题讨论】:

    标签: django django-rest-framework


    【解决方案1】:

    您可以引发不同的异常,例如:

    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)
    

    【讨论】:

    • 这里的三元运算符可能更干净,self.status_code = status_code if status_code else status.HTTP_500_INTERNAL_SERVER_ERROR
    【解决方案2】:

    使用 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
    

    【讨论】:

    • 太好了,这行得通。如果我想为不同的视图自定义异常,这个功能会变得非常复杂。随着视图数量的增加,此功能也会增加。我想知道是否有可扩展的解决方案。
    • 你的意思是覆盖'detail'?
    • 我的意思是如果有 3 个我想覆盖的异常,那么我将在 custom_exception_handler 中有一个 if..elif..elif 结构。我想定制的异常越多,我必须在这个方法中编写的 ifs 就越多。另外,我需要记住哪个 if 分支负责哪个视图的哪个 HTTP 操作。这可能是一个真正的问题。
    【解决方案3】:

    默认情况下,提升serializers.ValidationError 将返回HTTP_400_BAD_REQUEST

    但有时我们希望以正常的200 状态码返回ValidationError,因为客户端的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
    

    【讨论】:

      【解决方案4】:

      为了补充 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) 提升。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-22
        • 2020-05-23
        • 2019-05-23
        • 2020-12-09
        • 2020-08-02
        • 2016-05-07
        • 2015-01-12
        相关资源
        最近更新 更多