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
Вернуться на верх