aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/serializers.py
diff options
context:
space:
mode:
authorTom Christie2013-03-22 21:57:37 +0000
committerTom Christie2013-03-22 21:57:37 +0000
commit9bf7c9b714713f7b2fe84074cfd05a8bc3ef4022 (patch)
tree932d97342b9adc7a25b6620fde35d07184ed3c58 /rest_framework/serializers.py
parentdeb5e653e441bf31f3b183b575f72e6b4cf537ea (diff)
parent870d5c7d7810ecd7f187e13b5fe3a3bcba6b18c3 (diff)
downloaddjango-rest-framework-9bf7c9b714713f7b2fe84074cfd05a8bc3ef4022.tar.bz2
Merge master
Diffstat (limited to 'rest_framework/serializers.py')
-rw-r--r--rest_framework/serializers.py73
1 files changed, 68 insertions, 5 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index a81cbc29..26c34044 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -129,13 +129,15 @@ class BaseSerializer(WritableField):
_dict_class = SortedDictWithMetadata
def __init__(self, instance=None, data=None, files=None,
- context=None, partial=False, many=None, **kwargs):
+ context=None, partial=False, many=None,
+ allow_delete=False, **kwargs):
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_delete = allow_delete
self.context = context or {}
@@ -147,6 +149,13 @@ class BaseSerializer(WritableField):
self._data = None
self._files = None
self._errors = None
+ self._deleted = None
+
+ 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_delete and not many:
+ raise ValueError('allow_delete should only be used for bulk updates, but you have not set many=True')
#####
# Methods to determine which fields to use when (de)serializing objects.
@@ -387,6 +396,20 @@ class BaseSerializer(WritableField):
# Propagate errors up to our parent
raise NestedValidationError(serializer.errors)
+ def get_identity(self, data):
+ """
+ This hook is required for bulk update.
+ It is used to determine the canonical identity of a given object.
+
+ Note that the data has not been validated at this point, so we need
+ to make sure that we catch any cases of incorrect datatypes being
+ passed to this method.
+ """
+ try:
+ return data.get('id', None)
+ except AttributeError:
+ return None
+
@property
def errors(self):
"""
@@ -408,10 +431,33 @@ class BaseSerializer(WritableField):
if many:
ret = []
errors = []
- for item in data:
- ret.append(self.from_native(item, None))
- errors.append(self._errors)
- self._errors = any(errors) and errors or []
+ 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))
+
+ if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)):
+ for item in data:
+ if update:
+ # Determine which object we're updating
+ identity = self.get_identity(item)
+ 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:
+ self._errors = {'non_field_errors': ['Expected a list of items']}
else:
ret = self.from_native(data, files)
@@ -450,6 +496,9 @@ class BaseSerializer(WritableField):
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.
@@ -458,6 +507,10 @@ class BaseSerializer(WritableField):
[self.save_object(item, **kwargs) for item in self.object]
else:
self.save_object(self.object, **kwargs)
+
+ if self.allow_delete and self._deleted:
+ [self.delete_object(item) for item in self._deleted]
+
return self.object
@@ -765,3 +818,13 @@ class HyperlinkedModelSerializer(ModelSerializer):
'many': to_many
}
return HyperlinkedRelatedField(**kwargs)
+
+ def get_identity(self, data):
+ """
+ This hook is required for bulk update.
+ We need to override the default, to use the url as the identity.
+ """
+ try:
+ return data.get('url', None)
+ except AttributeError:
+ return None