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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 12 additions & 6 deletions
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

Lines changed: 44 additions & 5 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy