From d4e404397c569c09fb72c30c1c23a9a8cce175f5 Mon Sep 17 00:00:00 2001 From: Konstantin Alekseev Date: Sat, 31 May 2025 17:59:51 +0300 Subject: [PATCH] Fix unique together validation. While building validator unique together fields were compared with declared field names instead of model field names --- rest_framework/serializers.py | 3 ++- tests/test_validators.py | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0b87aa8fc1..8fe284bc84 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1469,12 +1469,13 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs model_field.unique_for_year} unique_constraint_names -= {None} + model_fields_names = set(model_fields.keys()) # Include each of the `unique_together` and `UniqueConstraint` field names, # so long as all the field names are included on the serializer. for unique_together_list, queryset, condition_fields, condition in self.get_unique_together_constraints(model): unique_together_list_and_condition_fields = set(unique_together_list) | set(condition_fields) - if set(field_names).issuperset(unique_together_list_and_condition_fields): + if model_fields_names.issuperset(unique_together_list_and_condition_fields): unique_constraint_names |= unique_together_list_and_condition_fields # Now we have all the field names that have uniqueness constraints diff --git a/tests/test_validators.py b/tests/test_validators.py index d19734d98c..c594eecbe5 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -516,6 +516,43 @@ def filter(self, **kwargs): validator.filter_queryset(attrs=data, queryset=queryset, serializer=serializer) assert queryset.called_with == {'race_name': 'bar', 'position': 1} + def test_uniq_together_validation_uses_model_fields_method_field(self): + class TestSerializer(serializers.ModelSerializer): + position = serializers.SerializerMethodField() + + def get_position(self, obj): + return obj.position or 0 + + class Meta: + model = NullUniquenessTogetherModel + fields = ['race_name', 'position'] + + serializer = TestSerializer() + expected = dedent(""" + TestSerializer(): + race_name = CharField(max_length=100) + position = SerializerMethodField() + """) + assert repr(serializer) == expected + + def test_uniq_together_validation_uses_model_fields_with_source_field(self): + class TestSerializer(serializers.ModelSerializer): + pos = serializers.IntegerField(source='position') + + class Meta: + model = NullUniquenessTogetherModel + fields = ['race_name', 'pos'] + + serializer = TestSerializer() + expected = dedent(""" + TestSerializer(): + race_name = CharField(max_length=100, required=True) + pos = IntegerField(source='position') + class Meta: + validators = [] + """) + assert repr(serializer) == expected + class UniqueConstraintModel(models.Model): race_name = models.CharField(max_length=100)