You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DRF通用视图重构后出现User无code属性错误的原因及解决

问题:DRF切换到通用视图后出现User对象无code属性错误

我使用Django Rest Framework(DRF)开发OTP验证视图,原基于APIView的实现可正常运行,但改用CreateAPIView通用视图后,出现'User' object has no attribute 'code'错误,且即使报错,User对象仍会被创建。


原正常运行的代码

models.py

class User(AbstractUser):
    phone_number = PhoneNumberField(blank=True)
    def __str__(self) -> str:
        return f"{self.username}"

原views.py(APIView版本)

class VerifyOTPCode(APIView):
    
    permission_classes=[AllowAny]
    
    def post(self, request):
        
        serializer = UserOTPCodeSerializer(data=request.data)
        
        if serializer.is_valid():
            user = serializer.save()
            return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors)

serializers.py

class UserOTPCodeSerializer(serializers.ModelSerializer):
    
    code = serializers.IntegerField()
    class Meta:
        model = User
        fields = ("first_name", "last_name", "email", "phone_number", "code")
    
    def validate(self, data):
        
        code, phone_number = str(data['code']), data['phone_number']
        
        if is_six_digits(code) and is_otp_approved(code, phone_number):
            return data
        
        raise serializers.ValidationError('Code was not approved')

    def create(self, validated_data):
        del validated_data['code']
        return User.objects.create(**validated_data)

重构后的代码(CreateAPIView版本)

新views.py

class VerifyOTPCode(generics.CreateAPIView):
    
    permission_classes= [AllowAny]
    
    serializer_class= UserOTPCodeSerializer

错误信息

Internal Server Error: /user/register/verify-otp/
Traceback (most recent call last):
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\fields.py", line 457, in get_attribute
    return get_attribute(instance, self.source_attrs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\fields.py", line 97, in get_attribute
    instance = getattr(instance, attr)
AttributeError: 'User' object has no attribute 'code'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\django\core\handlers\base.py", 
line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\django\views\generic\base.py", 
line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\views.py", line 
509, in dispatch
    response = self.handle_exception(exc)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\views.py", line 
469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\views.py", line 
480, in raise_uncaught_exception
    raise exc
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\views.py", line 
506, in dispatch
    response = handler(request, *args, **kwargs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\generics.py", line 190, in post
    return self.create(request, *args, **kwargs)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\mixins.py", line 20, in create
    headers = self.get_success_headers(serializer.data)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\serializers.py", line 555, in data
    ret = super().data
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\serializers.py", line 253, in data
    self._data = self.to_representation(self.instance)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\serializers.py", line 509, in to_representation
    attribute = field.get_attribute(instance)
  File "C:\Users\61403\AppData\Local\Programs\Python\Python310\lib\site-packages\rest_framework\fields.py", line 490, in get_attribute
    raise type(exc)(msg)
AttributeError: Got AttributeError when attempting to get a value for field `code` on serializer `UserOTPCodeSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.      
Original exception text was: 'User' object has no attribute 'code'.

错误原因

  1. 序列化逻辑差异:原APIView直接返回serializer.validated_data(输入验证后的数据),而CreateAPIView的默认create方法会调用serializer.data,即序列化器需要将刚创建的User实例转换为响应数据。但code是序列化器自定义的、不属于User模型的字段,序列化器尝试从User实例中获取该属性时必然报错。
  2. 用户创建时机:报错发生在User对象创建之后——CreateAPIView先执行serializer.save()创建用户,后续才尝试序列化用户生成响应,因此即使报错,用户数据已写入数据库。

修复方案

方案1:将code字段设为仅写入(推荐)

给code字段添加write_only=True参数,让序列化器在生成响应时忽略该字段,不再尝试从User实例获取:

class UserOTPCodeSerializer(serializers.ModelSerializer):
    # 添加write_only=True标记字段仅用于输入
    code = serializers.IntegerField(write_only=True)
    class Meta:
        model = User
        fields = ("first_name", "last_name", "email", "phone_number", "code")
    
    def validate(self, data):
        code, phone_number = str(data['code']), data['phone_number']
        if is_six_digits(code) and is_otp_approved(code, phone_number):
            return data
        raise serializers.ValidationError('Code was not approved')

    def create(self, validated_data):
        del validated_data['code']
        return User.objects.create(**validated_data)

方案2:重写CreateAPIView的create方法

模仿原APIView的逻辑,直接返回验证后的数据而非序列化后的用户实例:

class VerifyOTPCode(generics.CreateAPIView):
    permission_classes= [AllowAny]
    serializer_class= UserOTPCodeSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        # 返回validated_data而非serializer.data
        return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

方案3:拆分输入输出序列化器(更规范)

将用于接收输入的序列化器和返回响应的序列化器分开,输入序列化器包含code字段,输出序列化器仅返回User模型的字段:

# serializers.py
class UserCreateInputSerializer(serializers.ModelSerializer):
    code = serializers.IntegerField()
    class Meta:
        model = User
        fields = ("first_name", "last_name", "email", "phone_number", "code")
    
    def validate(self, data):
        code, phone_number = str(data['code']), data['phone_number']
        if is_six_digits(code) and is_otp_approved(code, phone_number):
            return data
        raise serializers.ValidationError('Code was not approved')

    def create(self, validated_data):
        del validated_data['code']
        return User.objects.create(**validated_data)

class UserOutputSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("first_name", "last_name", "email", "phone_number")

# views.py
class VerifyOTPCode(generics.CreateAPIView):
    permission_classes= [AllowAny]
    serializer_class= UserCreateInputSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
        output_serializer = UserOutputSerializer(user)
        return Response(output_serializer.data, status=status.HTTP_201_CREATED)

内容的提问来源于stack exchange,提问作者meg hidey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 01:40:34