Deletion
========

.. versionadded:: 4.5.0

There is nothing special about deleting polymorphic models. The same rules apply as to
:ref:`the deletion of normal Django models <topics-db-queries-delete>` that have parent/child
relationships up and down a model inheritance hierarchy. Django must walk the model inheritance and
relationship graph and collect all of the affected objects so that it can correctly order deletion
SQL statements to respect database constraints and issue signals.

The polymorphic deletion logic is the same as the normal Django deletion logic because Django
already walks the model inheritance hierarchy. :class:`~polymorphic.query.PolymorphicQuerySet` and
:class:`~polymorphic.managers.PolymorphicManager` disrupt this process by confusing Django's graph
walker by returning concrete subclass instances instead of base class instances when it attempts to
walk reverse relationships to polymorphic models. To prevent this confusion,
:pypi:`django-polymorphic` wraps the :attr:`~django.db.models.ForeignKey.on_delete` handlers of
reverse relations to polymorphic models with :class:`~polymorphic.deletion.PolymorphicGuard`
which disables polymorphic behavior on the related querysets during collection.

**You may define your polymorphic models as you normally would using the standard Django**
:attr:`~django.db.models.ForeignKey.on_delete` **actions**.
:class:`~polymorphic.models.PolymorphicModel` will automatically wrap the actions for you. actions
wrapped with :class:`~polymorphic.deletion.PolymorphicGuard` serialize in migrations as the
underlying wrapped action. This ensures migrations generated by versions of
:pypi:`django-polymorphic` after 4.5.0 should be the same as with prior versions. The guard is also
unnecessary during migrations because Django generates basic managers instead of using the default
polymorphic managers.

It is a design goal of :pypi:`django-polymorphic` that deletion should just work without any special
treatment. However if you encounter attribute errors or database integrity errors during deletion
you may manually wrap the :attr:`~django.db.models.ForeignKey.on_delete` action of reverse relations
to polymorphic models with :class:`~polymorphic.deletion.PolymorphicGuard` to disable polymorphic
behavior during deletion collection. If you encounter an issue like this
`please report it to us <https://github.com/django-commons/django-polymorphic/issues>`_. For example:

.. code-block:: python

    from polymorphic.models import PolymorphicModel
    from polymorphic.deletion import PolymorphicGuard
    from django.db import models

    class MyModel(models.Model):
        # ...

    class RelatedModel(PolymorphicModel):
        my_model = models.ForeignKey(
            MyModel,
            on_delete=PolymorphicGuard(models.CASCADE),
        )

Deleting Children (upcasting)
-----------------------------

When deleting a polymorphic model instance, you can choose to keep the parent model instances by
passing the ``keep_parents=True`` argument to the
:meth:`~polymorphic.models.PolymorphicModel.delete` method. This will delete only the subclass
instance, and leave the parent instances intact. :pypi:`django-polymorphic` will ensure that the
``polymorphic_ctype`` fields of the parent instances are updated accordingly to reflect their new
concrete model type.

.. tip::

    You can delete multiple levels of child rows by deleting the model from the desired parent
    level. For example, if you have a model inheritance hierarchy of ``Base -> ChildA -> ChildB``,
    and you delete a ``ChildB`` row from its parent model ``ChildA`` instance both the ``ChildA``
    and ``ChildB`` rows will be deleted leaving a concrete row type of ``Base``.
