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.
When trying the solution provided, I have the following code:
class TagSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
name = serializers.CharField(required=True)
style = serializers.JSONField(read_only=True)
def create(self, validated_data):
print(validated_data)
name = validated_data.pop("name")
tag, _ = models.Tag.objects.get_or_create(
name=name, defaults=validated_data
)
return tag
class ItemSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, required=False)
class Meta:
model = models.Item
fields = "__all__"
Which raise the following error when I challenge it using swagger:
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
Another strange fact, when creating test with APITestCase
, my tags are vanishing:
class TestAPIMissingTags(APITestCase):
fixtures = [
"core/fixtures/users.yaml"
]
payload = {
"name": "test",
"tags": [{"name": "test"}, {"name": "critical"}],
}
def setUp(self):
self.user = models.User.objects.get(username="jlandercy")
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_complete_payload_is_sent(self):
response = self.client.post("/api/core/item/", data=self.payload)
It returns a 201 without tags associated:
<Response status_code=201, "application/json">
{'id': 'f4f7a670-06d6-4bad-9979-3d0bd5bedd0c', 'tags': [], 'name': 'test'}
It seems tags are popped out and not processed which prevent me to reproduce the above issue. Leading to different behavior between Swagger and Unit Test Suite.
Thank you to Willem, it helped a lot to point out how to solve this problem.
Changing the TagSerializer
for Serializer
instead of ModelSerializer
was sufficient to make the mechanics work:
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
It can then be refactored into a Mixin to reuse with another entities having tags:
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
It must be the first inline in the MRO in order to make it works:
class ItemSerializer(TagCreationMixin, serializers.ModelSerializer):
...
If not, it raises the following error:
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
Anyway it does not solve the issue to reproduce tag creation with Unit Tests.