From be129b38fac096c9ec29b938ffc706b05a91ad75 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 14 Feb 2014 13:46:58 +0100 Subject: [PATCH 1/7] Allow nested / bulk updates to finer tune their behavior by splitting add and remove actions. --- rest_framework/serializers.py | 48 ++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 10256d479..80112e49e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -34,6 +34,11 @@ from rest_framework.settings import api_settings from rest_framework.relations import * from rest_framework.fields import * +# nested mode constants +BATCH_ADD = 1 +BATCH_UPDATE = 2 +BATCH_DELETE = 4 + def _resolve_model(obj): """ @@ -178,17 +183,33 @@ class BaseSerializer(WritableField): _options_class = SerializerOptions _dict_class = SortedDictWithMetadata + batch_mode = BATCH_UPDATE def __init__(self, instance=None, data=None, files=None, context=None, partial=False, many=None, - allow_add_remove=False, **kwargs): + batch_mode=None, **kwargs): + allow_add_remove = kwargs.pop('allow_add_remove', None) super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) self.parent = None self.root = None self.partial = partial self.many = many - self.allow_add_remove = allow_add_remove + + # Handle allow_add_remove depreaction + if hasattr(self, 'allow_add_remove'): + warnings.warn('The `allow_add_remove` keyword argument is deprecated. ' + 'Use the `batch_mode` keyword argument instead.', + DeprecationWarning, stacklevel=2) + self.allow_add_remove = self.allow_add_remove + if 'allow_add_remove' in kwargs: + warnings.warn('The `allow_add_remove` keyword argument is deprecated. ' + 'Use the `batch_mode` keyword argument instead.', + DeprecationWarning, stacklevel=2) + self.allow_add_remove = allow_add_remove + + if batch_mode: + self.batch_mode = batch_mode self.context = context or {} @@ -204,9 +225,23 @@ class BaseSerializer(WritableField): if many and instance is not None and not hasattr(instance, '__iter__'): raise ValueError('instance should be a queryset or other iterable with many=True') - if allow_add_remove and not many: + if 'allow_add_remove' in kwargs and not many: raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') + # Compatibility between allow_add_remove and batch_mode + def set_allow_add_remove(self, value): + self.batch_mode = BATCH_UPDATE + if value: + self.batch_mode = BATCH_ADD | BATCH_UPDATE | BATCH_DELETE + + def get_allow_add_remove(self): + if self.batch_mode == BATCH_ADD | BATCH_UPDATE | BATCH_DELETE: + return True + if self.batch_mode == BATCH_UPDATE: + return False + + allow_add_remove = property(get_allow_add_remove, set_allow_add_remove) + ##### # Methods to determine which fields to use when (de)serializing objects. @@ -463,7 +498,8 @@ class BaseSerializer(WritableField): 'context': self.context, 'partial': self.partial, 'many': self.many, - 'allow_add_remove': self.allow_add_remove + 'allow_add_remove': self.allow_add_remove, + 'batch_mode': self.batch_mode } serializer = self.__class__(**kwargs) @@ -525,7 +561,7 @@ class BaseSerializer(WritableField): # Determine which object we're updating identity = self.get_identity(item) self.object = identity_to_objects.pop(identity, None) - if self.object is None and not self.allow_add_remove: + if self.object is None and not (self.batch_mode & BATCH_ADD): ret.append(None) errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) continue @@ -533,7 +569,7 @@ class BaseSerializer(WritableField): ret.append(self.from_native(item, None)) errors.append(self._errors) - if update and self.allow_add_remove: + if update and (self.batch_mode & BATCH_DELETE): ret._deleted = identity_to_objects.values() self._errors = any(errors) and errors or [] From ea7b76f943abd94e81003517b0751f3c310d505e Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 14 Feb 2014 15:44:59 +0100 Subject: [PATCH 2/7] Fixed regression & added comments. --- rest_framework/serializers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 80112e49e..3724b8112 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -196,13 +196,15 @@ class BaseSerializer(WritableField): self.partial = partial self.many = many - # Handle allow_add_remove depreaction + # Handle allow_add_remove depreaction if hasattr(self, 'allow_add_remove'): + # If we already have allow_add_remove, it's a class argument. + # By reassigning it we'll trigger the batch_mode configuration warnings.warn('The `allow_add_remove` keyword argument is deprecated. ' 'Use the `batch_mode` keyword argument instead.', DeprecationWarning, stacklevel=2) self.allow_add_remove = self.allow_add_remove - if 'allow_add_remove' in kwargs: + if allow_add_remove: warnings.warn('The `allow_add_remove` keyword argument is deprecated. ' 'Use the `batch_mode` keyword argument instead.', DeprecationWarning, stacklevel=2) From 041b3ece2b0364eed482ce55cc1b4d2cbfa50abb Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 14 Feb 2014 18:21:37 +0100 Subject: [PATCH 3/7] Added another test. --- .../tests/test_serializer_nested.py | 106 +++++++++++++++--- 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index 6d69ffbd0..00af3dd10 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -6,9 +6,45 @@ Doesn't cover model serializers. from __future__ import unicode_literals from django.test import TestCase from rest_framework import serializers +from rest_framework.serializers import BATCH_ADD, BATCH_UPDATE, BATCH_DELETE from . import models +class BlogPostCommentSerializer(serializers.ModelSerializer): + class Meta: + model = models.BlogPostComment + fields = ('id', 'text') + + +class BlogPostSerializer(serializers.ModelSerializer): + comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set') + class Meta: + model = models.BlogPost + fields = ('id', 'title', 'comments') + + +class PersonSerializer(serializers.ModelSerializer): + posts = BlogPostSerializer(many=True, source='blogpost_set') + class Meta: + model = models.Person + fields = ('id', 'name', 'age', 'posts') + + +class BlogPostCreateSerializer(serializers.ModelSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_ADD | BATCH_UPDATE) + class Meta: + model = models.BlogPost + fields = ('id', 'title', 'comments') + +class BlogPostDeleteSerializer(serializers.ModelSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_ADD | BATCH_UPDATE) + class Meta: + model = models.BlogPost + fields = ('id', 'title', 'comments') + + class WritableNestedSerializerBasicTests(TestCase): """ Tests for deserializing nested entities. @@ -316,32 +352,68 @@ class ForeignKeyNestedSerializerUpdateTests(TestCase): class NestedModelSerializerUpdateTests(TestCase): def test_second_nested_level(self): + """ + Make sure we can span relations for nested representations + """ john = models.Person.objects.create(name="john") post = john.blogpost_set.create(title="Test blog post") post.blogpostcomment_set.create(text="I hate this blog post") post.blogpostcomment_set.create(text="I love this blog post") - class BlogPostCommentSerializer(serializers.ModelSerializer): - class Meta: - model = models.BlogPostComment - - class BlogPostSerializer(serializers.ModelSerializer): - comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set') - class Meta: - model = models.BlogPost - fields = ('id', 'title', 'comments') - - class PersonSerializer(serializers.ModelSerializer): - posts = BlogPostSerializer(many=True, source='blogpost_set') - class Meta: - model = models.Person - fields = ('id', 'name', 'age', 'posts') - serialize = PersonSerializer(instance=john) deserialize = PersonSerializer(data=serialize.data, instance=john) - self.assertTrue(deserialize.is_valid()) + self.assertTrue(deserialize.is_valid(), deserialize.errors) result = deserialize.object result.save() self.assertEqual(result.id, john.id) + + def test_nested_creation_in_update_only_mode(self): + """ + Create nested + """ + post = models.BlogPost(title='Test blog post') + post.save() + data = { + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + }, { + 'text': 'I love this blog post', + }], + } + + serializer = BlogPostSerializer(data=data, instance=post) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, { + u'comments': [{ + u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + }, { + u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + }] + }) + + def test_updates_in_creation_mode(self): + """ + Create nested + """ + post = models.BlogPost(title='Test blog post') + post.save() + post.blogpostcomment_set.create(text="This should remain.") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + }, { + 'text': 'I love this blog post', + }], + } + + serializer = BlogPostCreateSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid(), serializer.errors) + post = serializer.save() + self.assertTrue(post.id) + self.assertEqual(post.blogpostcomment_set.count(), 3) + From 2d5ec548cf1752c98b0159bdb24bda1a1f4d90e4 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 14 Feb 2014 18:38:01 +0100 Subject: [PATCH 4/7] More test coverage. --- rest_framework/tests/test_serializer_nested.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index 00af3dd10..c543b4756 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -351,7 +351,7 @@ class ForeignKeyNestedSerializerUpdateTests(TestCase): class NestedModelSerializerUpdateTests(TestCase): - def test_second_nested_level(self): + def test_allows_second_nesting_level(self): """ Make sure we can span relations for nested representations """ @@ -368,6 +368,9 @@ class NestedModelSerializerUpdateTests(TestCase): result = deserialize.object result.save() self.assertEqual(result.id, john.id) + self.assertEqual( + [i.id for i in result.blogpost_set.all()], + [i.id for i in john.blogpost_set.all()]) def test_nested_creation_in_update_only_mode(self): """ From 79462676fd3b67c54c77b2ccff9c7a0319ed2f2c Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 14 Feb 2014 18:38:21 +0100 Subject: [PATCH 5/7] Refactored a bit the tests. --- .../tests/test_serializer_nested.py | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index c543b4756..966e26d0d 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -30,21 +30,6 @@ class PersonSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'age', 'posts') -class BlogPostCreateSerializer(serializers.ModelSerializer): - comments = BlogPostCommentSerializer(many=True, - source='blogpostcomment_set', batch_mode=BATCH_ADD | BATCH_UPDATE) - class Meta: - model = models.BlogPost - fields = ('id', 'title', 'comments') - -class BlogPostDeleteSerializer(serializers.ModelSerializer): - comments = BlogPostCommentSerializer(many=True, - source='blogpostcomment_set', batch_mode=BATCH_ADD | BATCH_UPDATE) - class Meta: - model = models.BlogPost - fields = ('id', 'title', 'comments') - - class WritableNestedSerializerBasicTests(TestCase): """ Tests for deserializing nested entities. @@ -372,13 +357,43 @@ class NestedModelSerializerUpdateTests(TestCase): [i.id for i in result.blogpost_set.all()], [i.id for i in john.blogpost_set.all()]) - def test_nested_creation_in_update_only_mode(self): + +class NestedModelSerializerCreationsTests(TestCase): + def test_in_batch_add_mode(self): """ - Create nested + Create nested while being in BATCH_ADD mode. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_ADD) + + post = models.BlogPost(title='Test blog post') + post.save() + post.blogpostcomment_set.create(text="This should remain.") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + }, { + 'text': 'I love this blog post', + }], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid(), serializer.errors) + post = serializer.save() + self.assertTrue(post.id) + self.assertEqual(post.blogpostcomment_set.count(), 3) + + def test_fails_in_batch_update_mode(self): + """ + Create nested while being in BATCH_UPDATE mode. """ post = models.BlogPost(title='Test blog post') post.save() data = { + 'id': post.id, 'title': 'Test blog post', 'comments': [{ 'text': 'I hate this blog post', @@ -397,26 +412,8 @@ class NestedModelSerializerUpdateTests(TestCase): }] }) - def test_updates_in_creation_mode(self): - """ - Create nested - """ - post = models.BlogPost(title='Test blog post') - post.save() - post.blogpostcomment_set.create(text="This should remain.") - data = { - 'id': post.id, - 'title': 'Test blog post', - 'comments': [{ - 'text': 'I hate this blog post', - }, { - 'text': 'I love this blog post', - }], - } - - serializer = BlogPostCreateSerializer(data=data, instance=post) - self.assertTrue(serializer.is_valid(), serializer.errors) - post = serializer.save() - self.assertTrue(post.id) - self.assertEqual(post.blogpostcomment_set.count(), 3) + def test_update_update_only_mode(self): + self.assertFalse(True) + def test_update_delete_only_mode(self): + self.assertFalse(True) From 6442053debf8c7d28b94cf4007fe1f85e1628089 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sat, 15 Feb 2014 00:26:08 +0100 Subject: [PATCH 6/7] Fixed the tests and added a non update mode. --- rest_framework/serializers.py | 4 + .../tests/test_serializer_nested.py | 207 +++++++++++++++++- 2 files changed, 202 insertions(+), 9 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 3724b8112..6fd9841ef 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -567,6 +567,10 @@ class BaseSerializer(WritableField): ret.append(None) errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) continue + if self.object is not None and not (self.batch_mode & BATCH_UPDATE): + ret.append(None) + errors.append({'non_field_errors': ['Cannot update an item.']}) + continue ret.append(self.from_native(item, None)) errors.append(self._errors) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index 966e26d0d..dd0fa80a8 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -359,9 +359,9 @@ class NestedModelSerializerUpdateTests(TestCase): class NestedModelSerializerCreationsTests(TestCase): - def test_in_batch_add_mode(self): + def test_works_in_batch_add_mode(self): """ - Create nested while being in BATCH_ADD mode. + Create nested while being in BATCH_ADD mode works. """ class LocalSerializer(BlogPostSerializer): comments = BlogPostCommentSerializer(many=True, @@ -369,7 +369,6 @@ class NestedModelSerializerCreationsTests(TestCase): post = models.BlogPost(title='Test blog post') post.save() - post.blogpostcomment_set.create(text="This should remain.") data = { 'id': post.id, 'title': 'Test blog post', @@ -384,11 +383,11 @@ class NestedModelSerializerCreationsTests(TestCase): self.assertTrue(serializer.is_valid(), serializer.errors) post = serializer.save() self.assertTrue(post.id) - self.assertEqual(post.blogpostcomment_set.count(), 3) + self.assertEqual(post.blogpostcomment_set.count(), 2) def test_fails_in_batch_update_mode(self): """ - Create nested while being in BATCH_UPDATE mode. + Create nested while being in BATCH_UPDATE mode fails. """ post = models.BlogPost(title='Test blog post') post.save() @@ -412,8 +411,198 @@ class NestedModelSerializerCreationsTests(TestCase): }] }) - def test_update_update_only_mode(self): - self.assertFalse(True) + def test_fails_in_batch_delete_mode(self): + """ + Create nested while being in BATCH_DELETE mode fails. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_DELETE) - def test_update_delete_only_mode(self): - self.assertFalse(True) + post = models.BlogPost(title='Test blog post') + post.save() + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + }, { + 'text': 'I love this blog post', + }], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, { + u'comments': [{ + u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + }, { + u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + }] + }) + + +class NestedModelSerializerUpdatesTests(TestCase): + def test_fails_in_batch_add_mode(self): + """ + Update nested while being in BATCH_ADD mode fails. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_ADD) + + post = models.BlogPost(title='Test blog post') + post.save() + comment1 = post.blogpostcomment_set.create(text="I hate this blog post") + comment2 = post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'id': comment1.id, + 'text': 'I hate this blog post :p', + }, { + 'id': comment2.id, + 'text': 'I love this blog post :p', + }], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, { + u'comments': [{ + u'non_field_errors': [u'Cannot update an item.'] + }, { + u'non_field_errors': [u'Cannot update an item.'] + }] + }) + + def test_fails_in_batch_update_mode(self): + """ + Update nested while being in BATCH_UPDATE mode works. + """ + post = models.BlogPost(title='Test blog post') + post.save() + comment1 = post.blogpostcomment_set.create(text="I hate this blog post") + comment2 = post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'id': comment1.id, + 'text': 'I hate this blog post :p', + }, { + 'id': comment2.id, + 'text': 'I love this blog post :p', + }], + } + + serializer = BlogPostSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid(), serializer.errors) + post = serializer.save() + self.assertTrue(post.id) + self.assertEqual(post.blogpostcomment_set.count(), 2) + self.assertEqual( + set(i['text'] for i in data['comments']), + set(i.text for i in post.blogpostcomment_set.all().order_by('id')) + ) + + def test_fails_in_batch_delete_mode(self): + """ + Update nested while being in BATCH_DELETE mode fails. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_DELETE) + + post = models.BlogPost(title='Test blog post') + post.save() + comment1 = post.blogpostcomment_set.create(text="I hate this blog post") + comment2 = post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [{ + 'id': comment1.id, + 'text': 'I hate this blog post :p', + }, { + 'id': comment2.id, + 'text': 'I love this blog post :p', + }], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, { + u'comments': [{ + u'non_field_errors': [u'Cannot update an item.'] + }, { + u'non_field_errors': [u'Cannot update an item.'] + }] + }) + +class NestedModelSerializerDeletesTests(TestCase): + def test_fails_in_batch_add_mode(self): + """ + Delete nested while being in BATCH_ADD mode doesn't delete. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_ADD) + + post = models.BlogPost(title='Test blog post') + post.save() + post.blogpostcomment_set.create(text="I hate this blog post") + post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid()) + post = serializer.save() + self.assertEqual(post.blogpostcomment_set.count(), 2) + + def test_fails_in_batch_update_mode(self): + """ + Delete nested while being in BATCH_UPDATE mode doesn't delete. + """ + post = models.BlogPost(title='Test blog post') + post.save() + post.blogpostcomment_set.create(text="I hate this blog post") + post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [], + } + + serializer = BlogPostSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid(), serializer.errors) + post = serializer.save() + self.assertEqual(post.blogpostcomment_set.count(), 2) + + def test_fails_in_batch_delete_mode(self): + """ + Delete nested while being in BATCH_DELETE mode works. + """ + class LocalSerializer(BlogPostSerializer): + comments = BlogPostCommentSerializer(many=True, + source='blogpostcomment_set', batch_mode=BATCH_DELETE) + + post = models.BlogPost(title='Test blog post') + post.save() + post.blogpostcomment_set.create(text="I hate this blog post") + post.blogpostcomment_set.create(text="I love this blog post") + data = { + 'id': post.id, + 'title': 'Test blog post', + 'comments': [], + } + + serializer = LocalSerializer(data=data, instance=post) + self.assertTrue(serializer.is_valid(), serializer.errors) + post = serializer.save() + self.assertEqual(post.blogpostcomment_set.count(), 0) From d4d80bdff5d03a397f2354f94363dcad32ba86c7 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 19 Feb 2014 12:49:35 +0100 Subject: [PATCH 7/7] Removed the remaining traces from explicit unicode. --- .../tests/test_serializer_nested.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index dd0fa80a8..c8f0e67dd 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -404,10 +404,10 @@ class NestedModelSerializerCreationsTests(TestCase): serializer = BlogPostSerializer(data=data, instance=post) self.assertFalse(serializer.is_valid()) self.assertEqual(serializer.errors, { - u'comments': [{ - u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + 'comments': [{ + 'non_field_errors': ['Cannot create a new item, only existing items may be updated.'] }, { - u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + 'non_field_errors': ['Cannot create a new item, only existing items may be updated.'] }] }) @@ -434,10 +434,10 @@ class NestedModelSerializerCreationsTests(TestCase): serializer = LocalSerializer(data=data, instance=post) self.assertFalse(serializer.is_valid()) self.assertEqual(serializer.errors, { - u'comments': [{ - u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + 'comments': [{ + 'non_field_errors': ['Cannot create a new item, only existing items may be updated.'] }, { - u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.'] + 'non_field_errors': ['Cannot create a new item, only existing items may be updated.'] }] }) @@ -470,10 +470,10 @@ class NestedModelSerializerUpdatesTests(TestCase): serializer = LocalSerializer(data=data, instance=post) self.assertFalse(serializer.is_valid()) self.assertEqual(serializer.errors, { - u'comments': [{ - u'non_field_errors': [u'Cannot update an item.'] + 'comments': [{ + 'non_field_errors': ['Cannot update an item.'] }, { - u'non_field_errors': [u'Cannot update an item.'] + 'non_field_errors': ['Cannot update an item.'] }] }) @@ -534,10 +534,10 @@ class NestedModelSerializerUpdatesTests(TestCase): serializer = LocalSerializer(data=data, instance=post) self.assertFalse(serializer.is_valid()) self.assertEqual(serializer.errors, { - u'comments': [{ - u'non_field_errors': [u'Cannot update an item.'] + 'comments': [{ + 'non_field_errors': ['Cannot update an item.'] }, { - u'non_field_errors': [u'Cannot update an item.'] + 'non_field_errors': ['Cannot update an item.'] }] })