Extending Modoboa

Introduction

Modoboa offers a plugin API to expand its capabilities. The current implementation provides the following possibilities:

  • Expand navigation by adding entry points to your plugin inside the GUI
  • Access and modify administrative objects (domains, mailboxes, etc.)
  • Register callback actions for specific events

Plugins are nothing more than Django applications with an extra file called main.py. This file contains a complete description of the plugin:

  • Admin and user parameters
  • Registered events
  • Custom menu entries

The communication between both applications is provided by events. Modoboa offers some kind of hooks to let plugin add custom actions.

The following subsections describe plugin architecture and explain how you can create your own plugin.

Adding a custom plugin

All Modoboa plugins must be located inside the extensions directory. To create a new plugin, execute the following steps:

  • Run python manage.py startapp to get some basefiles for your plugin,
  • Move the resulting directory inside the extensions one,
  • Create a file called main.py inside your plugin’s directory.

The last file you created (ie. main.py) must follow a specific structure in order to be interpreted by Modoboa. The following functions are mandatory:

def init():
  """This function is called when Modoboa loads available and activated plugins.

  Declare parameters and register events here.
  """
  pass

def destroy():
  """This function is called when a plugin is disabled from the interface.

  Unregister parameters and events here.
  """
  pass

def infos():
  """This function is used to populate the plugins list
  available in the administration panel.

  It must return a dictionnary containing at least three
  keys : "name", "version", "description"
  """
  return {
      "name" : "Example plugin",
      "version" : "1.0",
      "description" : "Example description"
      }

def urls(prefix):
  """This function is used to update the global urls table.

  It must return a sequence that corresponds to those used
  inside the main ``urls.py`` file. (ie. a classical
  Django construction)

  :param prefix: the global url prefix (the part after the hostname)
  """
  return (r'^%sexample/' % prefix, include('modoboa.extensions.example.urls'))

Once you have filled main.py, simply add your plugin’s name to the INSTALLED_APPS variable located inside settings.py. (use the same format as other plugins). Eventually, run python manage.py syncdb if your plugin provides custom tables.

Events

Playing with events

Modoboa provides a simple API to interact with events. It understands two kinds of events:

  • Those that return a value,
  • Those that return nothing.

Listening to a specific event is achieved as follow:

from modoboa.lib import events

events.register(event, callback)

event is the event’s name you want to listen to, callback is the function that will be called when this event is raised. Its prototype must be as follow:

def callback(*args, **kwargs):
  pass

It is very generic, it allows events to provide arguments if needed.

You can also stop listening to an event like this:

events.unregister(event, callback)

The parameters are the same as those used with register.

Read further to get a complete list and description of the available events.

UserLogin

Raised when a user logs in. This event provides a request argument that corresponds to the Request object used inside the associated view function.

UserLogout

Raised when a user logs out. This event provides a request argument that corresponds to the Request object used inside the associated view function.

UserMenuDisplay

Raised when a user menu is about to be displayed. It provides a target argument that indicates which menu is being displayed. Possible values are:

  • uprefs_menu : corresponds to the menu bar available inside the User preferences page
  • top_menu : corresponds to the top blue bar

All callbacks that listen to this event must return a list of dictionnaries (corresponding to menu entries). Each dictionnary must contain at least the following keys:

{"name" : "a_name_without_spaces",
 "label" : _("The menu label"),
 "url" : reverse("your_view"),   # can be set to ""
 "img" : static_url("your_pic")} # can be set to ""

AdminMenuDisplay

Raised when an admin menu is about to be displayed. It provides a target argument that indicates which menu is being displayed. Possible values are:

  • admin_menu_box : corresponds to the menu bar available inside administration pages
  • top_menu : corresponds to the Admin dropdown menu located inside the top blue bar

See UserMenuDisplay for a description of what callbacks that listen to this event must return.

CreateDomain

Raised when a new domain is created. The new domain object is available inside the dom argument.

DeleteDomain

Raised when an existing domain is about to be deleted. The domain object is available inside the dom argument.

CreateMailbox

Raised when a new mailbox is created. The new mailbox object is available inside the mbox argument.

DeleteMailbox

Raised when an existing mailbox is about to be deleted. The mailbox object is available inside the mbox argument.

ModifyMailbox

Raised when an existing mailbox is modified. The old mailbox and the new mailbox objects are respectively available inside the oldmbox and mbox arguments.

PermsGetTables

Raised when the different permission lists (one per role) are about to be displayed. The current User object is available inside the user argument.

Callbacks that listen to this event must return a list of dictionnaries (corresponding to tables). Each dictionnary must contain at least the following elements:

{"id" : "table_id",
 "title" : _("The title corresponding to this table"),
 "rel" : "x y",
 "content" : MyPermClass().get(request)}

For rel, replace x and y with the the Add form size (the one that appears when you click on the Add permission button.

For content, replace MyPermClass with the appropriate name.

PermsGetClass

Raised to retrieve the class (inheriting from Permissions) implementing a specific role. This event is used to add or delete or new instance of this role. The role’s name is available inside the role argument.

Callbacks listening to this event must return the class object corresponding to the given role argument.

ExtEnabled

Raised just after an extension has been activated. The Extension object is available inside the ext keyword argument.

ExtDisabled

Raised just after an extension has been disabled. The Extension object is available inside the ext keyword argument.

GetAnnouncement

Some places in the interface let plugins add their own announcement (ie. message). A keyword argument target is provided to registered callbacks and indicates the place where the announcement will appear:

  • loginpage : corresponds to the login page

Callbacks listening to this event must return a list of string.

PasswordChange

Raised just before a password change action. The associated User object is provided inside the user keyword argument.

Callbacks listening to this event must return a list containing either True or False. If at least one True is returned, the password change will be cancelled (ie. changing the password for this user is disabled).

Parameters

A plugin can declare its own parameters. There are two levels available:

  • ‘Administration’ parameters : used to configure the plugin, editable inside the Admin > Settings > Parameters page,
  • ‘User’ parameters : per-user parameters (or preferences), editable inside the Options > Preferences page.

Playing with parameters

To declare a new administration parameter, use the following function:

from modoboa.lib import parameters

parameters.register_admin(name, **kwargs)

To declare a new user parameter, use the following function:

parameter.register_user(name, **kwargs)

Both functions accept extra arguments listed here:

  • type : parameter’s type, possible values are : int, string, list, list_yesno,
  • deflt : default value,
  • help : help text,
  • values : list of possible values if type is list.

To undeclare parameters (for example when a plugin is disabled is disabled from the interface), use the following function:

parameters.unregister_app(appname)

appname corresponds to your plugin’s name, ie. the name of the directory containing the source code.

Custom permission levels

Custom permissions roles can be added to Modoboa. If you to want to integrate the default permissions panel (Admin > Permissions), each role you add must inherit from the Permissions (file admin/permissions.py) class and implement all its methods.

Note

See PermsGetTables and PermsGetClass to learn how to integrate your custom roles.