Fixed #15579 -- Added ability to delete only child models in multi-table inheritance.

This commit is contained in:
Andriy Sokolovskiy 2015-02-07 01:16:26 +02:00 committed by Tim Graham
parent b9cb81570e
commit 81c2d9f60b
5 changed files with 41 additions and 18 deletions

View File

@ -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

View File

@ -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):

View File

@ -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 <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
================

View File

@ -151,6 +151,10 @@ Models
managers created by ``ForeignKey``, ``GenericForeignKey``, and
``ManyToManyField``.
* Added the ``keep_parents`` parameter to :meth:`Model.delete()
<django.db.models.Model.delete>` to allow deleting only a child's data in a
model that uses multi-table inheritance.
CSRF
^^^^

View File

@ -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):