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

DRF中如何修改serializer create()方法关联嵌套已有用户对象

Django REST Framework 关联已存在用户创建群组问题修复

问题场景

应用中Group(群组)由User(用户)创建,预期逻辑为:创建Group对象时,仅允许将已存在的User设置为群组owner(所有者),关联通过和数据库主键id相互独立的user_id字段完成,但实际发送POST请求时持续触发校验报错。

原有实现代码

初始序列化器

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["user_id", "username"]


class GroupSerializer(serializers.ModelSerializer):
    owner = UserSerializer()

    class Meta:
        model = Group
        fields = ["name", "picture_url", "owner", "members"]

    def create(self, validated_data):
        owner_data = validated_data.pop("owner")
        user = User.objects.filter(user_id=owner_data.get("user_id"))
        instance = Group.objects.create(**validated_data)
        instance.owner = user
        instance.save
        return instance

视图POST方法

def post(self, request):
    new_group = request.data
    new_group["members"] = []
    serializer = self.serializer_class(data=new_group)
    serializer.is_valid(raise_exception=True)
    serializer.save()

    return Response(serializer.data, status=status.HTTP_201_CREATED)

模型定义

class User(models.Model):
    id = models.BigAutoField(primary_key=True, unique=True)
    user_id = models.BigIntegerField(unique=True)
    username = models.CharField(max_length=100)


class Group(models.Model):
    name = models.CharField(max_length=255, unique=True)
    picture_url = models.CharField(max_length=255)
    owner = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name="owned_groups",
        blank=True,
    )
    members = models.ManyToManyField(User, related_name="groups", blank=True)

测试请求与报错

发送的POST请求体:

{
    "owner": {
        "user_id":6
    },
    "picture_url":"nothing",
    "name":"jazz club"
}

接口返回报错:

{
    "owner": {
        "user_id": [
            "user with this user id already exists."
        ],
        "username": [
            "This field is required."
        ]
    }
}

报错根因

  • 嵌套的UserSerializer默认走嵌套对象创建/更新逻辑,会对传入的user_id做全局唯一性校验,传入已存在的user_id时直接抛出「user id已存在」错误
  • 嵌套的UserSerializer默认要求fields列表内所有字段(user_id、username)为必传项,未传username时触发必填校验错误
  • 原有create方法存在两处代码bug:
    • User.objects.filter()返回的是QuerySet集合,不是单个User实例,直接赋值给外键字段会触发类型错误
    • instance.save未添加调用括号,实际不会执行数据库保存操作

修复方案

无需修改模型和视图代码,仅调整序列化器逻辑即可:

  • 自定义关联字段,关闭嵌套序列化器的新建用户逻辑,仅校验传入的user_id对应的用户是否存在
  • 修复原有create方法的代码bug
  • 保证返回数据格式和原有预期一致,返回owner完整信息而非仅用户ID

修改后的完整序列化器代码:

from rest_framework import serializers
from .models import User, Group

# 读取用户详情时使用,返回user_id和username
class UserReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["user_id", "username"]

# 自定义owner关联字段:写入时接收user_id,校验对应用户是否存在;读取时返回完整用户信息
class ExistingUserByUserIdField(serializers.Field):
    def to_internal_value(self, data):
        # 写入逻辑:根据传入的user_id查找已存在的用户
        try:
            return User.objects.get(user_id=data)
        except User.DoesNotExist:
            raise serializers.ValidationError("指定user_id对应的用户不存在")
        except (TypeError, ValueError):
            raise serializers.ValidationError("user_id必须为整数")

    def to_representation(self, value):
        # 读取逻辑:返回用户的完整序列化结果
        return UserReadSerializer(value).data


class GroupSerializer(serializers.ModelSerializer):
    owner = ExistingUserByUserIdField()

    class Meta:
        model = Group
        fields = ["name", "picture_url", "owner", "members"]

    def create(self, validated_data):
        # 校验通过后,validated_data中的owner已经是查询到的User实例,可直接创建
        instance = Group.objects.create(**validated_data)
        # 初始化空成员列表
        instance.members.set([])
        return instance

效果验证

发送和之前完全一致的POST请求,可正常创建群组,返回结果格式符合预期:

{
    "name": "jazz club",
    "picture_url": "nothing",
    "owner": {
        "user_id": 6,
        "username": "该user_id对应的实际用户名"
    },
    "members": []
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 17:12:28