mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 01:47:59 +03:00 
			
		
		
		
	Skip validation of NULL field only if it part of unique_together
This commit is contained in:
		
							parent
							
								
									3d85473edf
								
							
						
					
					
						commit
						fe8d95f93e
					
				| 
						 | 
					@ -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))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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`
 | 
				
			||||||
# ----------------------------------
 | 
					# ----------------------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user