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

Django REST Framework如何对嵌套序列化器数据执行更新操作

存在的问题
  • 序列化器中profile字段设置了read_only=True,前端传入的嵌套profile数据会被直接忽略,不会进入validated_data,导致update方法拿不到嵌套数据
  • ListUsersSerializer的fields列表未包含bio字段,无法对bio字段执行更新操作
  • 反向关联名使用错误:Profile外键的默认反向关联是profile_set,但update方法中调用了instance.profile.all(),会触发属性不存在报错
  • 视图继承的是基础ViewSet,没有默认实现update方法,需要手动编写更新逻辑
  • 没有处理profile数据为空、或者传入的profile数量超过已有profile数量的边界情况,直接pop(0)会触发报错
修复后的代码

序列化器部分

class UsersProfileSerializer(serializers.ModelSerializer): 
    class Meta:
        model = Profile
        fields = ['id', 'age', 'gender', 'contact', 'address', 'profilepic']
        read_only_fields = ['id']


class ListUsersSerializer(serializers.ModelSerializer):
    # 移除read_only配置,保留反向关联source
    profile = UsersProfileSerializer(many=True, source='profile_set')

    class Meta:
        model = MyUser
        # 新增bio字段到字段列表
        fields = ['id', 'username', 'email', 'is_younger', 'bio',
                  'friends', 'profile']

    def update(self, instance, validated_data):
        # 从validated_data取出嵌套的profile数据,key对应source的取值
        profiles_data = validated_data.pop('profile_set', [])
        # 更新MyUser自身字段
        instance.bio = validated_data.get('bio', instance.bio)
        instance.is_younger = validated_data.get('is_younger', instance.is_younger)
        instance.email = validated_data.get('email', instance.email)
        # 单独处理多对多的friends字段
        if 'friends' in validated_data:
            instance.friends.set(validated_data.get('friends'))
        instance.save()

        # 处理Profile嵌套更新
        existing_profiles = {p.id: p for p in instance.profile_set.all()}
        for profile_data in profiles_data:
            profile_id = profile_data.get('id')
            # 传入id且存在对应记录则更新,否则新建关联Profile
            if profile_id and profile_id in existing_profiles:
                profile = existing_profiles.pop(profile_id)
                for attr, value in profile_data.items():
                    setattr(profile, attr, value)
                profile.save()
            else:
                Profile.objects.create(user=instance, **profile_data)
        
        # 可选:如果需要删除本次请求未提交的已有Profile,打开下方注释
        # for profile in existing_profiles.values():
        #     profile.delete()

        return instance

视图部分

class CheckUserDetailsViewSet(viewsets.ViewSet):
    permission_classes = [AllowAny]
    queryset = MyUser.objects.all()

    def list(self, request):
        serializer = ListUsersSerializer(self.queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        user = generics.get_object_or_404(self.queryset, pk=pk)
        serializer = ListUsersSerializer(user)
        return Response(serializer.data)

    # 新增update方法实现更新逻辑
    def update(self, request, pk=None):
        user = generics.get_object_or_404(self.queryset, pk=pk)
        # partial=True支持部分字段更新,不需要传全量字段
        serializer = ListUsersSerializer(instance=user, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)
额外优化建议
  • 正常业务场景下一个用户仅对应一个Profile,建议将Profile的外键改为OneToOneField,不需要配置many=True,嵌套更新逻辑会更简洁
  • 生产环境不要全局配置AllowAny权限,建议根据业务场景调整权限控制规则

内容的提问来源于stack exchange,提问作者user11416368

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 16:06:01