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
相关产品推荐
相关产品推荐

