diff options
| author | Tom Christie | 2013-03-19 14:26:48 +0000 |
|---|---|---|
| committer | Tom Christie | 2013-03-19 14:26:48 +0000 |
| commit | b2dc66448503c2120d943a2f282eab235afc67ba (patch) | |
| tree | 8115409050b9cc13eac7e6f1e32f7e6014006692 /rest_framework | |
| parent | 09e4ee7ae332326e77b23bac1539d31e582419e9 (diff) | |
| download | django-rest-framework-b2dc66448503c2120d943a2f282eab235afc67ba.tar.bz2 | |
Basic bulk create and bulk update
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/serializers.py | 38 | ||||
| -rw-r--r-- | rest_framework/tests/serializer.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/serializer_bulk_update.py | 174 |
3 files changed, 213 insertions, 1 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4fe857a6..34120dc6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -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 diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index beb372c2..9c0fdd78 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -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)) diff --git a/rest_framework/tests/serializer_bulk_update.py b/rest_framework/tests/serializer_bulk_update.py new file mode 100644 index 00000000..66fca883 --- /dev/null +++ b/rest_framework/tests/serializer_bulk_update.py @@ -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) |
