.. _dev.delete: ============================= Customize delete behaviour ============================= This section is a topic guide about how to customize behaviour around deleting records. Unlike Django, Lino has PROTECT as the default on_delete strategy in ForeignKey fields. If you want CASCADE, then you specify it explicitly using the :attr:`allow_cascaded_delete ` attribute on the model whose instances will be deleted. The :meth:`disable_delete ` method of a model decides whether a given database object may be deleted or not. Also the :meth:`disable_delete ` method of an actor. The :attr:`disable_delete` item in :attr:`data_record ` is a "preview" of whether that row can be deleted or not. The front end may use this information to disable or enable its delete button. But the :class:`DeleteSelected ` action will verify again before actually deleting a row. When Lino analyzes the application's models at startup, it adds a "disable_delete handler" (:mod:`lino.core.ddh`) to every model. The :meth:`lino.utils.diag.Analyzer.show_foreign_keys` can help to find examples for writing tests. It is used in specs like :ref:`noi.specs.ddh` or :ref:`voga.specs.db_roger`. Examples see :doc:`/specs/noi/ddh`. .. _lino.tested.ddh: .. currentmodule:: lino.core.model The ``disable_delete`` method ============================= To customize whether a :term:`database row` can be deleted or not, you override the :meth:`Model.disable_delete` method. When a user has view and write permission to an actor, they usually also have permission to delete individual :term:`database rows ` using the |delete| button in the toolbar. But before actually deleting a row, Lino will call the :meth:`disable_delete ` method do decide whether the action will actually run. .. class:: Model :noindex: .. method:: disable_delete(self, ar) Return `None` when there is no veto against deleting this :term:`database row`, otherwise a translatable message that explains to the user why they can't delete this row. As an example, here is how the :meth:`disable_delete ` method of :class:`lino_xl.lib.cal.GuestsByEvent` table adds a customized veto message to refuse deleting the presence of a guest in a calendar event for which Lino manages presences automatically :: @classmethod def disable_delete(cls, obj, ar): msg = super(GuestsByEvent, cls).disable_delete(obj, ar) if msg is not None: return msg mi = ar.master_instance assert mi == obj.event if mi.can_edit_guests_manually(): return None return _("Cannot edit guests manually.") When you override this method, be careful to call :func:`super` because Lino finds a lot of veto reasons automatically for you by checking whether the database contains related objects. For example. Lino by default forbids to delete any object that is referenced by other objects. Users will get a message of type "Cannot delete X because n Ys refer to it". See `About cascaded deletes`_ below for customizing this behaviour. About cascaded deletes ====================== Lino changes Django's default behaviour regarding cascaded delete on ForeignKey fields. You can set the :attr:`allow_cascaded_copy ` and :attr:`allow_cascaded_copy ` class attributes of a model to customize this behaviour. With Lino, unlike plain Django, you control cascaded delete behaviour on the model whose instances are going to be deleted instead of defining it on the models that refer to it. So you usually don't need to care about Django's `on_delete `__ attribute, Lino automatically (in :meth:`kernel_startup `) sets this to ``PROTECT`` for all FK fields that are not listed in the :attr:`allow_cascaded_delete` of their model. .. class:: Model :noindex: .. attribute:: allow_cascaded_delete A set of names of `ForeignKey` or `GenericForeignKey` fields of this model that allow for cascaded delete. If this is a simple string, Lino expects it to be a space-separated list of field names and convert it into a set at startup. This is also used by :class:`lino.mixins.duplicable.Duplicate` to decide whether slaves of a record being duplicated should be duplicated as well. Example: Lino should not refuse to delete a Mail just because it has some Recipient. When deleting a Mail, Lino should also delete its Recipients. That's why :class:`lino_xl.lib.outbox.models.Recipient` has ``allow_cascaded_delete = 'mail'``.