Skip to content

Commit

Permalink
Fix raising on nullable fields part of UniqueConstraint (#9531)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
browniebroke authored Dec 14, 2024
1 parent dbac145 commit a8595a8
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 0 deletions.
2 changes: 2 additions & 0 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 32 additions & 0 deletions tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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`
# ----------------------------------
Expand Down

0 comments on commit a8595a8

Please sign in to comment.