.. doctest docs/dev/custom_actions.rst .. _dev.custom_actions: ====================== Writing custom actions ====================== A custom action is an action written by the application or plugin developer. Examples of custom actions defined by certain libraries: - The :class:`MoveUp ` and :class:`MoveDown ` actions of a :class:`Sequenced `. - The :class:`Duplicate ` action for creating a copy of the current row. - In :mod:`lino.mixins.printable`: :class:`DirectPrintAction `, :class:`CachedPrintAction `, :class:`ClearCacheAction ` - The :class:`ToggleChoice ` You can define actions - either on the Model or on the Table - either using the `dd.action` decorator on a method or by defining a custom subclass of :class:`Action ` (and adding an instance of this class to the Model or the Table) Defining custom actions ======================= Application developers can define new custom actions by (1) applying the :func:`action` decorator to a model or table method, or (2) by subclassing the :class:`Action` class and instantiating them as an attribute of the model or table. The :ref:`Polls tutorial ` has a usage example of the first approach: .. code-block:: python @dd.action(help_text="Click here to vote this.") def vote(self, ar): def yes(ar): self.votes += 1 self.save() return ar.success( "Thank you for voting %s" % self, "Voted!", refresh=True) if self.votes > 0: msg = "%s has already %d votes!" % (self, self.votes) msg += "\nDo you still want to vote for it?" return ar.confirm(yes, msg) return yes(ar) The :func:`@dd.action ` decorator can have keyword parameters to specify information about the action, e.g. :attr:`label `, :attr:`help_text ` and :attr:`required `. The action method itself must have the following signature:: def vote(self, ar, **kw): ... return ar.success(kw) Where ``ar`` is an :class:`ActionRequest ` instance that holds information about the web request which called the action. - :meth:`callback ` and :meth:`confirm ` lets you define a dialog with the user using callbacks. - :meth:`success ` and :meth:`error ` are possible return values where you can ask the client to do certain things. The :func:`action` decorator ============================ .. currentmodule:: lino.core.actions You can use the :func:`action` decorator to to define custom actions. Any arguments you pass to the decorator are forwarded to :meth:`Action.__init__`. In practice you'll possibly use: :attr:`label `, :attr:`help_text ` and :attr:`required_roles `. The :ref:`Lino Polls tutorial ` shows the simplest form of defining an action by adding the :func:`action ` decorator to a method. The following two approaches are equivalent. Using a custom class:: class MyAction(dd.Action): def run_from_ui(self, ar): # do something... class MyModel(dd.Model): my_action = MyAction() Using a decorator:: class MyModel(dd.Model): @dd.action() def my_action(self, ar): # do something... In above case (and in some real cases) it might look tedious and redundant to define an action class and then instantiate it on the model. But in general we recommend this more verbose approach. We use the primitive approach (just a method on the model) only in very simple cases. The advantages become visible e.g. when you have several similar actions and want them to inherit from a common base class. Or we can reuse a same action class on different models (most standard actions like :class:`lino.core.actions.ShowInsert` do this). Or we have actions where we use instances of a same class with different instance values (e.g. the :class:`lino.core.actions.ShowSlaveTable` action). Also an explicit separate class it is syntactically more readable. Example project =============== The :mod:`lino_book.projects.actions` project shows some methods of defining actions. Here is the :xfile:`models.py` file used for this small demo project: .. literalinclude:: /../../book/lino_book/projects/actions/models.py >>> from lino import startup >>> startup('lino_book.projects.actions.settings') >>> from lino.api.doctest import * >>> from lino_book.projects.actions.models import * To demonstrate this, we log in and instantiate an `Moo` object: >>> ses = rt.login() >>> obj = Moo() Running an action programmatically is done using the :meth:`run ` method of your session. Since `a` and `m` are defined on the Model, we can run them directly: >>> rv = ses.run(obj.a) >>> print(rv["message"]) Called a() on Moo object >>> rv["success"] True >>> print(ses.run(obj.m)['message']) Called m() on Moo object This wouldn't work for `t` and `b` since these are defined on `Moos` (which is only one of many possible tables on model `Moo`): >>> ses.run(obj.t) Traceback (most recent call last): ... AttributeError: 'Moo' object has no attribute 't' So in this case we need to specify them table as the first parameter. And because they are row actions, we need to pass the instance as mandatory first argument: >>> print(ses.run(S1.t, obj)['message']) Called t() on Moo object >>> print(ses.run(S1.b, obj)['message']) Called a() on Moo object How to "remove" an inherited action or collected from a table ------------------------------------------------------------- Here are the actions on Moos: >>> pprint([ba.action for ba in Moos.get_actions()]) [, , , , , , , , , ] A subclass inherits all actions from her parent. When I define a second table `S1(Moos)`, then `S1` will have both actions `m` and `t`: >>> pprint([ba.action for ba in S1.get_actions()]) [, , , , , , , , , ] S2 does not have these actions because we "removed" them by overriding them with None: >>> pprint([ba.action for ba in S2.get_actions()]) [, , , , , ] .. _dialog_actions: Dialog actions ============== When you specify `parameters` on a custom action, then your action becomes a "dialog action". When a user invokes a dialog action, Lino opens a dialog window which asks for the values of these parameters. The action itself is being run only when the user submits the dialog window. Examples of dialog actions: - users.Users.change_password - pcsw.Clients.refuse_client - countries.Places.merge_row - contacts.Persons.create_household - coachings.Coachings.create_visit - cal.Guests.checkin - lino_xl.lib.sales.VatProductInvoice.make_copy MakeCopy