Skip to content

Commit 236667b

Browse files
rpkilbytomchristie
authored andcommitted
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
1 parent f744da7 commit 236667b

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

rest_framework/serializers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ def _read_only_defaults(self):
448448
default = field.get_default()
449449
except SkipField:
450450
continue
451-
defaults[field.field_name] = default
451+
defaults[field.source] = default
452452

453453
return defaults
454454

rest_framework/validators.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def enforce_required_fields(self, attrs, serializer):
106106
missing_items = {
107107
field_name: self.missing_message
108108
for field_name in self.fields
109-
if field_name not in attrs
109+
if serializer.fields[field_name].source not in attrs
110110
}
111111
if missing_items:
112112
raise ValidationError(missing_items, code='required')
@@ -115,17 +115,23 @@ def filter_queryset(self, attrs, queryset, serializer):
115115
"""
116116
Filter the queryset to all instances matching the given attributes.
117117
"""
118+
# field names => field sources
119+
sources = [
120+
serializer.fields[field_name].source
121+
for field_name in self.fields
122+
]
123+
118124
# If this is an update, then any unprovided field should
119125
# have it's value set based on the existing instance attribute.
120126
if serializer.instance is not None:
121-
for field_name in self.fields:
122-
if field_name not in attrs:
123-
attrs[field_name] = getattr(serializer.instance, field_name)
127+
for source in sources:
128+
if source not in attrs:
129+
attrs[source] = getattr(serializer.instance, source)
124130

125131
# Determine the filter keyword arguments and filter the queryset.
126132
filter_kwargs = {
127-
field_name: attrs[field_name]
128-
for field_name in self.fields
133+
source: attrs[source]
134+
for source in sources
129135
}
130136
return qs_filter(queryset, **filter_kwargs)
131137

tests/test_validators.py

+44-5
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,49 @@ class Meta:
301301
]
302302
}
303303

304+
def test_read_only_fields_with_default_and_source(self):
305+
class ReadOnlySerializer(serializers.ModelSerializer):
306+
name = serializers.CharField(source='race_name', default='test', read_only=True)
307+
308+
class Meta:
309+
model = UniquenessTogetherModel
310+
fields = ['name', 'position']
311+
validators = [
312+
UniqueTogetherValidator(
313+
queryset=UniquenessTogetherModel.objects.all(),
314+
fields=['name', 'position']
315+
)
316+
]
317+
318+
serializer = ReadOnlySerializer(data={'position': 1})
319+
assert serializer.is_valid(raise_exception=True)
320+
321+
def test_writeable_fields_with_source(self):
322+
class WriteableSerializer(serializers.ModelSerializer):
323+
name = serializers.CharField(source='race_name')
324+
325+
class Meta:
326+
model = UniquenessTogetherModel
327+
fields = ['name', 'position']
328+
validators = [
329+
UniqueTogetherValidator(
330+
queryset=UniquenessTogetherModel.objects.all(),
331+
fields=['name', 'position']
332+
)
333+
]
334+
335+
serializer = WriteableSerializer(data={'name': 'test', 'position': 1})
336+
assert serializer.is_valid(raise_exception=True)
337+
338+
# Validation error should use seriazlier field name, not source
339+
serializer = WriteableSerializer(data={'position': 1})
340+
assert not serializer.is_valid()
341+
assert serializer.errors == {
342+
'name': [
343+
'This field is required.'
344+
]
345+
}
346+
304347
def test_allow_explict_override(self):
305348
"""
306349
Ensure validators can be explicitly removed..
@@ -357,13 +400,9 @@ class MockQueryset:
357400
def filter(self, **kwargs):
358401
self.called_with = kwargs
359402

360-
class MockSerializer:
361-
def __init__(self, instance):
362-
self.instance = instance
363-
364403
data = {'race_name': 'bar'}
365404
queryset = MockQueryset()
366-
serializer = MockSerializer(instance=self.instance)
405+
serializer = UniquenessTogetherSerializer(instance=self.instance)
367406
validator = UniqueTogetherValidator(queryset, fields=('race_name',
368407
'position'))
369408
validator.filter_queryset(attrs=data, queryset=queryset, serializer=serializer)

0 commit comments

Comments
 (0)