Django Rest Framework中ListSerializer调用save()无法保存模型的问题
解决Django Rest Framework ListSerializer批量更新无法保存数据的问题
让我们一步步拆解你遇到的问题,从代码里的关键错误开始修正:
一、核心错误分析
1. 单个Serializer的键名拼写错误
你的NoAccessDetailsSerializer的update方法里,有个非常隐蔽的错误:
instance.user_working = validated_data.get('user_working ') # 注意末尾多了一个空格!
这个空格会导致无法正确获取请求中的user_working值,相当于每次都给user_working赋值None,自然不会更新数据库中的字段。
2. ListSerializer中错误操作QuerySet而非单个实例
原来的代码里直接修改instance(整个QuerySet对象)的属性,这完全不符合逻辑:
instance.id = instance[index].id # instance是QuerySet,不是单个模型实例 instance.row_chosen = validated_data[index].get('row_chosen')
你需要获取循环中的单个实例对象,再对其进行修改。
3. 批量更新的实例匹配逻辑不合理
直接通过索引instance[index]匹配validated_data[index],会导致如果请求数据的顺序和QuerySet的顺序不一致时,更新错误的对象;而且你的示例JSON里有两个相同id的条目,这种情况下按索引匹配会重复更新同一实例,但逻辑上容易混乱。
二、修正后的代码
1. 修正单个Serializer
去掉冗余代码,修正键名错误,同时保留合理的默认值逻辑:
class NoAccessDetailsSerializer(serializers.ModelSerializer): id = serializers.IntegerField(read_only=False) # 如果需要通过用户名(如"John")而非UserProfile的主键更新user_working,添加以下字段定义 # user_working = serializers.SlugRelatedField( # queryset=UserProfile.objects.all(), # slug_field='user_name' # ) class Meta: model = NoAccessDetails list_serializer_class = NoAccessDetailsListSerializer fields = ("id", "row_chosen", "user_working") def update(self, instance, validated_data): # 保留原有值作为默认,避免未传字段时覆盖数据 instance.row_chosen = validated_data.get('row_chosen', instance.row_chosen) # 修正键名错误,仅当传入值时才更新 user_working = validated_data.get('user_working') if user_working is not None: instance.user_working = user_working instance.save() return instance
2. 重构ListSerializer的update方法
通过ID映射匹配实例,复用单个Serializer的逻辑,避免重复代码:
class NoAccessDetailsListSerializer(serializers.ListSerializer): def update(self, instance, validated_data): # 将QuerySet转换为ID:实例的字典,快速匹配对应对象 instance_map = {obj.id: obj for obj in instance} ret = [] for data in validated_data: obj_id = data.get('id') obj = instance_map.get(obj_id) if not obj: # ID不在查询集中时,可跳过或抛出错误,根据业务需求调整 continue if obj.row_chosen: # 已选中的行直接加入返回列表,不更新 ret.append(obj) else: # 复用单个Serializer的验证和更新逻辑 serializer = self.child(obj, data=data, partial=True) serializer.is_valid(raise_exception=True) updated_obj = serializer.save() ret.append(updated_obj) return ret
3. APIView中的调用示例
确保验证通过后再执行保存:
class NoAccessDetailsUpdateView(APIView): def put(self, request, *args, **kwargs): # 提取请求数据中的ID列表 list_of_id = [item['id'] for item in request.data] filtered_id_data = NoAccessDetails.objects.filter(id__in=list_of_id) serializer = NoAccessDetailsSerializer( filtered_id_data, data=request.data, many=True ) # 验证失败直接抛出异常 serializer.is_valid(raise_exception=True) updated_instances = serializer.save() return Response(serializer.data)
三、额外排查点
如果按上述修改后仍无法保存,可检查以下内容:
- 事务回滚:是否有代码开启了数据库事务并最终回滚,导致数据未提交。
- Model的save方法:是否重写了
NoAccessDetails的save方法,存在阻止保存的逻辑。 - 权限设置:当前请求用户是否有
NoAccessDetails模型的更新权限。 - 外键匹配:如果
user_working传入的是用户名而非主键,必须在Serializer中配置SlugRelatedField,否则会因外键关联失败导致隐性错误。
内容的提问来源于stack exchange,提问作者Aashay Amballi
相关产品推荐
相关产品推荐

