Django DRF, create an Item with associated tags whether they exist or not
I have the following create function for ItemSerializer
class. It aims to create new Item with tags, creating tags on the fly if they does not exist or getting them if they exist.
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = models.Tag
fields = ("id", "name", "style")
class ItemSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, required=False)
class Meta:
model = models.Item
fields = "__all__"
def create(self, validated_data):
print(validated_data)
tags = validated_data.pop("tags", [])
item = models.Item.objects.create(**validated_data)
for tag in tags:
current_tag, _ = models.Tag.objects.get_or_create(**tag)
item.tags.add(current_tag)
return item
Anyway when I perform a POST on the Item with already existing tag:
{
"tags": [{"name": "My Tag"}],
"name": "My Item"
}
I get the following DRF 400 answer:
{
"tags": [
{
"name": [
"tag with this name already exists."
]
}
]
}
It seems in this case my create
function is skipped and Django DRF tries to create the tag anyway.
The main reason this happens is because when you create something with a serializer, and there is a sub-serializer, it will also try to create items for the sub-serialzer(s) first.
Since the name of the Tag
is unique, it will thus not start creating any tags, because the constraint fails, and since it can not create (all) of the specified Tag
, so does the Item
.
Perhaps a bit of a "un-elegant" solution might be to make a serializer that does not enforces that the name is unique, and then creates or retrieves the corresponding Tag
, like:
class TagSerializer(serializers.Serializer):
id = models.IntegerField(read_only=True)
name = models.CharField(required=True)
style = models.CharField(read_only=True)
def create(self, validated_data):
name = validated_data.pop(name)
tag, __ = Tag.objects.get_or_create(
name=validated_data['name'], defaults=validated_data
)
return tag
class ItemSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, required=False)
class Meta:
model = models.Item
fields = '__all__'
# no create