These are 2 models I have:
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)
I want to do something that is quite common but apparently not possible out of the box with DRF: I want to have an endpoint /experience/ with a POST method where I can send a LIST of skill ids (skill field, ForeignKey). For example:
{
"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
}
If there are Skill records in the DB with ids 1,2,3 then it will create 3 records in the experience table (one for each skill ofc) . If there's no skill with such id, then during validation it should return an error to the user informing so.
The name of the field can be either skill , skills, skill_ids... it does not matter.
This is the ExperienceSerializer I created:
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
but that gives me the error:
django.db.utils.IntegrityError: null value in column "skill_id" of relation "coody_portfolio_experience" violates not-null constraint DETAIL: Failing row contains (21, BOOM, XYZ Company, 2022-01-01, 2022-12-31, Description of Project ABC, Contributions to Project ABC, Summary of Experience, 1, null, f, Consultant, Description of XYZ Company).
I also tried using serializers.ListField but it doesn't seem to be quite the serializer for this.
Tried the approach from this answer as well, so then I had my serializer like this:
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
I modified the answer a bit from child = serializers.IntegerField, to child=SkillSerializer(), as it was giving me an error of child not being instantiated. Noticed also the use of ListField now as well.
And here is my payload in this version:
{
"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
}
which gives error 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."
]
}
}
}
Tried also this example here to no avail. Spend some time reading this entire post explaining the issue of nested serialization, but I don't think it's quite related to my issue. All I want is a list to be sent in POST
I'm honestly going into a rabbit hole now of just trying different pieces together, but I have no idea how DRF wants me to do these stuff and their documentation is awful and lacking simple examples.
If someone could post example but also with explanations and not just the solution that would be much appreciated