mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-03 20:10:10 +03:00
Merge 066e4402c4
into 5333a93126
This commit is contained in:
commit
613899e58e
|
@ -35,6 +35,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):
|
||||
"""
|
||||
|
@ -179,17 +184,35 @@ 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'):
|
||||
# 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:
|
||||
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 {}
|
||||
|
||||
|
@ -205,9 +228,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.
|
||||
|
||||
|
@ -464,7 +501,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)
|
||||
|
||||
|
@ -526,15 +564,19 @@ 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
|
||||
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)
|
||||
|
||||
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 []
|
||||
|
|
|
@ -6,9 +6,30 @@ 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 WritableNestedSerializerBasicTests(TestCase):
|
||||
"""
|
||||
Tests for deserializing nested entities.
|
||||
|
@ -315,33 +336,273 @@ 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
|
||||
"""
|
||||
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)
|
||||
self.assertEqual(
|
||||
[i.id for i in result.blogpost_set.all()],
|
||||
[i.id for i in john.blogpost_set.all()])
|
||||
|
||||
|
||||
class NestedModelSerializerCreationsTests(TestCase):
|
||||
def test_works_in_batch_add_mode(self):
|
||||
"""
|
||||
Create nested while being in BATCH_ADD mode works.
|
||||
"""
|
||||
class LocalSerializer(BlogPostSerializer):
|
||||
comments = BlogPostCommentSerializer(many=True,
|
||||
source='blogpostcomment_set', batch_mode=BATCH_ADD)
|
||||
|
||||
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.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
post = serializer.save()
|
||||
self.assertTrue(post.id)
|
||||
self.assertEqual(post.blogpostcomment_set.count(), 2)
|
||||
|
||||
def test_fails_in_batch_update_mode(self):
|
||||
"""
|
||||
Create nested while being in BATCH_UPDATE mode fails.
|
||||
"""
|
||||
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 = BlogPostSerializer(data=data, instance=post)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors, {
|
||||
'comments': [{
|
||||
'non_field_errors': ['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.']
|
||||
}]
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
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, {
|
||||
'comments': [{
|
||||
'non_field_errors': ['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.']
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
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, {
|
||||
'comments': [{
|
||||
'non_field_errors': ['Cannot update an item.']
|
||||
}, {
|
||||
'non_field_errors': ['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, {
|
||||
'comments': [{
|
||||
'non_field_errors': ['Cannot update an item.']
|
||||
}, {
|
||||
'non_field_errors': ['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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user