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

问题描述 投票:0回答:1

我有 2 个型号:


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 方法的端点 /experience/,我可以在其中发送技能 ID 列表(技能字段、ForeignKey)。例如:

{
  "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 条记录(每个技能对应一条记录)。如果没有这样的 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”中的空值违反了非空约束 详细信息:失败行包含 (21、BOOM、XYZ 公司、2022-01-01、2022-12-31、项目 ABC 的描述、对项目 ABC 的贡献、经验总结、1、null、f、顾问、XYZ 的描述公司)。

我也尝试过使用serializers.ListField,但它似乎不太适合这个序列化器。

也尝试了方法来自这个答案,所以我的序列化器如下所示:

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."
            ]
        }
    }
}

也尝试过这里的例子无济于事。 花一些时间阅读整篇文章解释嵌套序列化的问题,但我认为这与我的问题不太相关。我想要的只是一个要在 POST 中发送的列表

老实说,我现在正陷入一个兔子洞,只是尝试将不同的部分放在一起,但我不知道 DRF 希望我如何做这些事情,而且他们的文档很糟糕并且缺乏简单的示例。

如果有人可以发布示例,但也可以提供解释,而不仅仅是解决方案,将不胜感激

django django-rest-framework django-serializer
1个回答
1
投票

根据当前关系,如果您的有效负载包含

"skills_ids": [1,2,3],
,那么您将创建
three
不同的
Experience
实例,每个实例都包含一项技能,这就是
NOT
您想要的,这是不好的做法。

相反,

many-to-many
关系更合适,将多个技能关联到
Experience
,反之亦然,从而避免数据库中出现重复值。

这也是您在

experience.skill.add(skill)
使用的语法,即如何使用这种关系将
Skill
附加到
Experience
。但是,实际上,除了让框架为您工作之外,您不需要做任何事情!

模型.py

class Skill(models.Model):
    ...


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

序列化器.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
}
© www.soinside.com 2019 - 2024. All rights reserved.