mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-23 15:54:16 +03:00
Basic bulk create and bulk update
This commit is contained in:
parent
09e4ee7ae3
commit
b2dc664485
|
@ -37,9 +37,6 @@ Declaring a serializer looks very similar to declaring a form:
|
|||
"""
|
||||
Given a dictionary of deserialized field values, either update
|
||||
an existing model instance, or create a new model instance.
|
||||
|
||||
Note that if we don't define this method, then deserializing
|
||||
data will simply return a dictionary of items.
|
||||
"""
|
||||
if instance is not None:
|
||||
instance.title = attrs.get('title', instance.title)
|
||||
|
@ -48,7 +45,9 @@ Declaring a serializer looks very similar to declaring a form:
|
|||
return instance
|
||||
return Comment(**attrs)
|
||||
|
||||
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. The `restore_object` method is optional, and is only required if we want our serializer to support deserialization.
|
||||
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data.
|
||||
|
||||
The `restore_object` method is optional, and is only required if we want our serializer to support deserialization into fully fledged object instances. If we don't define this method, then deserializing data will simply return a dictionary of items.
|
||||
|
||||
## Serializing objects
|
||||
|
||||
|
@ -88,18 +87,22 @@ By default, serializers must be passed values for all required fields or they wi
|
|||
|
||||
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `instance` with partial data
|
||||
|
||||
## Serializing querysets
|
||||
## Serializing multiple objects
|
||||
|
||||
To serialize a queryset instead of an object instance, you should pass the `many=True` flag when instantiating the serializer.
|
||||
To serialize a queryset or list of objects instead of a single object instance, you should pass the `many=True` flag when instantiating the serializer.
|
||||
|
||||
queryset = Comment.objects.all()
|
||||
serializer = CommentSerializer(queryset, many=True)
|
||||
serializer.data
|
||||
# [{'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}, {'email': u'jamie@example.com', 'content': u'baz', 'created': datetime.datetime(2013, 1, 12, 16, 12, 45, 104445)}]
|
||||
# [
|
||||
# {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)},
|
||||
# {'email': u'jamie@example.com', 'content': u'baz', 'created': datetime.datetime(2013, 1, 12, 16, 12, 45, 104445)}
|
||||
# ]
|
||||
|
||||
## Validation
|
||||
|
||||
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` property will contain a dictionary representing the resulting error messages.
|
||||
|
||||
Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field. The `non_field_errors` key may also be present, and will list any general validation errors.
|
||||
|
||||
When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items.
|
||||
|
|
|
@ -128,6 +128,7 @@ class BaseSerializer(Field):
|
|||
self._data = None
|
||||
self._files = None
|
||||
self._errors = None
|
||||
self._deleted = None
|
||||
|
||||
#####
|
||||
# Methods to determine which fields to use when (de)serializing objects.
|
||||
|
@ -331,6 +332,13 @@ class BaseSerializer(Field):
|
|||
return [self.to_native(item) for item in obj]
|
||||
return self.to_native(obj)
|
||||
|
||||
def get_identity(self, data):
|
||||
"""
|
||||
This hook is required for bulk update.
|
||||
It is used to determine the canonical identity of a given object.
|
||||
"""
|
||||
return data.get('id')
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""
|
||||
|
@ -352,9 +360,32 @@ class BaseSerializer(Field):
|
|||
if many:
|
||||
ret = []
|
||||
errors = []
|
||||
update = self.object is not None
|
||||
|
||||
if update:
|
||||
# If this is a bulk update we need to map all the objects
|
||||
# to a canonical identity so we can determine which
|
||||
# individual object is being updated for each item in the
|
||||
# incoming data
|
||||
objects = self.object
|
||||
identities = [self.get_identity(self.to_native(obj)) for obj in objects]
|
||||
identity_to_objects = dict(zip(identities, objects))
|
||||
|
||||
for item in data:
|
||||
if update:
|
||||
# Determine which object we're updating
|
||||
try:
|
||||
identity = self.get_identity(item)
|
||||
except:
|
||||
self.object = None
|
||||
else:
|
||||
self.object = identity_to_objects.pop(identity, None)
|
||||
|
||||
ret.append(self.from_native(item, None))
|
||||
errors.append(self._errors)
|
||||
|
||||
if update:
|
||||
self._deleted = identity_to_objects.values()
|
||||
self._errors = any(errors) and errors or []
|
||||
else:
|
||||
ret = self.from_native(data, files)
|
||||
|
@ -394,6 +425,9 @@ class BaseSerializer(Field):
|
|||
def save_object(self, obj, **kwargs):
|
||||
obj.save(**kwargs)
|
||||
|
||||
def delete_object(self, obj):
|
||||
obj.delete()
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
Save the deserialized object and return it.
|
||||
|
@ -402,6 +436,10 @@ class BaseSerializer(Field):
|
|||
[self.save_object(item, **kwargs) for item in self.object]
|
||||
else:
|
||||
self.save_object(self.object, **kwargs)
|
||||
|
||||
if self._deleted:
|
||||
[self.delete_object(item) for item in self._deleted]
|
||||
|
||||
return self.object
|
||||
|
||||
|
||||
|
|
|
@ -266,7 +266,7 @@ class ValidationTests(TestCase):
|
|||
Data of the wrong type is not valid.
|
||||
"""
|
||||
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.assertTrue(isinstance(serializer.errors, list))
|
||||
|
||||
|
|
174
rest_framework/tests/serializer_bulk_update.py
Normal file
174
rest_framework/tests/serializer_bulk_update.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
"""
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class BulkUpdateSerializerTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
class Book(object):
|
||||
object_map = {}
|
||||
|
||||
def __init__(self, id, title, author):
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.author = author
|
||||
|
||||
def save(self):
|
||||
Book.object_map[self.id] = self
|
||||
|
||||
def delete(self):
|
||||
del Book.object_map[self.id]
|
||||
|
||||
class BookSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
title = serializers.CharField(max_length=100)
|
||||
author = serializers.CharField(max_length=100)
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
if instance:
|
||||
instance.id = attrs['id']
|
||||
instance.title = attrs['title']
|
||||
instance.author = attrs['author']
|
||||
return instance
|
||||
return Book(**attrs)
|
||||
|
||||
self.Book = Book
|
||||
self.BookSerializer = BookSerializer
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
for item in data:
|
||||
book = Book(item['id'], item['title'], item['author'])
|
||||
book.save()
|
||||
|
||||
def books(self):
|
||||
return self.Book.object_map.values()
|
||||
|
||||
def test_bulk_update_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': 2,
|
||||
'title': 'Kafka on the shore',
|
||||
'author': 'Haruki Murakami'
|
||||
}
|
||||
]
|
||||
serializer = self.BookSerializer(self.books(), data=data, many=True)
|
||||
self.assertEqual(serializer.is_valid(), True)
|
||||
self.assertEqual(serializer.data, data)
|
||||
serializer.save()
|
||||
new_data = self.BookSerializer(self.books(), many=True).data
|
||||
self.assertEqual(data, new_data)
|
||||
|
||||
def test_bulk_update_error(self):
|
||||
"""
|
||||
Correct bulk update serialization should return the input data.
|
||||
"""
|
||||
data = [
|
||||
{
|
||||
'id': 0,
|
||||
'title': 'The electric kool-aid acid test',
|
||||
'author': 'Tom Wolfe'
|
||||
}, {
|
||||
'id': 'foo',
|
||||
'title': 'Kafka on the shore',
|
||||
'author': 'Haruki Murakami'
|
||||
}
|
||||
]
|
||||
expected_errors = [
|
||||
{},
|
||||
{'id': ['Enter a whole number.']}
|
||||
]
|
||||
serializer = self.BookSerializer(self.books(), data=data, many=True)
|
||||
self.assertEqual(serializer.is_valid(), False)
|
||||
self.assertEqual(serializer.errors, expected_errors)
|
Loading…
Reference in New Issue
Block a user