From a8595a8eae2649b763f4882da643c1dc9183d6f1 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Sat, 14 Dec 2024 09:08:22 +0000 Subject: [PATCH] Fix raising on nullable fields part of `UniqueConstraint` (#9531) * Add test to reproduce problem with nullable fields part of a unique constraint Ref #9378 * Simplify test case and add similar case for unique_together * Add test for unique together in a better place * Default nullable fields to null in unique constraints checks * Remove redundant test and move other to more appropriate place --- rest_framework/serializers.py | 2 ++ tests/test_validators.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index b1b7b64774..f37bd3a3d6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1490,6 +1490,8 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs default = timezone.now elif unique_constraint_field.has_default(): default = unique_constraint_field.default + elif unique_constraint_field.null: + default = None else: default = empty diff --git a/tests/test_validators.py b/tests/test_validators.py index 4bb8658d5b..9c1a0eac31 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -441,6 +441,14 @@ def test_ignore_validation_for_null_fields(self): serializer = NullUniquenessTogetherSerializer(data=data) assert serializer.is_valid() + def test_ignore_validation_for_missing_nullable_fields(self): + data = { + 'date': datetime.date(2000, 1, 1), + 'race_name': 'Paris Marathon', + } + serializer = NullUniquenessTogetherSerializer(data=data) + assert serializer.is_valid(), serializer.errors + def test_do_not_ignore_validation_for_null_fields(self): # None values that are not on fields part of the uniqueness constraint # do not cause the instance to skip validation. @@ -539,12 +547,30 @@ class Meta: ] +class UniqueConstraintNullableModel(models.Model): + title = models.CharField(max_length=100) + age = models.IntegerField(null=True) + tag = models.CharField(max_length=100, null=True) + + class Meta: + constraints = [ + # Unique constraint on 2 nullable fields + models.UniqueConstraint(name='unique_constraint', fields=('age', 'tag')) + ] + + class UniqueConstraintSerializer(serializers.ModelSerializer): class Meta: model = UniqueConstraintModel fields = '__all__' +class UniqueConstraintNullableSerializer(serializers.ModelSerializer): + class Meta: + model = UniqueConstraintNullableModel + fields = ('title', 'age', 'tag') + + class TestUniqueConstraintValidation(TestCase): def setUp(self): self.instance = UniqueConstraintModel.objects.create( @@ -611,6 +637,12 @@ def test_single_field_uniq_validators(self): ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")} assert ids_in_qs == {frozenset([1]), frozenset([3])} + def test_nullable_unique_constraint_fields_are_not_required(self): + serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'}) + self.assertTrue(serializer.is_valid(), serializer.errors) + result = serializer.save() + self.assertIsInstance(result, UniqueConstraintNullableModel) + # Tests for `UniqueForDateValidator` # ----------------------------------