Fix: Prevent SerializerMethodField from becoming HiddenField in unique_together

Ensures SerializerMethodFields shadowing model fields in uniqueness constraints aren't hidden.
This commit is contained in:
google-labs-jules[bot] 2025-05-24 08:48:39 +00:00 committed by ddalex
parent 33d59fefaa
commit 92e755b956
2 changed files with 100 additions and 2 deletions

View File

@ -1483,6 +1483,12 @@ class ModelSerializer(Serializer):
hidden_fields = {}
uniqueness_extra_kwargs = {}
# Identify declared SerializerMethodFields to prevent them from becoming HiddenFields
serializer_method_field_names = {
name for name, field_instance in declared_fields.items()
if isinstance(field_instance, SerializerMethodField)
}
for unique_constraint_name in unique_constraint_names:
# Get the model field that is referred too.
unique_constraint_field = model._meta.get_field(unique_constraint_name)
@ -1507,7 +1513,9 @@ class ModelSerializer(Serializer):
elif default is not empty:
# The corresponding field is not present in the
# serializer. We have a default to use for it, so
# add in a hidden field that populates it.
# add in a hidden field that populates it,
# unless it's a SerializerMethodField.
if unique_constraint_name not in serializer_method_field_names:
hidden_fields[unique_constraint_name] = HiddenField(default=default)
# Update `extra_kwargs` with any new options.

View File

@ -1346,6 +1346,31 @@ class Issue6110ModelSerializer(serializers.ModelSerializer):
fields = ('name',)
class UniqueTestModel(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=100, null=True, blank=True)
other_field = models.CharField(max_length=100, default="default_value")
class Meta:
unique_together = [("name", "description")]
def __str__(self):
return f"{self.name} - {self.description or 'No description'}"
class UniqueTestModelSerializer(serializers.ModelSerializer):
description = serializers.SerializerMethodField()
class Meta:
model = UniqueTestModel
fields = ["name", "description", "other_field"]
def get_description(self, obj):
if obj.description:
return f"Serialized: {obj.description}"
return "Serialized: No description provided"
class Issue6110Test(TestCase):
def test_model_serializer_custom_manager(self):
instance = Issue6110ModelSerializer().create({'name': 'test_name'})
@ -1395,3 +1420,68 @@ class Issue6751Test(TestCase):
serializer.save()
self.assertEqual(instance.char_field, 'value changed by signal')
class TestSerializerMethodFieldInUniqueTogether(TestCase):
def test_serializer_method_field_not_hidden_in_unique_together(self):
"""
Tests that a SerializerMethodField named the same as a model field
in a unique_together constraint is not treated as a HiddenField and
that unique_together validation still functions correctly.
"""
serializer = UniqueTestModelSerializer()
self.assertFalse(
isinstance(serializer.fields['description'], serializers.HiddenField),
"Field 'description' should not be a HiddenField."
)
self.assertTrue(
isinstance(serializer.fields['description'], serializers.SerializerMethodField),
"Field 'description' should be a SerializerMethodField."
)
instance = UniqueTestModel.objects.create(name="TestName", description="TestDesc")
serializer_output = UniqueTestModelSerializer(instance).data
self.assertIn("description", serializer_output)
self.assertEqual(serializer_output["description"], "Serialized: TestDesc")
instance_no_desc = UniqueTestModel.objects.create(name="TestNameNoDesc")
serializer_output_no_desc = UniqueTestModelSerializer(instance_no_desc).data
self.assertEqual(serializer_output_no_desc["description"], "Serialized: No description provided")
UniqueTestModel.objects.create(name="UniqueName", description="UniqueDesc")
invalid_data = {"name": "UniqueName", "description": "UniqueDesc", "other_field": "some_value"}
serializer_invalid = UniqueTestModelSerializer(data=invalid_data)
with self.assertRaises(serializers.ValidationError) as context:
serializer_invalid.is_valid(raise_exception=True)
self.assertIn("non_field_errors", context.exception.detail)
self.assertTrue(any("unique test model with this name and description already exists" in str(err)
for err_list in context.exception.detail.values() for err in err_list))
UniqueTestModel.objects.create(name="UniqueNameNull", description=None)
invalid_data_null = {"name": "UniqueNameNull", "description": None, "other_field": "some_value"}
serializer_invalid_null = UniqueTestModelSerializer(data=invalid_data_null)
with self.assertRaises(serializers.ValidationError) as context_null:
serializer_invalid_null.is_valid(raise_exception=True)
self.assertIn("non_field_errors", context_null.exception.detail)
self.assertTrue(any("unique test model with this name and description already exists" in str(err)
for err_list in context_null.exception.detail.values() for err in err_list))
valid_data = {"name": "NewName", "description": "NewDesc", "other_field": "another_value"}
serializer_valid = UniqueTestModelSerializer(data=valid_data)
self.assertTrue(serializer_valid.is_valid(raise_exception=True))
self.assertEqual(serializer_valid.validated_data['name'], "NewName")
valid_data_no_desc = {"name": "NameOnly"}
serializer_valid_no_desc = UniqueTestModelSerializer(data=valid_data_no_desc)
self.assertTrue(serializer_valid_no_desc.is_valid(raise_exception=True))
self.assertIsNone(serializer_valid_no_desc.validated_data.get('description'))
self.assertEqual(serializer_valid_no_desc.validated_data['other_field'], "default_value")
saved_instance = serializer_valid_no_desc.save()
self.assertEqual(saved_instance.name, "NameOnly")
self.assertIsNone(saved_instance.description)
self.assertEqual(saved_instance.other_field, "default_value")
output_after_save = UniqueTestModelSerializer(saved_instance).data
self.assertEqual(output_after_save['description'], "Serialized: No description provided")