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, return manager._insert([self], fields=fields, return_id=update_pk,
using=using, raw=raw) 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) using = using or router.db_for_write(self.__class__, instance=self)
assert self._get_pk_val() is not None, ( assert self._get_pk_val() is not None, (
"%s object can't be deleted because its %s attribute is set to 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 = Collector(using=using)
collector.collect([self]) collector.collect([self], keep_parents=keep_parents)
collector.delete() collector.delete()
delete.alters_data = True delete.alters_data = True

View File

@ -174,7 +174,7 @@ class Collector(object):
return [objs] return [objs]
def collect(self, objs, source=None, nullable=False, collect_related=True, 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 Adds 'objs' to the collection of objects to be deleted as well as all
parent instances. 'objs' must be a homogeneous iterable collection of 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 current model, rather than after. (Needed for cascading to parent
models, the one case in which the cascade follows the forwards models, the one case in which the cascade follows the forwards
direction of an FK rather than the reverse direction.) 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): if self.can_fast_delete(objs):
self.fast_deletes.append(objs) self.fast_deletes.append(objs)
@ -200,20 +203,21 @@ class Collector(object):
model = new_objs[0].__class__ model = new_objs[0].__class__
# Recursively collect concrete model's parent models, but not their if not keep_parents:
# related objects. These will be found by meta.get_fields() # Recursively collect concrete model's parent models, but not their
concrete_model = model._meta.concrete_model # related objects. These will be found by meta.get_fields()
for ptr in six.itervalues(concrete_model._meta.parents): concrete_model = model._meta.concrete_model
if ptr: for ptr in six.itervalues(concrete_model._meta.parents):
# FIXME: This seems to be buggy and execute a query for each if ptr:
# parent object fetch. We have the parent data in the obj, # FIXME: This seems to be buggy and execute a query for each
# but we don't have a nice way to turn that data into parent # parent object fetch. We have the parent data in the obj,
# object instance. # but we don't have a nice way to turn that data into parent
parent_objs = [getattr(obj, ptr.name) for obj in new_objs] # object instance.
self.collect(parent_objs, source=model, parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
source_attr=ptr.rel.related_name, self.collect(parent_objs, source=model,
collect_related=False, source_attr=ptr.rel.related_name,
reverse_dependency=True) collect_related=False,
reverse_dependency=True)
if collect_related: if collect_related:
for related in get_candidate_relations_to_delete(model._meta): 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 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 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 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()`` If you want customized deletion behavior, you can override the ``delete()``
method. See :ref:`overriding-model-methods` for more details. 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 Pickling objects
================ ================

View File

@ -151,6 +151,10 @@ Models
managers created by ``ForeignKey``, ``GenericForeignKey``, and managers created by ``ForeignKey``, ``GenericForeignKey``, and
``ManyToManyField``. ``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 CSRF
^^^^ ^^^^

View File

@ -349,6 +349,13 @@ class DeletionTests(TestCase):
self.assertFalse(S.objects.exists()) self.assertFalse(S.objects.exists())
self.assertFalse(T.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): class FastDeleteTests(TestCase):