Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
How plugins build the application menu¶
We have seen the application menu. And we have mentioned the Lino Extensions Library, a library of shared plugins that are designed to work together. One aspect of this collaboration is how to integrate the functionalities into the application menu.
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.tera1.settings.demo')
>>> from lino.api.doctest import *
The lino_book.projects.tera1
project has the following menu:
>>> show_menu('robin')
- Office : My Excerpts, My Upload files
- Therapies : My Therapies, -, Individual therapies, Life groups, Other groups, -, My Patients, My Notes
- Contacts : Persons, Organizations, Households, Patients, Partner Lists
- Calendar : My appointments, Overdue appointments, My unconfirmed appointments, My tasks, My guests, My presences, My overdue appointments, My cash roll, Calendar
- Invoicing : My invoicing plan, Sales invoices (SLS), Sales credit notes (SLC)
- Accounting :
- Purchases : Purchase invoices (PRC)
- Wages : Paychecks (SAL)
- Financial : Bestbank Payment Orders (PMO), Cash book (CSH), Bestbank (BNK)
- VAT : VAT declarations (VAT)
- Miscellaneous transactions : Miscellaneous transactions (MSC), Preliminary transactions (PRE)
- Reports :
- Therapies : Status Report
- Invoicing : Due invoices
- Accounting : Purchase journal (analytic), Analytic Account Balances, Accounting Report, Debtors, Creditors
- VAT : Intra-Community purchases, Intra-Community sales
- Configure :
- System : Users, Divisions, Site Parameters, System tasks
- Office : Excerpt Types, My Text Field Templates, Library volumes, Upload types
- Therapies : Dossier types, Topics, Note Types, Event Types
- Places : Countries, Places
- Contacts : Legal forms, Functions, Household Types, Healthcare plans, Healthcare rules, Procurers, Life modes, List Types
- Clients : Client Contact types
- Invoicing : Fees, Cash daybooks, Price rules, Paper types, Flatrates, Follow-up rules, Invoicing tasks
- Calendar : Calendars, Rooms, Recurring events, Guest roles, Service types, Recurrency policies, Remote Calendars, Planner rows
- Accounting : Analytical accounts, Sheet items, Fiscal years, Accounting periods, Accounts, Journals, Payment terms
- Topics : Topics
- Explorer :
- System : Authorities, User types, User roles, content types, All dashboard widgets, Background procedures, Data checkers, Data problem messages
- Office : Excerpts, Text Field Templates, Mentions, Upload files, Upload areas
- Therapies : Dossiers, Enrolments, Enrolment states, Course layouts, Dossier states, Notes
- Contacts : Contact persons, Partners, Household member roles, Household Members, Healthcare tariffs, Healthcare situations, List memberships
- Clients : Client Contacts, Known contact types
- Invoicing : Price factors, Sales invoices, Sales invoice items, Invoicing plans, Sales rules
- Calendar : Calendar entries, Tasks, Presences, Subscriptions, Entry states, Presence states, Task states, Planner columns, Display colors
- SEPA : Bank accounts
- Financial : Bank Statements, Journal Entries, Payment Orders
- Accounting : Purchase invoices, Accounting Reports, Common sheet items, General account balances, Analytic accounts balances, Partner balances, Sheet item entries, Common accounts, Match rules, Vouchers, Voucher types, Movements, Trade types, Journal groups
- Topics : Tags
- VAT : Special Belgian VAT declarations, Declaration fields, VAT areas, VAT regimes, VAT classes, VAT columns, Invoices, VAT rules
- Site : About, User sessions
This menu is not defined by overriding the setup_menu
method. It was “automatically” generated by
the installed plugins. Because as an application developer you wouldn’t
want to maintain such a menu for each application.
Let’s explore how this works.
The top-level menus¶
The default implementation of setup_menu
uses a set of predefined top-level menus:
- Main menu¶
The root menu of the application menu, which contains a series of sub-menus called top-level menus.
- Master menu¶
A standard top-level menu meant to contain commands for editing “master data”, i.e. data that is visible to normal users but doesn’t change very often.
- Configuration menu¶
A standard top-level menu meant to contain commands for editing “configuration data”. Actions in this menu usually require some Staff or Admin role.
- Explorer menu¶
A standard top-level menu meant to contain commands for editing “explorer data”. These actions are not meant to be generally useful, but might be interesting when exploring database content.
- Site menu¶
A standard top-level menu meant to contain commands about this site.
These predefined top-level menus are stored in the
top_level_menus
attribute of your
application.
>>> pprint(settings.SITE.top_level_menus)
[('master', 'Master'),
('main', None),
('reports', 'Reports'),
('config', 'Configure'),
('explorer', 'Explorer'),
('site', 'Site')]
It is a list of tuples with 2 items each. Each tuple has an internal name and a display name. Only the main entry has no display name.
You might specify your own set of top-level menus, but the Lino Extensions Library assumes that your application uses the default value described above.
These top-level menus are “filled” by the different installed plugins.
When a Lino application starts up, it loops over the installed plugins and, for
each of them, loops again over these top-level menus and checks whether the
plugin has a method called setup_XXX_menu()
(where XXX is the top-level
menu name).
As a result, with the default top_level_menus
, each plugin is checked whether it has
one of the following methods:
setup_master_menu
setup_main_menu
setup_reports_menu
setup_config_menu
setup_explorer_menu
setup_site_menu
Introduction to menu groups¶
The next level after the top-level menu are the menu groups.
- menu group¶
The group to which a plugin belongs in the application menu.
As long as the application developer doesn’t say anything, The default rule is
that each plugin creates its own menu group, labelled using the plugin’s
verbose_name
attribute.
But this internal grouping does not always match how the end users would think about their application.
The application developer can define an explicit
menu_group
.
This is the name of another plugin.
When a plugin was automatically installed because some other plugin needs it (as
given by needs_plugins
), then it
gets grouped together with this other plugin.
When a plugin A is automatically being installed because needed by a plugin B
which is itself being installed automatically because needed by a third plugin
C, then A.get_menu_group
returns C (and not B). A case where this happens is
lino_welfare.modlib.pcsw
, which needs lino_xl.lib.coachings
, which
in turn needs lino_xl.lib.clients
.
Defining menu items¶
The setup_XXX_menu()
methods of a lino.core.plugin.Plugin
are
expected to add menu items to some menu.
The lino.modlib.about
plugin adds the “About” action to the “Site” menu:
def setup_site_menu(self, site, user_type, m):
m.add_action(site.models.about.About)
# or: m.add_action('about.About')
The lino_xl.lib.invoicing
plugin adds an action “Invoicing plan” to the
“Sales” menu:
def setup_main_menu(self, site, user_type, m):
mg = site.plugins.sales
m = m.add_menu(mg.app_label, mg.verbose_name)
m.add_action('invoicing.MyPlans.start_plan')
The lino.modlib.about
plugin also adds a quick link:
def get_quicklinks(site, user):
yield 'about.SiteSearch'
The resolve_action function¶
- lino.core.actors.resolve_action(spec, action=None)¶
Return the bound action corresponding to the given specifier spec and the optional action name.
When the specifier is a string, it is resolved as follows:
foo.Bar
resolves to the value ofBar
in themodels.py
module of pluginfoo
.foo.Bar.baz
resolves to a bound action whenBar
is an actor, otherwise to the class attributebaz
ofBar
.
The resolved specifier can be:
a database model
an actor
an action instance
a bound action
An action instance will return the bound action on its
Action.defining_actor
. A database model will return the default action of the model’s default table. An actor will return its default action.
>>> from lino.core.actors import resolve_action
>>> resolve_action('contacts.Persons')
<BoundAction(contacts.Persons, <lino.core.actions.ShowTable grid>)>
>>> resolve_action('contacts.Person')
<BoundAction(contacts.Persons, <lino.core.actions.ShowTable grid>)>
>>> resolve_action('users.User')
<BoundAction(users.AllUsers, <lino.core.actions.ShowTable grid>)>
>>> resolve_action(rt.models.about.About)
<BoundAction(about.About, <lino.core.actions.ShowEmptyTable show ('Detail')>)>
>>> resolve_action('about.About')
<BoundAction(about.About, <lino.core.actions.ShowEmptyTable show ('Detail')>)>
Here is the lino_xl.lib.invoicing.Plan.execute_plan
action:
>>> resolve_action('invoicing.Plan', action='execute_plan')
<BoundAction(invoicing.Plans, <lino_xl.lib.invoicing.actions.ExecutePlan execute_plan ('Execute plan')>)>
>>> resolve_action('invoicing.Plan.execute_plan')
<BoundAction(invoicing.Plans, <lino_xl.lib.invoicing.actions.ExecutePlan execute_plan ('Execute plan')>)>
>>> resolve_action(rt.models.invoicing.Plan.execute_plan)
<BoundAction(invoicing.Plans, <lino_xl.lib.invoicing.actions.ExecutePlan execute_plan ('Execute plan')>)>
>>> resolve_action('foo')
Traceback (most recent call last):
...
Exception: Invalid action specifier 'foo' (must be of form `plugin_name.ClassName[.action_name]`).
>>> resolve_action('foo.bar.baz.trump')
Traceback (most recent call last):
...
Exception: Invalid action specifier 'foo.bar.baz.trump' (must be of form `plugin_name.ClassName[.action_name]`).
>>> resolve_action('')
Traceback (most recent call last):
...
Exception: Invalid action specifier '' (must be of form `plugin_name.ClassName[.action_name]`).
>>> resolve_action('invoicing.Plan', action='trump')
Traceback (most recent call last):
...
Exception: invoicing.Plans has no action named 'trump'
>>> resolve_action('invoicing.Plan.execute_plan', action='trump')
Traceback (most recent call last):
...
Exception: Invalid action specifier 'invoicing.Plan.execute_plan' when action keyword is specified.