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
В любом случае воспроизведение создания тега с помощью модульных тестов не решает проблему.