diff --git a/django/db/models/base.py b/django/db/models/base.py index cb1d37f9df..07a4f5f7e1 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -827,7 +827,7 @@ class Model(six.with_metaclass(ModelBase)): return manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw) - def delete(self, using=None): + def delete(self, using=None, keep_parents=False): using = using or router.db_for_write(self.__class__, instance=self) assert self._get_pk_val() is not None, ( "%s object can't be deleted because its %s attribute is set to None." % @@ -835,7 +835,7 @@ class Model(six.with_metaclass(ModelBase)): ) collector = Collector(using=using) - collector.collect([self]) + collector.collect([self], keep_parents=keep_parents) collector.delete() delete.alters_data = True diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 02b8cf3c27..23bc55ae5d 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -174,7 +174,7 @@ class Collector(object): return [objs] def collect(self, objs, source=None, nullable=False, collect_related=True, - source_attr=None, reverse_dependency=False): + source_attr=None, reverse_dependency=False, keep_parents=False): """ Adds 'objs' to the collection of objects to be deleted as well as all parent instances. 'objs' must be a homogeneous iterable collection of @@ -189,6 +189,9 @@ class Collector(object): current model, rather than after. (Needed for cascading to parent models, the one case in which the cascade follows the forwards direction of an FK rather than the reverse direction.) + + If 'keep_parents' is False, data of parent's models will be not + deleted. """ if self.can_fast_delete(objs): self.fast_deletes.append(objs) @@ -200,20 +203,21 @@ class Collector(object): model = new_objs[0].__class__ - # Recursively collect concrete model's parent models, but not their - # related objects. These will be found by meta.get_fields() - concrete_model = model._meta.concrete_model - for ptr in six.itervalues(concrete_model._meta.parents): - if ptr: - # FIXME: This seems to be buggy and execute a query for each - # parent object fetch. We have the parent data in the obj, - # but we don't have a nice way to turn that data into parent - # object instance. - parent_objs = [getattr(obj, ptr.name) for obj in new_objs] - self.collect(parent_objs, source=model, - source_attr=ptr.rel.related_name, - collect_related=False, - reverse_dependency=True) + if not keep_parents: + # Recursively collect concrete model's parent models, but not their + # related objects. These will be found by meta.get_fields() + concrete_model = model._meta.concrete_model + for ptr in six.itervalues(concrete_model._meta.parents): + if ptr: + # FIXME: This seems to be buggy and execute a query for each + # parent object fetch. We have the parent data in the obj, + # but we don't have a nice way to turn that data into parent + # object instance. + parent_objs = [getattr(obj, ptr.name) for obj in new_objs] + self.collect(parent_objs, source=model, + source_attr=ptr.rel.related_name, + collect_related=False, + reverse_dependency=True) if collect_related: for related in get_candidate_relations_to_delete(model._meta): diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 8f55104357..162384c3c7 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -533,7 +533,7 @@ value, the field will be added to the updated fields. Deleting objects ================ -.. method:: Model.delete([using=DEFAULT_DB_ALIAS]) +.. method:: Model.delete([using=DEFAULT_DB_ALIAS, keep_parents=False]) Issues an SQL ``DELETE`` for the object. This only deletes the object in the database; the Python instance will still exist and will still have data in @@ -545,6 +545,14 @@ For more details, including how to delete objects in bulk, see If you want customized deletion behavior, you can override the ``delete()`` method. See :ref:`overriding-model-methods` for more details. +Sometimes with :ref:`multi-table inheritance ` you may +want to delete only a child model's data. Specifying ``keep_parents=True`` will +keep the parent model's data. + +.. versionchanged:: 1.9 + + The ``keep_parents`` parameter was added. + Pickling objects ================ diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 1f9caa1145..2c787fc40a 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -151,6 +151,10 @@ Models managers created by ``ForeignKey``, ``GenericForeignKey``, and ``ManyToManyField``. +* Added the ``keep_parents`` parameter to :meth:`Model.delete() + ` to allow deleting only a child's data in a + model that uses multi-table inheritance. + CSRF ^^^^ diff --git a/tests/delete/tests.py b/tests/delete/tests.py index 24d5aeabea..9465340d0b 100644 --- a/tests/delete/tests.py +++ b/tests/delete/tests.py @@ -349,6 +349,13 @@ class DeletionTests(TestCase): self.assertFalse(S.objects.exists()) self.assertFalse(T.objects.exists()) + def test_delete_with_keeping_parents(self): + child = RChild.objects.create() + parent_id = child.r_ptr_id + child.delete(keep_parents=True) + self.assertFalse(RChild.objects.filter(id=child.id).exists()) + self.assertTrue(R.objects.filter(id=parent_id).exists()) + class FastDeleteTests(TestCase):