DRF用户注册API提交数据触发AttributeError问题咨询
问题场景
我使用DRF开发用户注册API,采用与Django默认User模型一对一关联的Profile模型。基于User模型创建序列化器,并将Profile的字段定义为序列化器字段。提交数据时,用户和Profile已成功保存到数据库,但出现以下错误:
AttributeError at /auth/registration Got AttributeError when attempting to get a value for field address on serializer UserRegistrationSerializer. 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 'address'.
为address字段添加write_only=True参数后问题解决,但想了解错误产生的原因,以及视图为何会访问address字段,却忽略phone_number和date_of_birth字段且未报错。
相关代码
models.py
class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) date_of_birth = models.DateField(null=True) phone_number = models.CharField(null=True, max_length=30) address = models.CharField(max_length=250) def __str__(self): return f'Profile of {self.user.username}'
serializers.py
class UserRegistrationSerializer(serializers.ModelSerializer): phone_number = serializers.CharField(required=False, max_length=30) date_of_birth = serializers.DateField(required=False) address = serializers.CharField(max_length=250) password = serializers.CharField(write_only=True) password2 = serializers.CharField(write_only=True, label = 'Confirm Password') class Meta: model = User fields = ['username', 'first_name', 'last_name', 'email', 'phone_number', 'date_of_birth', 'address', 'password', 'password2'] def create(self, validated_data): phone_number = validated_data.pop('phone_number', None) date_of_birth = validated_data.pop('date_of_birth', None) address = validated_data.pop('address', None) password = validated_data.pop('password', None) first_name = validated_data.pop('first_name', None) last_name = validated_data.pop('last_name', None) user = User ( username = validated_data['username'], email = validated_data['email'], first_name = first_name, last_name = last_name ) user.set_password(password) user.save() Profile.objects.create( user=user, date_of_birth=date_of_birth, address=address, phone_number=phone_number ) return user
views.py
class UserRegistrationView(generics.CreateAPIView): serializer_class = UserRegistrationSerializer queryset = User.objects.all()
问题解析
1. 错误产生的核心原因
DRF的CreateAPIView在完成对象创建后,会默认将创建好的User实例序列化,并把序列化结果作为响应返回给客户端。你的序列化器基于User模型,但address是Profile模型的字段,User实例本身并不具备address属性,因此序列化阶段尝试从User对象中获取address值时,直接抛出了AttributeError。
2. 为什么phone_number和date_of_birth没报错?
这两个字段在序列化器中设置了required=False,且它们的默认值为None。DRF在序列化字段时,如果字段是非必填且实例没有对应属性,会自动返回None而不会触发错误;但address是必填字段(未设置required=False),DRF会强制尝试从实例中读取该字段的值,找不到对应属性就直接报错。
3. write_only=True解决问题的原理
write_only=True参数告诉DRF:该字段仅用于接收客户端提交的输入数据,不会在序列化输出(即返回响应时)包含此字段。因此序列化返回阶段不会去尝试从User实例中获取address的值,自然也就不会触发AttributeError。
更规范的实现建议
如果希望在响应中同时返回Profile的字段,更合理的做法是:
- 使用嵌套序列化器:单独编写
ProfileSerializer,然后在UserRegistrationSerializer中嵌套该序列化器,通过source关联到profile对象; - 重写to_representation方法:在
UserRegistrationSerializer中重写to_representation,手动补充Profile的字段数据; - 返回组合数据:在
create方法中返回包含User和Profile数据的字典(而非仅返回User实例),但这种方式需要调整序列化逻辑。
内容的提问来源于stack exchange,提问作者porado5146

