Skip validation of NULL field only if it part of unique_together

This commit is contained in:
Aider Ibragimov 2015-02-19 18:03:44 +03:00
parent 3d85473edf
commit fe8d95f93e
2 changed files with 53 additions and 6 deletions

View File

@ -140,7 +140,10 @@ class UniqueTogetherValidator:
queryset = self.exclude_current_instance(attrs, queryset) queryset = self.exclude_current_instance(attrs, queryset)
# Ignore validation if any field is None # Ignore validation if any field is None
if None not in attrs.values() and queryset.exists(): checked_values = [
value for field, value in attrs.items() if field in self.fields
]
if None not in checked_values and queryset.exists():
field_names = ', '.join(self.fields) field_names = ', '.join(self.fields)
raise ValidationError(self.message.format(field_names=field_names)) raise ValidationError(self.message.format(field_names=field_names))

View File

@ -83,11 +83,37 @@ class UniquenessTogetherModel(models.Model):
unique_together = ('race_name', 'position') unique_together = ('race_name', 'position')
class NullUniquenessTogetherModel(models.Model):
"""
Used to ensure that null values are not included when checking
unique_together constraints.
Ignoring items which have a null in any of the validated fields is the same
behavior that database backends will use when they have the
unique_together constraint added.
Example case: a null position could indicate a non-finisher in the race,
there could be many non-finishers in a race, but all non-NULL
values *should* be unique against the given `race_name`.
"""
date_of_birth = models.DateField(null=True) # Not part of the uniqueness constraint
race_name = models.CharField(max_length=100)
position = models.IntegerField(null=True)
class Meta:
unique_together = ('race_name', 'position')
class UniquenessTogetherSerializer(serializers.ModelSerializer): class UniquenessTogetherSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = UniquenessTogetherModel model = UniquenessTogetherModel
class NullUniquenessTogetherSerializer(serializers.ModelSerializer):
class Meta:
model = NullUniquenessTogetherModel
class TestUniquenessTogetherValidation(TestCase): class TestUniquenessTogetherValidation(TestCase):
def setUp(self): def setUp(self):
self.instance = UniquenessTogetherModel.objects.create( self.instance = UniquenessTogetherModel.objects.create(
@ -183,15 +209,33 @@ class TestUniquenessTogetherValidation(TestCase):
assert repr(serializer) == expected assert repr(serializer) == expected
def test_ignore_validation_for_null_fields(self): def test_ignore_validation_for_null_fields(self):
UniquenessTogetherModel.objects.create( # None values that are on fields which are part of the uniqueness
race_name=None, # constraint cause the instance to ignore uniqueness validation.
NullUniquenessTogetherModel.objects.create(
date_of_birth=datetime.date(2000, 1, 1),
race_name='Paris Marathon',
position=None position=None
) )
data = {'race_name': None, 'position': None} data = {
serializer = UniquenessTogetherSerializer(data=data) 'date': datetime.date(2000, 1, 1),
'race_name': 'Paris Marathon',
'position': None
}
serializer = NullUniquenessTogetherSerializer(data=data)
assert serializer.is_valid() assert serializer.is_valid()
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.
NullUniquenessTogetherModel.objects.create(
date_of_birth=datetime.date(2000, 1, 1),
race_name='Paris Marathon',
position=1
)
data = {'date': None, 'race_name': 'Paris Marathon', 'position': 1}
serializer = NullUniquenessTogetherSerializer(data=data)
assert not serializer.is_valid()
# Tests for `UniqueForDateValidator` # Tests for `UniqueForDateValidator`
# ---------------------------------- # ----------------------------------