Fix UniqueTogetherValidator with field sources (#7086)

* Add failing tests for unique_together+source

* Fix UniqueTogetherValidator source handling

* Fix read-only+default+source handling

* Update test to use functional serializer

* Test UniqueTogetherValidator error+source
This commit is contained in:
Ryan P Kilby 2019-12-12 05:02:30 -08:00 committed by Tom Christie
parent f744da74d2
commit 236667b717
3 changed files with 57 additions and 12 deletions

View File

@ -448,7 +448,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
default = field.get_default()
except SkipField:
continue
defaults[field.field_name] = default
defaults[field.source] = default
return defaults

View File

@ -106,7 +106,7 @@ class UniqueTogetherValidator:
missing_items = {
field_name: self.missing_message
for field_name in self.fields
if field_name not in attrs
if serializer.fields[field_name].source not in attrs
}
if missing_items:
raise ValidationError(missing_items, code='required')
@ -115,17 +115,23 @@ class UniqueTogetherValidator:
"""
Filter the queryset to all instances matching the given attributes.
"""
# field names => field sources
sources = [
serializer.fields[field_name].source
for field_name in self.fields
]
# If this is an update, then any unprovided field should
# have it's value set based on the existing instance attribute.
if serializer.instance is not None:
for field_name in self.fields:
if field_name not in attrs:
attrs[field_name] = getattr(serializer.instance, field_name)
for source in sources:
if source not in attrs:
attrs[source] = getattr(serializer.instance, source)
# Determine the filter keyword arguments and filter the queryset.
filter_kwargs = {
field_name: attrs[field_name]
for field_name in self.fields
source: attrs[source]
for source in sources
}
return qs_filter(queryset, **filter_kwargs)

View File

@ -301,6 +301,49 @@ class TestUniquenessTogetherValidation(TestCase):
]
}
def test_read_only_fields_with_default_and_source(self):
class ReadOnlySerializer(serializers.ModelSerializer):
name = serializers.CharField(source='race_name', default='test', read_only=True)
class Meta:
model = UniquenessTogetherModel
fields = ['name', 'position']
validators = [
UniqueTogetherValidator(
queryset=UniquenessTogetherModel.objects.all(),
fields=['name', 'position']
)
]
serializer = ReadOnlySerializer(data={'position': 1})
assert serializer.is_valid(raise_exception=True)
def test_writeable_fields_with_source(self):
class WriteableSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='race_name')
class Meta:
model = UniquenessTogetherModel
fields = ['name', 'position']
validators = [
UniqueTogetherValidator(
queryset=UniquenessTogetherModel.objects.all(),
fields=['name', 'position']
)
]
serializer = WriteableSerializer(data={'name': 'test', 'position': 1})
assert serializer.is_valid(raise_exception=True)
# Validation error should use seriazlier field name, not source
serializer = WriteableSerializer(data={'position': 1})
assert not serializer.is_valid()
assert serializer.errors == {
'name': [
'This field is required.'
]
}
def test_allow_explict_override(self):
"""
Ensure validators can be explicitly removed..
@ -357,13 +400,9 @@ class TestUniquenessTogetherValidation(TestCase):
def filter(self, **kwargs):
self.called_with = kwargs
class MockSerializer:
def __init__(self, instance):
self.instance = instance
data = {'race_name': 'bar'}
queryset = MockQueryset()
serializer = MockSerializer(instance=self.instance)
serializer = UniquenessTogetherSerializer(instance=self.instance)
validator = UniqueTogetherValidator(queryset, fields=('race_name',
'position'))
validator.filter_queryset(attrs=data, queryset=queryset, serializer=serializer)