django 在DRF中,如何创建一个POST序列化程序,在其中可以添加外键字段的多个值

von4xj4u  于 2023-06-07  发布在  Go
关注(0)|答案(1)|浏览(192)

这是我的两个模型:

class Skill(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name + " - ID: " + str(self.id)

class Experience(models.Model):
    consultant = models.ForeignKey("Consultant", related_name="experience", on_delete=models.CASCADE)
    project_name = models.CharField(max_length=100)
    company = models.CharField(max_length=100)
    company_description = models.TextField(null=True, blank=True)
    from_date = models.DateField()
    to_date = models.DateField()
    project_description = models.CharField(max_length=100)
    contribution = models.TextField()
    summary = models.TextField()
    is_pinned = models.BooleanField(default=False)
    role = models.CharField(max_length=100, null=True)
    skill = models.ForeignKey("Skill", related_name="experience", on_delete=models.CASCADE)

我想用DRF做一些很常见但显然不可能开箱即用的事情:我想有一个端点/经验/与POST方法,我可以发送技能ID列表(技能字段,外键)。例如:

{
  "project_name": "Project AVC",
  "company": "XYZ Company",
  "company_description": "Description of XYZ Company",
  "from_date": "2022-01-01",
  "to_date": "2022-12-31",
  "project_description": "Description of Project ABC",
  "contribution": "Contributions to Project ABC",
  "summary": "Summary of Experience",
  "is_pinned": false,
  "role": "Consultant",
  "skills_ids": [1,2,3],
  "consultant": 1
}

如果数据库中有ID为1,2,3的技能记录,则它将在经验表中创建3条记录(c的每个技能一条)。如果没有这样的id的技能,那么在验证过程中,它应该返回一个错误给用户,通知用户。
字段的名称可以是skill、skills、skill_ids...没关系。
这是我创建的ExperienceSerializer:

class ExperienceSerializer(serializers.ModelSerializer):
    skills = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=Skill.objects.all(),
        write_only=True
    )

    class Meta:
        model = Experience
        exclude = ['skill']

    def create(self, validated_data):
        skills_data = validated_data.pop('skills', [])
        experience = Experience.objects.create(**validated_data)

        for skill in skills_data:
            experience.skill.add(skill)

        return experience

但这给了我一个错误

django.db.utils.IntegrityError:关系“coody_portfolio_experience”的列“skill_id”中的空值违反了非空约束DETAIL:失败行包含(21,BOOM,XYZ公司,2022-01-01,2022-12-31,项目ABC的描述,对项目ABC的贡献,经验总结,1,null,f,顾问,XYZ公司的描述)。

我还尝试使用serializers.ListField,但它似乎不太适合这个序列化器。
也尝试了from this answer方法,所以我的序列化器如下所示:

class ExperienceSerializer(serializers.ModelSerializer):
    skill_ids = serializers.ListField(
        child=SkillSerializer(),
        write_only=True
    )

    class Meta:
        model = Experience
        fields = (
            'consultant',
            'project_name',
            'company',
            'company_description',
            'from_date',
            'to_date',
            'project_description',
            'contribution',
            'summary',
            'is_pinned',
            'role',
            'skill',
            'skill_ids'
        )

    def create(self, validated_data):
        skill_ids = validated_data.pop('skill_ids')
        experience = Experience.objects.create(**validated_data)
        experience.set(skill_ids)

        return experience

我将答案从child = serializers.IntegerField修改为child=SkillSerializer(),因为它给了我一个child未被示例化的错误。现在还注意到ListField的使用。
这是我在这个版本中的有效载荷:

{
 "project_name": "BOOM",
 "company": "XYZ Company",
 "company_description": "Description of XYZ Company",
 "from_date": "2022-01-01",
 "to_date": "2022-12-31",
 "project_description": "Description of Project ABC",
 "contribution": "Contributions to Project ABC",
 "summary": "Summary of Experience",
 "is_pinned": false,
 "role": "Consultant",
 "skill_ids": [3, 4,2,1],
   "consultant": 1
}

错误400:

{
    "skill": [
        "This field is required."
    ],
    "skill_ids": {
        "0": {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        },
        "1": {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        },
        "2": {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        },
        "3": {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        }
    }
}

也试过this example here,但没有用。花些时间阅读这篇解释嵌套序列化问题的整篇文章,但我不认为它与我的问题有很大关系。所有我想要的是一个列表被发送在邮政
老实说,我现在正在进入一个兔子洞,只是尝试不同的部分在一起,但我不知道如何DRF希望我做这些事情,他们的文档是可怕的,缺乏简单的例子。
如果有人可以张贴的例子,但也与解释,而不仅仅是解决方案,将不胜感激

wnavrhmk

wnavrhmk1#

使用当前关系,如果你的payload包含"skills_ids": [1,2,3],,那么你将创建three不同的Experience示例,每个示例包含一个技能,这是你想要的**NOT**,这是不好的做法。
相反,many-to-many关系更合适,将多个技能关联到Experience,反之亦然,从而避免数据库中的重复值。
这也是您在experience.skill.add(skill)上使用的语法,这就是您如何使用这种关系将Skill附加到Experience。但是,实际上,除了让框架为您工作之外,您不需要做任何事情!
models.py

class Skill(models.Model):
    ...

class Experience(models.Model):
    ...
    skills = models.ManyToManyField(Skill)

serializers.py

class ExperienceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Experience
        fields = '__all__'

有效载荷

{
  "project_name": "Project AVC",
  "company": "XYZ Company",
  "company_description": "Description of XYZ Company",
  "from_date": "2022-01-01",
  "to_date": "2022-12-31",
  "project_description": "Description of Project ABC",
  "contribution": "Contributions to Project ABC",
  "summary": "Summary of Experience",
  "is_pinned": false,
  "role": "Consultant",
  "skills": [1,2,3],
  "consultant": 1
}

相关问题