mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
567 lines
14 KiB
ReStructuredText
567 lines
14 KiB
ReStructuredText
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 piece
|
|
of code that integrates them into Modoboa. Usually, the *__init__.py* file
|
|
will contain a complete description of the plugin:
|
|
|
|
* Admin and user parameters
|
|
* Observed 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
|
|
======================
|
|
|
|
To create a new plugin, just start a new django application like
|
|
this (into modoboa's directory)::
|
|
|
|
$ python manage.py startapp
|
|
|
|
Then, you need to register this application using the provided
|
|
API. Just copy/paste the following example into the *__init__.py* file
|
|
of the future extension::
|
|
|
|
from modoboa.extensions import ModoExtension, exts_pool
|
|
|
|
class MyExtension(ModoExtension):
|
|
name = "myext"
|
|
label = "My Extension"
|
|
version = "0.1"
|
|
description = "A description"
|
|
url = "myext_root_location" # optional, name is used if not defined
|
|
|
|
def init(self):
|
|
"""This method is called when the extension is activated.
|
|
"""
|
|
pass
|
|
|
|
def load(self):
|
|
"""This method is called when Modoboa loads available and activated plugins.
|
|
|
|
Declare parameters and register events here.
|
|
"""
|
|
pass
|
|
|
|
def destroy(self):
|
|
"""This function is called when a plugin is disabled from the interface.
|
|
|
|
Unregister parameters and events here.
|
|
"""
|
|
pass
|
|
|
|
exts_pool.register_extension(MyExtension)
|
|
|
|
Once done, simply add your plugin's module name to the
|
|
``INSTALLED_APPS`` variable located inside *settings.py*. Optionaly,
|
|
run ``python manage.py syncdb`` if your plugin provides custom tables
|
|
and ``python manage.py collectstatic`` to update static files.
|
|
|
|
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
|
|
|
|
def callback(*args):
|
|
pass
|
|
|
|
events.register('event', callback)
|
|
|
|
You can also use the custom decorator ``events.observe``::
|
|
|
|
@events.observe('event')
|
|
def callback(*args):
|
|
pass
|
|
|
|
``event`` is the event's name you want to listen to, ``callback`` is
|
|
the function that will be called each time this event is raised. Each
|
|
event impose to callbacks a specific prototype to respect. See below
|
|
for a detailled list.
|
|
|
|
To stop listening to as specific event, you must use the
|
|
``unregister`` function::
|
|
|
|
events.unregister('event', callback)
|
|
|
|
The parameters are the same than those used with ``register``.
|
|
|
|
Read further to get a complete list and description of all available events.
|
|
|
|
UserLogin
|
|
---------
|
|
|
|
Raised when a user logs in.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(request, username, password): pass
|
|
|
|
|
|
UserLogout
|
|
----------
|
|
|
|
Raised when a user logs out.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(request): pass
|
|
|
|
|
|
.. _usermenudisplay:
|
|
|
|
UserMenuDisplay
|
|
---------------
|
|
|
|
Raised when a user menu is about to be displayed.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(target, user): pass
|
|
|
|
The ``target`` argument 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 the 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.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(target, user): pass
|
|
|
|
The ``target`` argument 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 :ref:`usermenudisplay` for a description of what callbacks that
|
|
listen to this event must return.
|
|
|
|
GetStaticContent
|
|
----------------
|
|
|
|
Let extensions add static content (ie. CSS or javascript) to default
|
|
pages. It is pretty useful for functionalities that don't need a
|
|
template but need javascript stuff.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
Callbacks listening to this event must return a list of string.
|
|
|
|
CanCreate
|
|
---------
|
|
|
|
Raised just before a user tries to create a new object.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user): pass
|
|
|
|
* ``user`` is a ``User`` instance
|
|
|
|
Return ``True`` or ``False`` to indicate if this user can respectively
|
|
create or not create a new ``Domain`` object.
|
|
|
|
CreateDomain
|
|
------------
|
|
|
|
Raised when a new domain is created.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, domain): pass
|
|
|
|
* ``user`` corresponds to the ``User`` object creating the domain (its owner)
|
|
* ``domain`` is a ``Domain`` instance
|
|
|
|
DeleteDomain
|
|
------------
|
|
|
|
Raised when an existing domain is about to be deleted.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(domain): pass
|
|
|
|
* ``domain`` is a ``Domain`` instance
|
|
|
|
DomainAliasCreated
|
|
------------------
|
|
|
|
Raised when a new domain alias is created.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, domain_alias): pass
|
|
|
|
* ``user`` is the new domain alias owner (``User`` instance)
|
|
* ``domain_alias`` is the new domain alias (``DomainAlias`` instance)
|
|
|
|
DomainAliasDeleted
|
|
------------------
|
|
|
|
Raised when an existing domain alias is about to be deleted.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(domain_alias): pass
|
|
|
|
* ``domain_alias`` is a ``DomainAlias`` instance
|
|
|
|
CreateMailbox
|
|
-------------
|
|
|
|
Raised when a new mailbox is created.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, mailbox): pass
|
|
|
|
* ``user`` is the new mailbox's owner (``User`` instance)
|
|
* ``mailbox`` is the new mailbox (``Mailbox`` instance)
|
|
|
|
DeleteMailbox
|
|
-------------
|
|
|
|
Raised when an existing mailbox is about to be deleted.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(mailbox): pass
|
|
|
|
* ``mailbox`` is a ``Mailbox`` instance
|
|
|
|
ModifyMailbox
|
|
-------------
|
|
|
|
Raised when an existing mailbox is modified.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(newmailbox, oldmailbox): pass
|
|
|
|
* ``newmailbox`` is a ``Mailbox`` instance containing the new values
|
|
* ``oldmailbox`` is a ``Mailbox`` instance containing the old values
|
|
|
|
MailboxAliasCreated
|
|
-------------------
|
|
|
|
Raised when a new mailbox alias is created.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, mailbox_alias): pass
|
|
|
|
* ``user`` is the new domain alias owner (``User`` instance)
|
|
* ``mailbox_alias`` is the new mailbox alias (``Alias`` instance)
|
|
|
|
MailboxAliasDeleted
|
|
-------------------
|
|
|
|
Raised when an existing mailbox alias is about to be deleted.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(mailbox_alias): pass
|
|
|
|
* ``mailbox_alias`` is an ``Alias`` instance
|
|
|
|
AccountCreated
|
|
--------------
|
|
|
|
Raised when a new account is created.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(account): pass
|
|
|
|
* ``account`` is the newly created account (``User`` instance)
|
|
|
|
|
|
AccountModified
|
|
---------------
|
|
|
|
Raised when an existing account is modified.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(oldaccount, newaccount): pass
|
|
|
|
* ``oldaccount`` is the account before it is modified
|
|
|
|
* ``newaccount`` is the account after the modification
|
|
|
|
AccountDeleted
|
|
--------------
|
|
|
|
Raised when an existing account is deleted.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(account): pass
|
|
|
|
* ``oldaccount`` is the account that is going to be deleted
|
|
|
|
ExtraAccountForm
|
|
----------------
|
|
|
|
Let extensions add new forms to the global account edition form (the
|
|
one with tabs).
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, account): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
* ``account`` is the account beeing modified (``User`` instance)
|
|
|
|
Callbacks listening to the event must return a list of dictionnaries,
|
|
each one must contain at least three keys::
|
|
|
|
{"id" : "<the form's id>",
|
|
"title" : "<the title used to present the form>",
|
|
"cls" : TheFormClassName}
|
|
|
|
CheckExtraAccountForm
|
|
---------------------
|
|
|
|
When an account is being modified, this event lets extensions check if
|
|
this account is concerned by a specific form.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(account, form): pass
|
|
|
|
* ``account`` is the ``User`` instance beeing modified
|
|
|
|
* ``form`` is a dictionnary (same content as for ``ExtraAccountForm``)
|
|
|
|
Callbacks listening to this event must return a list containing one
|
|
Boolean.
|
|
|
|
FillAccountInstances
|
|
--------------------
|
|
|
|
When an account is beeing modified, this event is raised to fill extra
|
|
forms.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, account, instances): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
* ``account`` is the ``User`` instance beeing modified
|
|
|
|
* ``instances`` is a dictionnary where the callback will add
|
|
information needed to fill a specific form
|
|
|
|
.. _getextraroles:
|
|
|
|
GetExtraRoles
|
|
-------------
|
|
|
|
Let extensions define new administrative roles.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
Callbacks listening to this event must return a list of 2uple (two
|
|
strings) which respect the following format: ``(value, label)``.
|
|
|
|
ExtEnabled
|
|
----------
|
|
|
|
Raised just after an extension has been activated.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(extension): pass
|
|
|
|
* ``extension`` is an ``Extension`` instance
|
|
|
|
ExtDisabled
|
|
-----------
|
|
|
|
Raised just after an extension has been disabled.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(extension): pass
|
|
|
|
* ``extension`` is an ``Extension`` instance
|
|
|
|
GetAnnouncement
|
|
---------------
|
|
|
|
Some places in the interface let plugins add their own announcement
|
|
(ie. message).
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(target): pass
|
|
|
|
* ``target`` is a string indicating the place where the announcement
|
|
will appear:
|
|
|
|
* ``loginpage`` : corresponds to the login page
|
|
|
|
Callbacks listening to this event must return a list of string.
|
|
|
|
TopNotifications
|
|
----------------
|
|
|
|
Let extensions add custom content into the top bar.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
Callbacks listening to this event must return a list of string.
|
|
|
|
ExtraAdminContent
|
|
-----------------
|
|
|
|
Let extensions add extra content into the admin panel.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user, target, currentpage): pass
|
|
|
|
* ``user`` is a ``User`` instance corresponding to the currently
|
|
logged in user
|
|
|
|
* ``target`` is a string indicating the place where the content will
|
|
be displayed. Possible values are : ``leftcol``
|
|
|
|
* ``currentpage`` is a string indicating which page (or section) is
|
|
displayed. Possible values are : ``domains``, ``identities``
|
|
|
|
Callbacks listening to this event must return a list of string.
|
|
|
|
PasswordChange
|
|
--------------
|
|
|
|
Raised just before a *password change* action.
|
|
|
|
*Callback prototype*::
|
|
|
|
def callback(user): pass
|
|
|
|
* ``user`` is a ``User`` instance
|
|
|
|
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 administrative roles
|
|
===========================
|
|
|
|
Modoboa uses Django's internal permission system. Administrative roles
|
|
are nothing more than groups (``Group`` instances).
|
|
|
|
If an extension needs to add new roles, it needs to follow those steps:
|
|
|
|
#. Create a new ``Group`` instance. It can be done by providing
|
|
`fixtures <https://docs.djangoproject.com/en/dev/howto/initial-data/>`_
|
|
or by creating it into the extension ``init`` function
|
|
|
|
#. A a new listener for the :ref:`getextraroles` event that will return
|
|
the group's name
|