Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

More about plugins

A plugin is a Python module that encapsulates a set of functionality designed to be used in more than one application. It can define database models, actors, actions, fixtures, template files, JavaScript snippets, dependencies, and configuration settings. None of these are mandatory.

See Plugins API for a list of the plugins defined in Lino and the XL.

A plugin in Lino corresponds to what Django calls an “application”. See also What is an application?.

Usage overview

The application developer defines which plugins to install in the application’s get_installed_plugins method.

The plugin developer defines a plugin in the __init__.py file of the package. Lino expects this file to define a class named Plugin, which inherits from the abstract base Plugin class. Your Plugin class is the central description of your plugin.

Here is a fictive example:

from lino.api import ad, _

class Plugin(ad.Plugin):
    verbose_name = _("Better calendar")
    extends = 'mylib.cal'
    needs_plugins  = ['lino_xl.lib.contacts']

    def setup_main_menu(self, site, user_type, m):
        m = m.add_menu(self.app_label, self.verbose_name)
        m.add_action('cal.Teams')
        m.add_action('cal.Agendas')

A plugin can depend on other plugins by specifying them in the needs_plugins attribute. This means that when you install this plugin, Lino will automatically install these other plugins as well

A plugin can define a set of menu commands using methods like setup_main_menu. This is explained in More about the application menu.

A plugin can extend another plugin by inheriting from its Plugin class. This is explained in Plugin inheritance.

A plugin that extends another plugin can optionally extend one or multiple database models defined by the parent plugin. If it does so, it must declare their names in extends_models.

Accessing plugins

Django developers are used to code like this:

from myapp.models import Foo

def print_foo(pk=1):
    print(Foo.objects.get(pk=pk))

In Lino we prefer to use the rt.models dict as follows:

from lino.api import rt

def print_foo(pk=1):
    Foo = rt.models.myapp.Foo
    print(Foo.objects.get(pk=pk))

This approach has the advantage of providing Plugin inheritance. One of the basic reasons for using plugins is that another developer can extend it and use their extension instead of the original plugin. Which means that the plugin developer does not know (and does not want to know) where the model classes are actually defined.

Note that rt.models is populated only after having imported the models. So you cannot use it at the module-level namespace of a models.py module. For example the following variant of above code would not work:

from lino.api import rt
Foo = rt.models.foos.Foo  # error `AttrDict has no item "foos"`
def print_foo(pk=1):
    print(Foo.objects.get(pk=pk))

Plugin descriptors get defined and configured before Django models start to load. Lino creates one Plugin instance for every installed plugin and makes it globally available in dd.plugins.FOO (where FOO is the app_label of the plugin).

The Plugin class is comparable to Django’s AppConfig class, which has been added in version 1.7., but there is at least one important difference: in Lino the Plugin instances for all installed plugins are available (in dd.plugins) before Django starts to load the first models.py. This is possible because Plugins are defined in __init__.py files of your plugins. As a consequence, unlike Django’s AppConfig, you cannot define a Plugin in your models.py file, you must define it in your plugins’s __init__.py.

Configuring plugins

Plugins can have attributes for holding configuration settings that are not meant to configured by site users via the web interface.

For example, the countries.country_code setting is defined as follows:

...
class Plugin(ad.Plugin):
    ...
    country_code = 'BE'

The values of plugin attributes can be configured at three levels.

As a core developer you specify a hard-coded default value.

As an application developer you can specify default values in your application by overriding the Site.get_plugin_configs() of your Site class. For example:

class Site(Site):

    def get_plugin_configs(self):
        yield super().get_plugin_configs()
        yield 'countries', 'country_code', 'FR'
        yield 'contacts', 'hide_region', True

As a server administrator you can override these configuration defaults in your project’s settings.py

The Site.get_plugin_setting() method

Some use cases for testing the Site.get_plugin_setting() method:

>>> from lino import startup
>>> startup('lino_book.projects.min1.settings')
>>> from lino.api.doctest import *
>>> settings.SITE.get_plugin_setting('foo', 'bar')
Traceback (most recent call last):
...
Exception: Plugin foo is not installed and no default was provided
>>> settings.SITE.get_plugin_setting('contacts', 'bar')
Traceback (most recent call last):
...
AttributeError: 'Plugin' object has no attribute 'bar'

In both cases, you can avoid the traceback by specifying a default value:

>>> print(settings.SITE.get_plugin_setting('foo', 'bar', None))
None
>>> settings.SITE.get_plugin_setting('contacts', 'bar', 'baz')
'baz'

The old style using Site.setup_plugins() still works but is deprecated:

class Site(Site):

    def setup_plugins(self):
        super().setup_plugins()
        self.plugins.countries.configure(country_code='BE')
        self.plugins.contacts.configure(hide_region=True)

Note that Site.setup_plugins() is called after Site.get_plugin_configs(). This can cause unexpected behaviour when you mix both methods.

using one of the following methods:

  • by overriding the Site class as described above for application developers

  • by setting the value directly after instantiation of your SITE object.

plugin configuration setting

A setting that can easily be set in a settings.py file.