Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1626,14 +1626,28 @@ def to_internal_value(self, data):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
return [self.child.run_validation(item) for item in data]
return self.run_child_validation(data)

def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return [self.child.to_representation(item) if item is not None else None for item in data]

def run_child_validation(self, data):
result = []
errors = OrderedDict()

for idx, item in enumerate(data):
try:
result.append(self.child.run_validation(item))
except ValidationError as e:
errors[idx] = e.detail

if not errors:
return result
raise ValidationError(errors)


class DictField(Field):
child = _UnvalidatedField()
Expand Down Expand Up @@ -1669,10 +1683,7 @@ def to_internal_value(self, data):
data = html.parse_html_dict(data)
if not isinstance(data, dict):
self.fail('not_a_dict', input_type=type(data).__name__)
return {
six.text_type(key): self.child.run_validation(value)
for key, value in data.items()
}
return self.run_child_validation(data)

def to_representation(self, value):
"""
Expand All @@ -1683,6 +1694,22 @@ def to_representation(self, value):
for key, val in value.items()
}

def run_child_validation(self, data):
result = {}
errors = OrderedDict()

for key, value in data.items():
key = six.text_type(key)

try:
result[key] = self.child.run_validation(value)
except ValidationError as e:
errors[key] = e.detail

if not errors:
return result
raise ValidationError(errors)


class JSONField(Field):
default_error_messages = {
Expand Down
46 changes: 41 additions & 5 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,7 +1767,7 @@ class TestListField(FieldValues):
]
invalid_inputs = [
('not a list', ['Expected a list of items but got type "str".']),
([1, 2, 'error'], ['A valid integer is required.']),
([1, 2, 'error', 'error'], {2: ['A valid integer is required.'], 3: ['A valid integer is required.']}),
({'one': 'two'}, ['Expected a list of items but got type "dict".'])
]
outputs = [
Expand All @@ -1794,6 +1794,25 @@ def test_collection_types_are_invalid_input(self):
assert exc_info.value.detail == ['Expected a list of items but got type "dict".']


class TestNestedListField(FieldValues):
"""
Values for nested `ListField` with IntegerField as child.
"""
valid_inputs = [
([[1, 2], [3]], [[1, 2], [3]]),
([[]], [[]])
]
invalid_inputs = [
(['not a list'], {0: ['Expected a list of items but got type "str".']}),
([[1, 2, 'error'], ['error']], {0: {2: ['A valid integer is required.']}, 1: {0: ['A valid integer is required.']}}),
([{'one': 'two'}], {0: ['Expected a list of items but got type "dict".']})
]
outputs = [
([[1, 2], [3]], [[1, 2], [3]]),
]
field = serializers.ListField(child=serializers.ListField(child=serializers.IntegerField()))


class TestEmptyListField(FieldValues):
"""
Values for `ListField` with allow_empty=False flag.
Expand Down Expand Up @@ -1834,13 +1853,13 @@ class TestUnvalidatedListField(FieldValues):

class TestDictField(FieldValues):
"""
Values for `ListField` with CharField as child.
Values for `DictField` with CharField as child.
"""
valid_inputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
]
invalid_inputs = [
({'a': 1, 'b': None}, ['This field may not be null.']),
({'a': 1, 'b': None, 'c': None}, {'b': ['This field may not be null.'], 'c': ['This field may not be null.']}),
('not a dict', ['Expected a dictionary of items but got type "str".']),
]
outputs = [
Expand All @@ -1866,9 +1885,26 @@ def test_allow_null(self):
assert output is None


class TestNestedDictField(FieldValues):
"""
Values for nested `DictField` with CharField as child.
"""
valid_inputs = [
({0: {'a': 1, 'b': '2'}, 1: {3: 3}}, {'0': {'a': '1', 'b': '2'}, '1': {'3': '3'}}),
]
invalid_inputs = [
({0: {'a': 1, 'b': None}, 1: {'c': None}}, {'0': {'b': ['This field may not be null.']}, '1': {'c': ['This field may not be null.']}}),
({0: 'not a dict'}, {'0': ['Expected a dictionary of items but got type "str".']}),
]
outputs = [
({0: {'a': 1, 'b': '2'}, 1: {3: 3}}, {'0': {'a': '1', 'b': '2'}, '1': {'3': '3'}}),
]
field = serializers.DictField(child=serializers.DictField(child=serializers.CharField()))


class TestDictFieldWithNullChild(FieldValues):
"""
Values for `ListField` with allow_null CharField as child.
Values for `DictField` with allow_null CharField as child.
"""
valid_inputs = [
({'a': None, 'b': '2', 3: 3}, {'a': None, 'b': '2', '3': '3'}),
Expand All @@ -1883,7 +1919,7 @@ class TestDictFieldWithNullChild(FieldValues):

class TestUnvalidatedDictField(FieldValues):
"""
Values for `ListField` with no `child` argument.
Values for `DictField` with no `child` argument.
"""
valid_inputs = [
({'a': 1, 'b': [4, 5, 6], 1: 123}, {'a': 1, 'b': [4, 5, 6], '1': 123}),
Expand Down