mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-27 03:54:01 +03:00
Merge pull request #716 from tomchristie/list-deserialization
List deserialization
This commit is contained in:
commit
775b4314ed
|
@ -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 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
|
### 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.
|
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.
|
||||||
|
|
|
@ -108,6 +108,7 @@ The following people have helped make REST framework great.
|
||||||
* Omer Katz - [thedrow]
|
* Omer Katz - [thedrow]
|
||||||
* Wiliam Souza - [waa]
|
* Wiliam Souza - [waa]
|
||||||
* Jonas Braun - [iekadou]
|
* Jonas Braun - [iekadou]
|
||||||
|
* Ian Dash - [bitmonkey]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
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
|
[thedrow]: https://github.com/thedrow
|
||||||
[waa]: https://github.com/wiliamsouza
|
[waa]: https://github.com/wiliamsouza
|
||||||
[iekadou]: https://github.com/iekadou
|
[iekadou]: https://github.com/iekadou
|
||||||
|
[bitmonkey]: https://github.com/bitmonkey
|
||||||
|
|
|
@ -7,8 +7,7 @@ from django.core.paginator import Page
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from rest_framework.compat import get_concrete_model
|
from rest_framework.compat import get_concrete_model, six
|
||||||
from rest_framework.compat import six
|
|
||||||
|
|
||||||
# Note: We do the following so that users of the framework can use this style:
|
# 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.
|
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 = {}
|
self._errors = {}
|
||||||
if data is not None or files is not None:
|
if data is not None or files is not None:
|
||||||
attrs = self.restore_fields(data, files)
|
attrs = self.restore_fields(data, files)
|
||||||
|
@ -330,7 +325,7 @@ class BaseSerializer(Field):
|
||||||
if self.many is not None:
|
if self.many is not None:
|
||||||
many = self.many
|
many = self.many
|
||||||
else:
|
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:
|
if many:
|
||||||
return [self.to_native(item) for item in obj]
|
return [self.to_native(item) for item in obj]
|
||||||
|
@ -348,19 +343,25 @@ class BaseSerializer(Field):
|
||||||
if self.many is not None:
|
if self.many is not None:
|
||||||
many = self.many
|
many = self.many
|
||||||
else:
|
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:
|
if many:
|
||||||
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
|
warnings.warn('Implict list/queryset serialization is due to be deprecated. '
|
||||||
'Use the `many=True` flag when instantiating the serializer.',
|
'Use the `many=True` flag when instantiating the serializer.',
|
||||||
PendingDeprecationWarning, stacklevel=3)
|
PendingDeprecationWarning, stacklevel=3)
|
||||||
|
|
||||||
# TODO: error data when deserializing lists
|
|
||||||
if many:
|
if many:
|
||||||
ret = [self.from_native(item, None) for item in data]
|
ret = []
|
||||||
ret = self.from_native(data, files)
|
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:
|
if not self._errors:
|
||||||
self.object = ret
|
self.object = ret
|
||||||
|
|
||||||
return self._errors
|
return self._errors
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
|
|
@ -268,7 +268,16 @@ class ValidationTests(TestCase):
|
||||||
data = ['i am', 'a', 'list']
|
data = ['i am', 'a', 'list']
|
||||||
serializer = CommentSerializer(self.comment, data=data, many=True)
|
serializer = CommentSerializer(self.comment, data=data, many=True)
|
||||||
self.assertEqual(serializer.is_valid(), False)
|
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'
|
data = 'and i am a string'
|
||||||
serializer = CommentSerializer(self.comment, data=data)
|
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
|
# This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
|
||||||
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
|
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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user