Skip to content

Commit 92e755b

Browse files
google-labs-jules[bot]ddalex
authored andcommitted
Fix: Prevent SerializerMethodField from becoming HiddenField in unique_together
Ensures SerializerMethodFields shadowing model fields in uniqueness constraints aren't hidden.
1 parent 33d59fe commit 92e755b

File tree

2 files changed

+100
-2
lines changed

2 files changed

+100
-2
lines changed

rest_framework/serializers.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,12 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs
14831483
hidden_fields = {}
14841484
uniqueness_extra_kwargs = {}
14851485

1486+
# Identify declared SerializerMethodFields to prevent them from becoming HiddenFields
1487+
serializer_method_field_names = {
1488+
name for name, field_instance in declared_fields.items()
1489+
if isinstance(field_instance, SerializerMethodField)
1490+
}
1491+
14861492
for unique_constraint_name in unique_constraint_names:
14871493
# Get the model field that is referred too.
14881494
unique_constraint_field = model._meta.get_field(unique_constraint_name)
@@ -1507,8 +1513,10 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs
15071513
elif default is not empty:
15081514
# The corresponding field is not present in the
15091515
# serializer. We have a default to use for it, so
1510-
# add in a hidden field that populates it.
1511-
hidden_fields[unique_constraint_name] = HiddenField(default=default)
1516+
# add in a hidden field that populates it,
1517+
# unless it's a SerializerMethodField.
1518+
if unique_constraint_name not in serializer_method_field_names:
1519+
hidden_fields[unique_constraint_name] = HiddenField(default=default)
15121520

15131521
# Update `extra_kwargs` with any new options.
15141522
for key, value in uniqueness_extra_kwargs.items():

tests/test_model_serializer.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,31 @@ class Meta:
13461346
fields = ('name',)
13471347

13481348

1349+
class UniqueTestModel(models.Model):
1350+
name = models.CharField(max_length=100)
1351+
description = models.CharField(max_length=100, null=True, blank=True)
1352+
other_field = models.CharField(max_length=100, default="default_value")
1353+
1354+
class Meta:
1355+
unique_together = [("name", "description")]
1356+
1357+
def __str__(self):
1358+
return f"{self.name} - {self.description or 'No description'}"
1359+
1360+
1361+
class UniqueTestModelSerializer(serializers.ModelSerializer):
1362+
description = serializers.SerializerMethodField()
1363+
1364+
class Meta:
1365+
model = UniqueTestModel
1366+
fields = ["name", "description", "other_field"]
1367+
1368+
def get_description(self, obj):
1369+
if obj.description:
1370+
return f"Serialized: {obj.description}"
1371+
return "Serialized: No description provided"
1372+
1373+
13491374
class Issue6110Test(TestCase):
13501375
def test_model_serializer_custom_manager(self):
13511376
instance = Issue6110ModelSerializer().create({'name': 'test_name'})
@@ -1395,3 +1420,68 @@ class Meta:
13951420
serializer.save()
13961421

13971422
self.assertEqual(instance.char_field, 'value changed by signal')
1423+
1424+
1425+
class TestSerializerMethodFieldInUniqueTogether(TestCase):
1426+
def test_serializer_method_field_not_hidden_in_unique_together(self):
1427+
"""
1428+
Tests that a SerializerMethodField named the same as a model field
1429+
in a unique_together constraint is not treated as a HiddenField and
1430+
that unique_together validation still functions correctly.
1431+
"""
1432+
serializer = UniqueTestModelSerializer()
1433+
1434+
self.assertFalse(
1435+
isinstance(serializer.fields['description'], serializers.HiddenField),
1436+
"Field 'description' should not be a HiddenField."
1437+
)
1438+
self.assertTrue(
1439+
isinstance(serializer.fields['description'], serializers.SerializerMethodField),
1440+
"Field 'description' should be a SerializerMethodField."
1441+
)
1442+
1443+
instance = UniqueTestModel.objects.create(name="TestName", description="TestDesc")
1444+
serializer_output = UniqueTestModelSerializer(instance).data
1445+
self.assertIn("description", serializer_output)
1446+
self.assertEqual(serializer_output["description"], "Serialized: TestDesc")
1447+
1448+
instance_no_desc = UniqueTestModel.objects.create(name="TestNameNoDesc")
1449+
serializer_output_no_desc = UniqueTestModelSerializer(instance_no_desc).data
1450+
self.assertEqual(serializer_output_no_desc["description"], "Serialized: No description provided")
1451+
1452+
UniqueTestModel.objects.create(name="UniqueName", description="UniqueDesc")
1453+
invalid_data = {"name": "UniqueName", "description": "UniqueDesc", "other_field": "some_value"}
1454+
serializer_invalid = UniqueTestModelSerializer(data=invalid_data)
1455+
with self.assertRaises(serializers.ValidationError) as context:
1456+
serializer_invalid.is_valid(raise_exception=True)
1457+
self.assertIn("non_field_errors", context.exception.detail)
1458+
self.assertTrue(any("unique test model with this name and description already exists" in str(err)
1459+
for err_list in context.exception.detail.values() for err in err_list))
1460+
1461+
UniqueTestModel.objects.create(name="UniqueNameNull", description=None)
1462+
invalid_data_null = {"name": "UniqueNameNull", "description": None, "other_field": "some_value"}
1463+
serializer_invalid_null = UniqueTestModelSerializer(data=invalid_data_null)
1464+
with self.assertRaises(serializers.ValidationError) as context_null:
1465+
serializer_invalid_null.is_valid(raise_exception=True)
1466+
self.assertIn("non_field_errors", context_null.exception.detail)
1467+
self.assertTrue(any("unique test model with this name and description already exists" in str(err)
1468+
for err_list in context_null.exception.detail.values() for err in err_list))
1469+
1470+
valid_data = {"name": "NewName", "description": "NewDesc", "other_field": "another_value"}
1471+
serializer_valid = UniqueTestModelSerializer(data=valid_data)
1472+
self.assertTrue(serializer_valid.is_valid(raise_exception=True))
1473+
self.assertEqual(serializer_valid.validated_data['name'], "NewName")
1474+
1475+
valid_data_no_desc = {"name": "NameOnly"}
1476+
serializer_valid_no_desc = UniqueTestModelSerializer(data=valid_data_no_desc)
1477+
self.assertTrue(serializer_valid_no_desc.is_valid(raise_exception=True))
1478+
self.assertIsNone(serializer_valid_no_desc.validated_data.get('description'))
1479+
self.assertEqual(serializer_valid_no_desc.validated_data['other_field'], "default_value")
1480+
1481+
saved_instance = serializer_valid_no_desc.save()
1482+
self.assertEqual(saved_instance.name, "NameOnly")
1483+
self.assertIsNone(saved_instance.description)
1484+
self.assertEqual(saved_instance.other_field, "default_value")
1485+
1486+
output_after_save = UniqueTestModelSerializer(saved_instance).data
1487+
self.assertEqual(output_after_save['description'], "Serialized: No description provided")

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy