diff options
| author | Tom Christie | 2014-11-06 10:34:59 +0000 | 
|---|---|---|
| committer | Tom Christie | 2014-11-06 10:34:59 +0000 | 
| commit | ed541864e637681e1aca3a808be1f26202b4c271 (patch) | |
| tree | 1265e7355052b4c3dc78f0ecef16aad5540b5437 | |
| parent | 73daf407150b4d0c456b529c4d9bba0e035fc370 (diff) | |
| download | django-rest-framework-ed541864e637681e1aca3a808be1f26202b4c271.tar.bz2 | |
Support for bulk create. Closes #1965.
| -rw-r--r-- | rest_framework/fields.py | 2 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 65 | ||||
| -rw-r--r-- | tests/test_fields.py | 2 | ||||
| -rw-r--r-- | tests/test_serializer_bulk_update.py | 240 | 
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): | 
