aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2013-03-08 14:45:25 -0800
committerTom Christie2013-03-08 14:45:25 -0800
commit775b4314eda853bbdfacab5cc5d3d0ec0e56a92e (patch)
tree61b27ea9e843742dabeccfdd49fcf75ec7ae8683
parentc5b98f0d106758298edf045e7bb44ecd7e4b9629 (diff)
parent28ae26466e1b1493feeba19480c6eb148d603330 (diff)
downloaddjango-rest-framework-775b4314eda853bbdfacab5cc5d3d0ec0e56a92e.tar.bz2
Merge pull request #716 from tomchristie/list-deserialization
List deserialization
-rw-r--r--docs/api-guide/serializers.md2
-rw-r--r--docs/topics/credits.md2
-rw-r--r--rest_framework/serializers.py23
-rw-r--r--rest_framework/tests/serializer.py40
4 files changed, 55 insertions, 12 deletions
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 6f1f2883..de2cf7d8 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -93,6 +93,8 @@ To serialize a queryset instead of an object instance, you should pass the `many
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.
+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.
+
### Field-level validation
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.
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 190ce490..d6f312ed 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -108,6 +108,7 @@ The following people have helped make REST framework great.
* Omer Katz - [thedrow]
* Wiliam Souza - [waa]
* Jonas Braun - [iekadou]
+* Ian Dash - [bitmonkey]
Many thanks to everyone who's contributed to the project.
@@ -250,3 +251,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[thedrow]: https://github.com/thedrow
[waa]: https://github.com/wiliamsouza
[iekadou]: https://github.com/iekadou
+[bitmonkey]: https://github.com/bitmonkey
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index ba9e9e9c..106e3f17 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -7,8 +7,7 @@ from django.core.paginator import Page
from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
-from rest_framework.compat import get_concrete_model
-from rest_framework.compat import six
+from rest_framework.compat import get_concrete_model, six
# Note: We do the following so that users of the framework can use this style:
#
@@ -285,10 +284,6 @@ class BaseSerializer(Field):
"""
Deserialize primitives -> objects.
"""
- if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
- # TODO: error data when deserializing lists
- return [self.from_native(item, None) for item in data]
-
self._errors = {}
if data is not None or files is not None:
attrs = self.restore_fields(data, files)
@@ -330,7 +325,7 @@ class BaseSerializer(Field):
if self.many is not None:
many = self.many
else:
- many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))
+ many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict, six.text_type))
if many:
return [self.to_native(item) for item in obj]
@@ -348,19 +343,25 @@ class BaseSerializer(Field):
if self.many is not None:
many = self.many
else:
- many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict))
+ many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))
if many:
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
'Use the `many=True` flag when instantiating the serializer.',
PendingDeprecationWarning, stacklevel=3)
- # TODO: error data when deserializing lists
if many:
- ret = [self.from_native(item, None) for item in data]
- ret = self.from_native(data, files)
+ ret = []
+ errors = []
+ for item in data:
+ ret.append(self.from_native(item, None))
+ errors.append(self._errors)
+ self._errors = any(errors) and errors or []
+ else:
+ ret = self.from_native(data, files)
if not self._errors:
self.object = ret
+
return self._errors
def is_valid(self):
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 51065017..beb372c2 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -268,7 +268,16 @@ class ValidationTests(TestCase):
data = ['i am', 'a', 'list']
serializer = CommentSerializer(self.comment, data=data, many=True)
self.assertEqual(serializer.is_valid(), False)
- self.assertEqual(serializer.errors, {'non_field_errors': ['Invalid data']})
+ self.assertTrue(isinstance(serializer.errors, list))
+
+ self.assertEqual(
+ serializer.errors,
+ [
+ {'non_field_errors': ['Invalid data']},
+ {'non_field_errors': ['Invalid data']},
+ {'non_field_errors': ['Invalid data']}
+ ]
+ )
data = 'and i am a string'
serializer = CommentSerializer(self.comment, data=data)
@@ -1072,3 +1081,32 @@ class NestedSerializerContextTests(TestCase):
# This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
+
+
+class DeserializeListTestCase(TestCase):
+
+ def setUp(self):
+ self.data = {
+ 'email': 'nobody@nowhere.com',
+ 'content': 'This is some test content',
+ 'created': datetime.datetime(2013, 3, 7),
+ }
+
+ def test_no_errors(self):
+ data = [self.data.copy() for x in range(0, 3)]
+ serializer = CommentSerializer(data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertTrue(isinstance(serializer.object, list))
+ self.assertTrue(
+ all((isinstance(item, Comment) for item in serializer.object))
+ )
+
+ def test_errors_return_as_list(self):
+ invalid_item = self.data.copy()
+ invalid_item['email'] = ''
+ data = [self.data.copy(), invalid_item, self.data.copy()]
+
+ serializer = CommentSerializer(data=data)
+ self.assertFalse(serializer.is_valid())
+ expected = [{}, {'email': ['This field is required.']}, {}]
+ self.assertEqual(serializer.errors, expected)