Introduction to plugins¶
A plugin is a Python module that encapsulates a set of functionality designed to be used in more than one application.
See Plugins 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¶
A plugin can define database models, actors, actions, fixtures, template files, javascript snippets, dependencies, and configuration settings. None of these are mandatory.
The application developer defines which plugins to install in the
application's get_installed_apps
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
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 configurable options.
Examples of configurable plugin attributes:
lino_xl.lib.contacts.Plugin.hide_region
The values of plugin attributes can be configured at three levels.
As a plugin 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(Site, self).get_plugin_configs()
yield ('countries', 'country_code', 'BE')
yield ('contacts', 'hide_region', True)
The old style using Site.setup_plugins()
still works but is deprecated:
class Site(Site):
def setup_plugins(self):
super(Site, self).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.
As a system administrator you can override these configuration
defaults in your project's settings.py
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.
Another (deprecated) method is by using the configure_plugin()
function.
For example:
from lino_cosi.lib.cosi.settings import *
configure_plugin('countries', country_code='BE')
SITE = Site(globals())
Beware the pitfall: configure_plugin()
must be called before the
SITE
has been instantiated, otherwise they will be ignored
silently. (It is not easy to prevent accidental calls to it after
Site initialization because there are scenarios where you want to
instantiate several Site objects.)
Keep in mind that you can indeed never be sure that your SITE
instance is actually being used. A local system admin can always decide to
import your settings.py
module and to re-instantiate your Site class
another time. That's part of our game and we don't want it to be forbidden.
- plugin configuration setting¶
A setting that can easily be set in a
settings.py
file.