8000 Merge pull request #716 from tomchristie/list-deserialization · corentinl/django-rest-framework@775b431 · GitHub
[go: up one dir, main page]

Skip to content

Commit 775b431

Browse files
committed
Merge pull request encode#716 from tomchristie/list-deserialization
List deserialization
2 parents c5b98f0 + 28ae264 commit 775b431

File tree

4 files changed

+55
-12
lines changed

4 files changed

+55
-12
lines changed

docs/api-guide/serializers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ To serialize a queryset instead of an object instance, you should pass the `many
9393

9494
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
9595

96+
When deserialising a list of items, errors will be returned as a list of tuples. The first item in an error tuple will be the index of the item with the error in the original data; The second item in the tuple will be a dict with the individual errors for that item.
97+
9698
### Field-level validation
9799

98100
You can specify custom field-level validation by adding `.validate_<fieldname>` methods to your `Serializer` subclass. These are analagous to `.clean_<fieldname>` methods on Django forms, but accept slightly different arguments.

docs/topics/credits.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ The following people have helped make REST framework great.
108108
* Omer Katz - [thedrow]
109109
* Wiliam Souza - [waa]
110110
* Jonas Braun - [iekadou]
111+
* Ian Dash - [bitmonkey]
111112

112113
Many thanks to everyone who's contributed to the project.
113114

@@ -250,3 +251,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
250251
[thedrow]: https://github.com/thedrow
251252
[waa]: https://github.com/wiliamsouza
252253
[iekadou]: https://github.com/iekadou
254+
[bitmonkey]: https://github.com/bitmonkey

rest_framework/serializers.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from django.db import models
88
from django.forms import widgets
99
from django.utils.datastructures import SortedDict
10-
from rest_framework.compat import get_concrete_model
11-
from rest_framework.compat import six
10+
from rest_framework.compat import get_concrete_model, six
1211

1312
# Note: We do the following so that users of the framework can use this style:
1413
#
@@ -285,10 +284,6 @@ def from_native(self, data, files):
285284
"""
286285
Deserialize primitives -> objects.
287286
"""
288-
if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
289-
# TODO: error data when deserializing lists
290-
return [self.from_native(item, None) for item in data]
291-
292287
self._errors = {}
293288
if data is not None or files is not None:
294289
attrs = self.restore_fields(data, files)
@@ -330,7 +325,7 @@ def field_to_native(self, obj, field_name):
330325
if self.many is not None:
331326
many = self.many
332327
else:
333-
many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))
328+
many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict, six.text_type))
334329

335330
if many:
336331
return [self.to_native(item) for item in obj]
@@ -348,19 +343,25 @@ def errors(self):
348343
if self.many is not None:
349344
many = self.many
350345
else:
351-
many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict))
346+
many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))
352347
if many:
353348
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
354349
'Use the `many=True` flag when instantiating the serializer.',
355350
PendingDeprecationWarning, stacklevel=3)
356351

357-
# TODO: error data when deserializing lists
358352
if many:
359-
ret = [self.from_native(item, None) for item in data]
360-
ret = self.from_native(data, files)
353+
ret = []
354+
errors = []
355+
for item in data:
356+
ret.append(self.from_native(item, None))
357+
errors.append(self._errors)
358+
self._errors = any(errors) and errors or []
359+
else:
360+
ret = self.from_native(data, files)
361361

362362
if not self._errors:
363363
self.object = ret
364+
364365
return self._errors
365366

366367
def is_valid(self):

rest_framework/tests/serializer.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,16 @@ def test_bad_type_data_is_false(self):
268268
data = ['i am', 'a', 'list']
269269
serializer = CommentSerializer(self.comment, data=data, many=True)
270270
self.assertEqual(serializer.is_valid(), False)
271-
self.assertEqual(serializer.errors, {'non_field_errors': ['Invalid data']})
271+
self.assertTrue(isinstance(serializer.errors, list))
272+
273+
self.assertEqual(
274+
serializer.errors,
275+
[
276+
{'non_field_errors': ['Invalid data']},
277+
{'non_field_errors': ['Invalid data']},
278+
{'non_field_errors': ['Invalid data']}
279+
]
280+
)
272281

273282
data = 'and i am a string'
274283
serializer = CommentSerializer(self.comment, data=data)
@@ -1072,3 +1081,32 @@ class AlbumCollectionSerializer(serializers.Serializer):
10721081

10731082
# This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
10741083
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
1084+
1085+
1086+
class DeserializeListTestCase(TestCase):
1087+
1088+
def setUp(self):
1089+
self.data = {
1090+
'email': 'nobody@nowhere.com',
1091+
'content': 'This is some test content',
1092+
'created': datetime.datetime(2013, 3, 7),
1093+
}
1094+
1095+
def test_no_errors(self):
1096+
data = [self.data.copy() for x in range(0, 3)]
1097+
serializer = CommentSerializer(data=data)
1098+
self.assertTrue(serializer.is_valid())
1099+ self.assertTrue(isinstance(serializer.object, list))
1100+
self.assertTrue(
1101+
all((isinstance(item, Comment) for item in serializer.object))
1102+
)
1103+
1104+
def test_errors_return_as_list(self):
1105+
invalid_item = self.data.copy()
1106+
invalid_item['email'] = ''
1107+
data = [self.data.copy(), invalid_item, self.data.copy()]
1108+
1109+
serializer = CommentSerializer(data=data)
1110+
self.assertFalse(serializer.is_valid())
1111+
expected = [{}, {'email': ['This field is required.']}, {}]
1112+
self.assertEqual(serializer.errors, expected)

0 commit comments

Comments
 (0)
0