Support for bulk create. Closes #1965.

This commit is contained in:
Tom Christie 2014-11-06 10:34:59 +00:00
parent 73daf40715
commit ed541864e6
4 changed files with 157 additions and 114 deletions

View File

@ -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 = []

View File

@ -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)

View File

@ -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 = [

View File

@ -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
"""
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.
# """
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)
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
self.BookSerializer = BookSerializer
# def test_bulk_create_success(self):
# """
# Correct bulk update serialization should return the input data.
# """
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'
# }
# ]
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)
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):
# """
# Correct bulk update serialization should return the input 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': ['Enter a whole number.']}
# ]
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)
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)
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']}
# ]
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)
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)
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.']}
expected_errors = {'non_field_errors': ['Expected a list of items but got type `int`.']}
# self.assertEqual(serializer.errors, expected_errors)
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)
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.']}
expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']}
# self.assertEqual(serializer.errors, expected_errors)
self.assertEqual(serializer.errors, expected_errors)
# class BulkUpdateSerializerTests(TestCase):