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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:11:38