调用Serializer.create方法出现意外行为,致无关数据丢失(DRF)
问题原因及解决办法
环境依赖
Django>=4.2.7,<4.3.0 djangorestframework>=3.14.0,<3.15.0 python-dateutil>=2.8.2,<2.9.0 psycopg2-binary>=2.9.9,<3.0.0 django-crontab>=0.7.1,<0.8.0
问题分析
核心原因是Python可变对象的引用传递特性:字典属于可变类型,当你把user_data存入data字典并传入serializer.create后,User.objects.create(或其内部调用的CostumUserCreate方法)对user_data执行了原地修改操作(比如用pop()删除键、clear()清空字典),导致外部的原user_data字典同步被改变——因为它们指向内存中的同一个对象。而profile_data未被做类似修改,所以保持正常。
从代码逻辑看,虽然serializer.create只是获取数据并传递,但User.objects.create内部大概率对user_data做了原地清理操作(比如处理密码时移除password键),才会引发这个问题。
解决办法
方法1:在Serializer的create方法中使用字典副本
修改create方法,对获取到的user_data创建副本,避免原对象被修改:
def create(self, validated_data): """ Passing in the restructured validated_data to the CostumUserCreate method. """ # 创建副本,隔离原字典与内部操作 user_data = validated_data.get('user_data').copy() profile_data = validated_data.get('profile_data') new_user = User.objects.create( user_data=user_data, profile_data=profile_data ) return new_user
方法2:修正User模型的创建逻辑
如果User.objects.create(或CostumUserCreate)内部存在对user_data的原地修改,比如:
# 假设User模型create方法中有类似代码 def create(self, user_data, profile_data): password = user_data.pop('password') # 原地移除password键 # 其他业务逻辑
将其改为操作副本:
def create(self, user_data, profile_data): # 创建副本后再操作,不影响原字典 user_data_copy = user_data.copy() password = user_data_copy.pop('password') # 后续逻辑使用user_data_copy而非原user_data
方法3:测试代码中传入副本(临时方案)
如果只是测试阶段临时规避,可以在组装data时传入user_data的副本:
data['user_data'] = user_data.copy() # 传入副本,原字典不受内部操作影响
验证修改
完成上述任意一种修改后,重新运行测试,外部的user_data字典将保持初始状态,不会被清空。
内容的提问来源于stack exchange,提问作者user23500627
相关产品推荐
相关产品推荐

