diff options
| author | Tom Christie | 2013-03-22 21:57:37 +0000 | 
|---|---|---|
| committer | Tom Christie | 2013-03-22 21:57:37 +0000 | 
| commit | 9bf7c9b714713f7b2fe84074cfd05a8bc3ef4022 (patch) | |
| tree | 932d97342b9adc7a25b6620fde35d07184ed3c58 /rest_framework/serializers.py | |
| parent | deb5e653e441bf31f3b183b575f72e6b4cf537ea (diff) | |
| parent | 870d5c7d7810ecd7f187e13b5fe3a3bcba6b18c3 (diff) | |
| download | django-rest-framework-9bf7c9b714713f7b2fe84074cfd05a8bc3ef4022.tar.bz2 | |
Merge master
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 73 | 
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  | 
