我有 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 希望我如何做这些事情,而且他们的文档很糟糕并且缺乏简单的示例。
如果有人可以发布示例,但也可以提供解释,而不仅仅是解决方案,将不胜感激
根据当前关系,如果您的有效负载包含
"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
}