Django DRF, создайте элемент с соответствующими тегами, независимо от того, существуют они или нет

У меня есть следующая функция create для класса ItemSerializer. Она предназначена для создания нового элемента с тегами, создания тегов на лету, если они не существуют, или получения их, если они существуют.

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

В любом случае, когда я выполняю публикацию по элементу с уже существующим тегом:

 {
    "tags": [{"name": "My Tag"}],
    "name": "My Item"
 }

Я получаю следующий ответ DRF 400:

{
  "tags": [
    {
      "name": [
        "tag with this name already exists."
      ]
    }
  ]
}

Похоже, в этом случае моя функция create пропущена, и Django DRF все равно пытается создать тег.

Основная причина, по которой это происходит, заключается в том, что когда вы создаете что-то с помощью сериализатора, и есть подсериализатор, он также попытается создать сначала элементы для подсериализатора(ов).

Поскольку имя Tag уникально, оно, таким образом, не начнет создавать какие-либо теги, поскольку ограничение не выполняется, и поскольку оно не может создать (все) указанные Tag, то же самое делает и Item.

Возможно, немного "неэлегантным" решением может быть создание сериализатора, который выполняет , а не , обеспечивает уникальность имени, а затем создает или извлекает соответствующий Tag, например:

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

Спасибо Виллему, он очень помог, указав, как решить эту проблему.

Изменения TagSerializer на Serializer вместо ModelSerializer было достаточно, чтобы заставить механику работать:

class TagSerializer(serializers.Serializer):

    id = serializers.UUIDField(read_only=True)
    name = serializers.CharField(required=True)
    style = serializers.JSONField(read_only=True)

class ItemSerializer(serializers.ModelSerializer):

    tags = TagSerializer(many=True, required=False)

    class Meta:
        model = models.Item
        fields = "__all__"

    def create(self, validated_data):
        tags = validated_data.pop("tags", [])
        failure_mode = models.Item.objects.create(**validated_data)
        for tag in tags:
            current_tag, _ = models.Tag.objects.get_or_create(**tag)
            failure_mode.tags.add(current_tag)
        return failure_mode

Затем он может быть преобразован в смесь для повторного использования с другими объектами, имеющими теги:

class TagCreationMixin:

    def create(self, validated_data):
        tags = validated_data.pop("tags", [])
        item= self.Meta.model.objects.create(**validated_data)
        for tag in tags:
            current_tag, _ = models.Tag.objects.get_or_create(**tag)
            item.tags.add(current_tag)
        return item

Он должен быть первым встроенным в MRO, чтобы он работал:

class ItemSerializer(TagCreationMixin, serializers.ModelSerializer):
    ...

Если нет, то возникает следующая ошибка:

AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `core.serializers.ItemSerializer`, or set `read_only=True` on nested serializer fields.
[18/May/2025 09:04:01] "POST /api/core/item/ HTTP/1.1" 500 7765

В любом случае воспроизведение создания тега с помощью модульных тестов не решает проблему.

Вернуться на верх