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'.
错误原因
- 序列化逻辑差异:原
APIView直接返回serializer.validated_data(输入验证后的数据),而CreateAPIView的默认create方法会调用serializer.data,即序列化器需要将刚创建的User实例转换为响应数据。但code是序列化器自定义的、不属于User模型的字段,序列化器尝试从User实例中获取该属性时必然报错。 - 用户创建时机:报错发生在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
相关产品推荐
相关产品推荐

