The XL and application menus

We have seen The application menu. You have already read about Lino Extensions Library, a library of shared plugins that are designed to work together. An important aspect of this collaboration is how to integrate the functionalities into the application menu.

This is a tested document. The following instructions are used for initialization:

>>> from lino import startup
>>> startup('lino_book.projects.lydia.settings.demo')
>>> from lino.api.doctest import *

The lino_book.projects.lydia 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 : Generate invoices, 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, Sales invoice journal
  - Accounting : Purchase journal (analytic), Analytic Account Balances, Accounting Report, Debtors, Creditors
  - VAT : Purchase journal, Intra-Community purchases, Intra-Community sales
- Configure :
  - System : Help Texts, Users, Divisions, Site Parameters
  - Office : Excerpt Types, My Text Field Templates, Library volumes, Upload types
  - Therapies : Dossier types, Topics, Note Types, Event Types
  - Places : Countries, Places
  - Contacts : Organization types, 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 areas
  - Calendar : Calendars, Rooms, Recurring events, Guest roles, Service types, Recurrency policies, Remote Calendars, Planner rows
  - Accounting : Analytical accounts, Sheet items, Accounts, Journals, Fiscal years, Accounting periods, Payment terms
  - Topics : Topics
- Explorer :
  - System : content types, Authorities, User types, User roles, All dashboard widgets, Data checkers, Data problem messages
  - Office : Excerpts, Text Field Templates, 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, Product invoice items, Invoicing plans, Sales rules
  - Calendar : Calendar entries, Tasks, Presences, Subscriptions, Entry states, Presence states, Task states, Planner columns, Access classes, 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 : Interests
  - 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. As an application developer you wouldn't want to maintain such a menu for each application. It was automatically generated by the installed plugins. Let's explore how this works.

The top-level menus

The default implementation of setup_menu uses a system of predefined top-level menus that are filled by the different installed plugins.

These predefined top-level menus are themselves configurable in the top_level_menus attribute of your application. You might specify your own set of top-level menus.

But the Lino Extensions Library assumes that your application uses the default value, which is:

>>> 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.

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, 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

Main menu

The root menu of an application, which contains a series of submenus 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.

Introduction to menu groups

The different installed plugins (identified by their app_label) are one way to group your database models into different "modules". But this grouping almost never exactly matches how the users would modularize their application.

TODO: write more about it.

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 of Bar in the models.py module of plugin foo.

  • foo.Bar.baz resolves to a bound action when Bar is an actor, otherwise to the class attribute baz of Bar.

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')>)>
>>> resolve_action('invoicing.Plan', action='start_plan')
<BoundAction(invoicing.Plans, <lino_xl.lib.invoicing.actions.StartInvoicing start_plan ('Generate invoices')>)>
>>> 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]`).