Skip to content

Commit aece4e1

Browse files
rpkilbysigvef
authored andcommitted
Fix nullable source='*' fields (encode#6659)
1 parent 5192133 commit aece4e1

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

rest_framework/fields.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,11 @@ def validate_empty_values(self, data):
493493
if data is None:
494494
if not self.allow_null:
495495
self.fail('null')
496+
# Nullable `source='*'` fields should not be skipped when its named
497+
# field is given a null value. This is because `source='*'` means
498+
# the field is passed the entire object, which is not null.
499+
elif self.source == '*':
500+
return (False, None)
496501
return (True, None)
497502

498503
return (False, data)

tests/test_serializer.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ def test_validate_list(self):
317317

318318
class TestStarredSource:
319319
"""
320-
Tests for `source='*'` argument, which is used for nested representations.
320+
Tests for `source='*'` argument, which is often used for complex field or
321+
nested representations.
321322
322323
For example:
323324
@@ -337,11 +338,28 @@ class NestedSerializer2(serializers.Serializer):
337338
c = serializers.IntegerField()
338339
d = serializers.IntegerField()
339340

340-
class TestSerializer(serializers.Serializer):
341+
class NestedBaseSerializer(serializers.Serializer):
341342
nested1 = NestedSerializer1(source='*')
342343
nested2 = NestedSerializer2(source='*')
343344

344-
self.Serializer = TestSerializer
345+
# nullable nested serializer testing
346+
class NullableNestedSerializer(serializers.Serializer):
347+
nested = NestedSerializer1(source='*', allow_null=True)
348+
349+
# nullable custom field testing
350+
class CustomField(serializers.Field):
351+
def to_representation(self, instance):
352+
return getattr(instance, 'foo', None)
353+
354+
def to_internal_value(self, data):
355+
return {'foo': data}
356+
357+
class NullableFieldSerializer(serializers.Serializer):
358+
field = CustomField(source='*', allow_null=True)
359+
360+
self.Serializer = NestedBaseSerializer
361+
self.NullableNestedSerializer = NullableNestedSerializer
362+
self.NullableFieldSerializer = NullableFieldSerializer
345363

346364
def test_nested_validate(self):
347365
"""
@@ -356,6 +374,12 @@ def test_nested_validate(self):
356374
'd': 4
357375
}
358376

377+
def test_nested_null_validate(self):
378+
serializer = self.NullableNestedSerializer(data={'nested': None})
379+
380+
# validation should fail (but not error) since nested fields are required
381+
assert not serializer.is_valid()
382+
359383
def test_nested_serialize(self):
360384
"""
361385
An object can be serialized into a nested representation.
@@ -364,6 +388,20 @@ def test_nested_serialize(self):
364388
serializer = self.Serializer(instance)
365389
assert serializer.data == self.data
366390

391+
def test_field_validate(self):
392+
serializer = self.NullableFieldSerializer(data={'field': 'bar'})
393+
394+
# validation should pass since no internal validation
395+
assert serializer.is_valid()
396+
assert serializer.validated_data == {'foo': 'bar'}
397+
398+
def test_field_null_validate(self):
399+
serializer = self.NullableFieldSerializer(data={'field': None})
400+
401+
# validation should pass since no internal validation
402+
assert serializer.is_valid()
403+
assert serializer.validated_data == {'foo': None}
404+
367405

368406
class TestIncorrectlyConfigured:
369407
def test_incorrect_field_name(self):

0 commit comments

Comments
 (0)