aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--rest_framework/fields.py2
-rw-r--r--rest_framework/serializers.py65
-rw-r--r--tests/test_fields.py2
-rw-r--r--tests/test_serializer_bulk_update.py240
4 files changed, 176 insertions, 133 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index b554f238..a9e2e48b 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -943,7 +943,7 @@ class ChoiceField(Field):
class MultipleChoiceField(ChoiceField):
default_error_messages = {
'invalid_choice': _('`{input}` is not a valid choice.'),
- 'not_a_list': _('Expected a list of items but got type `{input_type}`')
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
}
default_empty_html = []
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index dfac75fc..cbac3992 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -90,12 +90,10 @@ class BaseSerializer(Field):
raise NotImplementedError('`create()` must be implemented.')
def save(self, **kwargs):
- validated_data = self.validated_data
- if kwargs:
- validated_data = dict(
- list(validated_data.items()) +
- list(kwargs.items())
- )
+ validated_data = dict(
+ list(self.validated_data.items()) +
+ list(kwargs.items())
+ )
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
@@ -210,9 +208,9 @@ class BoundField(object):
class NestedBoundField(BoundField):
"""
- This BoundField additionally implements __iter__ and __getitem__
+ This `BoundField` additionally implements __iter__ and __getitem__
in order to support nested bound fields. This class is the type of
- BoundField that is used for serializer fields.
+ `BoundField` that is used for serializer fields.
"""
def __iter__(self):
for field in self.fields.values():
@@ -460,6 +458,10 @@ class ListSerializer(BaseSerializer):
child = None
many = True
+ default_error_messages = {
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ }
+
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
assert self.child is not None, '`child` is a required argument.'
@@ -485,7 +487,31 @@ class ListSerializer(BaseSerializer):
"""
if html.is_html_input(data):
data = html.parse_html_list(data)
- return [self.child.run_validation(item) for item in data]
+
+ if not isinstance(data, list):
+ message = self.error_messages['not_a_list'].format(
+ input_type=type(data).__name__
+ )
+ raise ValidationError({
+ api_settings.NON_FIELD_ERRORS_KEY: [message]
+ })
+
+ ret = []
+ errors = ReturnList(serializer=self)
+
+ for item in data:
+ try:
+ validated = self.child.run_validation(item)
+ except ValidationError, exc:
+ errors.append(exc.detail)
+ else:
+ ret.append(validated)
+ errors.append({})
+
+ if any(errors):
+ raise ValidationError(errors)
+
+ return ret
def to_representation(self, data):
"""
@@ -497,8 +523,25 @@ class ListSerializer(BaseSerializer):
serializer=self
)
- def create(self, attrs_list):
- return [self.child.create(attrs) for attrs in attrs_list]
+ def save(self, **kwargs):
+ assert self.instance is None, (
+ "Serializers do not support multiple update by default, because "
+ "it would be unclear how to deal with insertions, updates and "
+ "deletions. If you need to support multiple update, use a "
+ "`ListSerializer` class and override `.save()` so you can specify "
+ "the behavior exactly."
+ )
+
+ validated_data = [
+ dict(list(attrs.items()) + list(kwargs.items()))
+ for attrs in self.validated_data
+ ]
+
+ self.instance = [
+ self.child.create(attrs) for attrs in validated_data
+ ]
+
+ return self.instance
def __repr__(self):
return representation.list_repr(self, indent=1)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 96d09900..5db381ac 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -859,7 +859,7 @@ class TestMultipleChoiceField(FieldValues):
('aircon', 'manual'): set(['aircon', 'manual']),
}
invalid_inputs = {
- 'abc': ['Expected a list of items but got type `str`'],
+ 'abc': ['Expected a list of items but got type `str`.'],
('aircon', 'incorrect'): ['`incorrect` is not a valid choice.']
}
outputs = [
diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py
index 3341ce59..85b6b2fa 100644
--- a/tests/test_serializer_bulk_update.py
+++ b/tests/test_serializer_bulk_update.py
@@ -1,123 +1,123 @@
-# """
-# Tests to cover bulk create and update using serializers.
-# """
-# from __future__ import unicode_literals
-# from django.test import TestCase
-# from rest_framework import serializers
-
-
-# class BulkCreateSerializerTests(TestCase):
-# """
-# Creating multiple instances using serializers.
-# """
-
-# def setUp(self):
-# class BookSerializer(serializers.Serializer):
-# id = serializers.IntegerField()
-# title = serializers.CharField(max_length=100)
-# author = serializers.CharField(max_length=100)
-
-# self.BookSerializer = BookSerializer
-
-# def test_bulk_create_success(self):
-# """
-# Correct bulk update serialization should return the input data.
-# """
-
-# data = [
-# {
-# 'id': 0,
-# 'title': 'The electric kool-aid acid test',
-# 'author': 'Tom Wolfe'
-# }, {
-# 'id': 1,
-# 'title': 'If this is a man',
-# 'author': 'Primo Levi'
-# }, {
-# 'id': 2,
-# 'title': 'The wind-up bird chronicle',
-# 'author': 'Haruki Murakami'
-# }
-# ]
-
-# serializer = self.BookSerializer(data=data, many=True)
-# self.assertEqual(serializer.is_valid(), True)
-# self.assertEqual(serializer.object, data)
-
-# def test_bulk_create_errors(self):
-# """
-# Correct bulk update serialization should return the input data.
-# """
-
-# data = [
-# {
-# 'id': 0,
-# 'title': 'The electric kool-aid acid test',
-# 'author': 'Tom Wolfe'
-# }, {
-# 'id': 1,
-# 'title': 'If this is a man',
-# 'author': 'Primo Levi'
-# }, {
-# 'id': 'foo',
-# 'title': 'The wind-up bird chronicle',
-# 'author': 'Haruki Murakami'
-# }
-# ]
-# expected_errors = [
-# {},
-# {},
-# {'id': ['Enter a whole number.']}
-# ]
-
-# serializer = self.BookSerializer(data=data, many=True)
-# self.assertEqual(serializer.is_valid(), False)
-# self.assertEqual(serializer.errors, expected_errors)
-
-# def test_invalid_list_datatype(self):
-# """
-# Data containing list of incorrect data type should return errors.
-# """
-# data = ['foo', 'bar', 'baz']
-# serializer = self.BookSerializer(data=data, many=True)
-# self.assertEqual(serializer.is_valid(), False)
-
-# expected_errors = [
-# {'non_field_errors': ['Invalid data']},
-# {'non_field_errors': ['Invalid data']},
-# {'non_field_errors': ['Invalid data']}
-# ]
-
-# self.assertEqual(serializer.errors, expected_errors)
-
-# def test_invalid_single_datatype(self):
-# """
-# Data containing a single incorrect data type should return errors.
-# """
-# data = 123
-# serializer = self.BookSerializer(data=data, many=True)
-# self.assertEqual(serializer.is_valid(), False)
-
-# expected_errors = {'non_field_errors': ['Expected a list of items.']}
-
-# self.assertEqual(serializer.errors, expected_errors)
-
-# def test_invalid_single_object(self):
-# """
-# Data containing only a single object, instead of a list of objects
-# should return errors.
-# """
-# data = {
-# 'id': 0,
-# 'title': 'The electric kool-aid acid test',
-# 'author': 'Tom Wolfe'
-# }
-# serializer = self.BookSerializer(data=data, many=True)
-# self.assertEqual(serializer.is_valid(), False)
-
-# expected_errors = {'non_field_errors': ['Expected a list of items.']}
-
-# self.assertEqual(serializer.errors, expected_errors)
+"""
+Tests to cover bulk create and update using serializers.
+"""
+from __future__ import unicode_literals
+from django.test import TestCase
+from rest_framework import serializers
+
+
+class BulkCreateSerializerTests(TestCase):
+ """
+ Creating multiple instances using serializers.
+ """
+
+ def setUp(self):
+ class BookSerializer(serializers.Serializer):
+ id = serializers.IntegerField()
+ title = serializers.CharField(max_length=100)
+ author = serializers.CharField(max_length=100)
+
+ self.BookSerializer = BookSerializer
+
+ def test_bulk_create_success(self):
+ """
+ Correct bulk update serialization should return the input data.
+ """
+
+ data = [
+ {
+ 'id': 0,
+ 'title': 'The electric kool-aid acid test',
+ 'author': 'Tom Wolfe'
+ }, {
+ 'id': 1,
+ 'title': 'If this is a man',
+ 'author': 'Primo Levi'
+ }, {
+ 'id': 2,
+ 'title': 'The wind-up bird chronicle',
+ 'author': 'Haruki Murakami'
+ }
+ ]
+
+ serializer = self.BookSerializer(data=data, many=True)
+ self.assertEqual(serializer.is_valid(), True)
+ self.assertEqual(serializer.validated_data, data)
+
+ def test_bulk_create_errors(self):
+ """
+ Incorrect bulk create serialization should return errors.
+ """
+
+ data = [
+ {
+ 'id': 0,
+ 'title': 'The electric kool-aid acid test',
+ 'author': 'Tom Wolfe'
+ }, {
+ 'id': 1,
+ 'title': 'If this is a man',
+ 'author': 'Primo Levi'
+ }, {
+ 'id': 'foo',
+ 'title': 'The wind-up bird chronicle',
+ 'author': 'Haruki Murakami'
+ }
+ ]
+ expected_errors = [
+ {},
+ {},
+ {'id': ['A valid integer is required.']}
+ ]
+
+ serializer = self.BookSerializer(data=data, many=True)
+ self.assertEqual(serializer.is_valid(), False)
+ self.assertEqual(serializer.errors, expected_errors)
+
+ def test_invalid_list_datatype(self):
+ """
+ Data containing list of incorrect data type should return errors.
+ """
+ data = ['foo', 'bar', 'baz']
+ serializer = self.BookSerializer(data=data, many=True)
+ self.assertEqual(serializer.is_valid(), False)
+
+ expected_errors = [
+ {'non_field_errors': ['Invalid data. Expected a dictionary, but got unicode.']},
+ {'non_field_errors': ['Invalid data. Expected a dictionary, but got unicode.']},
+ {'non_field_errors': ['Invalid data. Expected a dictionary, but got unicode.']}
+ ]
+
+ self.assertEqual(serializer.errors, expected_errors)
+
+ def test_invalid_single_datatype(self):
+ """
+ Data containing a single incorrect data type should return errors.
+ """
+ data = 123
+ serializer = self.BookSerializer(data=data, many=True)
+ self.assertEqual(serializer.is_valid(), False)
+
+ expected_errors = {'non_field_errors': ['Expected a list of items but got type `int`.']}
+
+ self.assertEqual(serializer.errors, expected_errors)
+
+ def test_invalid_single_object(self):
+ """
+ Data containing only a single object, instead of a list of objects
+ should return errors.
+ """
+ data = {
+ 'id': 0,
+ 'title': 'The electric kool-aid acid test',
+ 'author': 'Tom Wolfe'
+ }
+ serializer = self.BookSerializer(data=data, many=True)
+ self.assertEqual(serializer.is_valid(), False)
+
+ expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']}
+
+ self.assertEqual(serializer.errors, expected_errors)
# class BulkUpdateSerializerTests(TestCase):