From 053eb1e213c7369cab4de683d7128a90b7a10089 Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Tue, 12 Aug 2025 17:08:28 +0200 Subject: [PATCH 1/2] Condition of UniqueValidator can be read-only We can't always expect to find the value of the condition in the serializer if the field is read-only. --- rest_framework/validators.py | 7 ++++++- tests/test_validators.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 4c444cf01e..76d2a2159f 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -189,7 +189,12 @@ def __call__(self, attrs, serializer): ] condition_sources = (serializer.fields[field_name].source for field_name in self.condition_fields) - condition_kwargs = {source: attrs[source] for source in condition_sources} + condition_kwargs = { + source: attrs[source] + if source in attrs + else getattr(serializer.instance, source) + for source in condition_sources + } if checked_values and None not in checked_values and qs_exists_with_condition(queryset, self.condition, condition_kwargs): field_names = ', '.join(self.fields) message = self.message.format(field_names=field_names) diff --git a/tests/test_validators.py b/tests/test_validators.py index c594eecbe5..15f30f1c05 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -589,6 +589,21 @@ class Meta: ] +class UniqueConstraintReadOnlyFieldModel(models.Model): + state = models.CharField(max_length=100, default="new") + position = models.IntegerField() + something = models.IntegerField() + + class Meta: + constraints = [ + models.UniqueConstraint( + name="unique_constraint_%(class)s", + fields=("position", "something"), + condition=models.Q(state="new"), + ), + ] + + class UniqueConstraintNullableModel(models.Model): title = models.CharField(max_length=100) age = models.IntegerField(null=True) @@ -738,6 +753,23 @@ class Meta: ) assert serializer.is_valid() + def test_uniq_constraint_condition_read_only(self): + class UniqueConstraintReadOnlyFieldModelSerializer(serializers.ModelSerializer): + class Meta: + model = UniqueConstraintReadOnlyFieldModel + read_only_fields = ("state",) + fields = ("position", "something", *read_only_fields) + + serializer = UniqueConstraintReadOnlyFieldModelSerializer( + data={"position": 1, "something": 1} + ) + assert serializer.is_valid() + UniqueConstraintReadOnlyFieldModel.objects.create(position=1, something=1) + serializer = UniqueConstraintReadOnlyFieldModelSerializer( + data={"position": 1, "something": 1} + ) + assert not serializer.is_valid() + # Tests for `UniqueForDateValidator` # ---------------------------------- From 9430c4982a6b556ff6c94ff357af9db540a6d9eb Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Tue, 12 Aug 2025 18:30:38 +0200 Subject: [PATCH 2/2] Reproducible test --- tests/test_validators.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_validators.py b/tests/test_validators.py index 15f30f1c05..ea5bf3a4dd 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -753,22 +753,30 @@ class Meta: ) assert serializer.is_valid() - def test_uniq_constraint_condition_read_only(self): + def test_uniq_constraint_condition_read_only_create(self): class UniqueConstraintReadOnlyFieldModelSerializer(serializers.ModelSerializer): class Meta: model = UniqueConstraintReadOnlyFieldModel read_only_fields = ("state",) fields = ("position", "something", *read_only_fields) - serializer = UniqueConstraintReadOnlyFieldModelSerializer( data={"position": 1, "something": 1} ) assert serializer.is_valid() - UniqueConstraintReadOnlyFieldModel.objects.create(position=1, something=1) + + def test_uniq_constraint_condition_read_only_partial(self): + class UniqueConstraintReadOnlyFieldModelSerializer(serializers.ModelSerializer): + class Meta: + model = UniqueConstraintReadOnlyFieldModel + read_only_fields = ("state",) + fields = ("position", "something", *read_only_fields) + instance = UniqueConstraintReadOnlyFieldModel.objects.create(position=1, something=1) serializer = UniqueConstraintReadOnlyFieldModelSerializer( - data={"position": 1, "something": 1} + instance=instance, + data={"position": 1, "something": 1}, + partial=True ) - assert not serializer.is_valid() + assert serializer.is_valid() # Tests for `UniqueForDateValidator`