diff --git a/INSTALL b/INSTALL
index d78c1a479..c0733d7a8 100644
--- a/INSTALL
+++ b/INSTALL
@@ -16,8 +16,8 @@ For extensions (mandatories since 0.8.2):
- `lxml python module `_
- `pycrypto python module `_
- `rrdtool python binding `_
-- `ckeditor version 3.4.1+ `_
- `sievelib python module `_
+- `ckeditor version 3.4.1+ `_
.. _howtoinstall:
@@ -31,7 +31,7 @@ Download the latest Modoboa's tarball and extract it::
$ cd modoboa
Then, enter the *modoboa* directory, create a *settings.py* file (you
-can use *settings-sample.py* as a model). Specify database relative
+can use *settings-sample.py* as an example). Specify database relative
informations. You must choose between *MySQL*, *PostgreSQL* or
*sqlite*, other types are not supported by major email projects like
*postfix* or *dovecot*.
@@ -51,15 +51,11 @@ this is the reason why you must add the ``--migrate`` option.
At the end of this command, a default super administrator is
available:
- * Username: admin
- * Password: password
+* Username: admin
+* Password: password
You may change its password the first time you log into Modoboa.
-Finally, create the ``scripts`` directory::
-
- $ mkdir scripts
-
Time zone and language
----------------------
@@ -192,8 +188,8 @@ Then, all you have to do is to modify the `settings.py` file:
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
+ 'modoboa.lib.authbackends.SimpleBackend',
'django.contrib.auth.backends.ModelBackend',
- 'modoboa.lib.authbackends.SimpleBackend'
)
* Set the required parameters to establish the communication with your
@@ -253,12 +249,76 @@ requires a migration.
0.9: global UI refactoring, new *limits* extension and more
-----------------------------------------------------------
+.. note::
+ Many files have been renamed/removed for this version. I recommend
+ that you backup important files (*settings.py*, etc.) elsewhere
+ (ie. */tmp* for example). Then, remove the *modoboa* directory,
+ extract the new tarball at the same place, rename the new directory
+ to *modoboa* and copy the files you've just backup into it.
+.. note::
+ If the first super administrator you created is named ``admin``,
+ its password will be changed to ``password`` at the end of this
+ upgrade. Don't forget to modify it!
+
+#. Edit the *settings.py* file and update the following variables
+ (just copy/paste their new content)::
+
+ MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'modoboa.lib.middleware.AjaxLoginRedirect',
+ 'modoboa.lib.middleware.CommonExceptionCatcher',
+ 'modoboa.lib.middleware.ExtControlMiddleware',
+ )
+
+ AUTHENTICATION_BACKENDS = (
+ 'modoboa.lib.authbackends.SimpleBackend',
+ 'django.contrib.auth.backends.ModelBackend',
+ )
+
+#. **For MySQL users only**, add the following option to your database
+ configuration::
+
+ DATABASES = {
+ "default" : {
+ # ...
+ # MySQL users only
+ "OPTIONS" : {
+ "init_command" : "SET foreign_key_checks = 0;",
+ },
+ }
+ }
+
+#. Add ``'modoboa.extensions.limits'`` to ``INSTALLED_APPS``
+
+#. Update your database (make sure to create a backup before launching
+ the following command)::
+
+ $ ./manage.py syncdb --migrate
+
+#. If you are4 using the *stats* extension, please rename the
+ */static/stats* directory to */static/graphs*
+
+#. Restart the python instance(s) that serve *Modoboa*
+
+#. Log into Modoboa, go to *Modoboa > Extensions*, uncheck all
+ extensions, save. Then, check the extensions you want to use and
+ save again.
+
+#. **For Dovecot users only**, you need to modify the
+ ``password_query`` (file */etc/dovecot/dovecot-sql.conf* by default
+ on a Debian system) like this::
+
+ password_query = SELECT email AS user, password FROM auth_user WHERE email='%u'
0.8.8: CSV import feature and minor fixes
-----------------------------------------
-#. Edit the *settings.py* file and
+#. Edit the *settings.py* file and add
``'modoboa.lib.middleware.AjaxLoginRedirect'`` to the
``MIDDLEWARE_CLASSES`` variable like this::
@@ -677,7 +737,7 @@ For *MySQL* users, edit your */etc/dovecot/dovecot-sql.conf* and modify followin
driver = mysql
connect = host= dbname= user= password=
default_pass_scheme = CRYPT
- password_query = SELECT concat(t1.address, '@', t2.name) AS user, password FROM admin_mailbox AS t1, admin_domain AS t2 WHERE t1.domain_id=t2.id AND t1.address='%n' AND t2.name='%d'
+ password_query = SELECT email AS user, password FROM auth_user WHERE email='%u'
user_query = SELECT concat(t2.name, '/', t1.path) AS home, uid, gid FROM admin_mailbox AS t1, admin_domain AS t2 WHERE t1.domain_id=t2.id AND t1.address='%n' AND t2.name='%d'
Enable quotas support
@@ -946,6 +1006,19 @@ plugins activation:
* To deactivate a plugin, go to the *admin -> settings -> extensions* page,
uncheck the corresponding box and click on the *Apply* button.
+Per-admin limits
+----------------
+
+This extension offers a way to define limits about how many objects
+(aliases, mailboxes) a domain administrator can create.
+
+It also brings a new role: ``Reseller``. A reseller is a domain
+administrator that can also manipulates domains and assign permissions
+to domain administrators.
+
+If you don't want to limit particular object type, just set the
+associated value to -1.
+
Amavisd-new quarantine
----------------------
@@ -1069,8 +1142,6 @@ Then run the following commands to update Modoboa SQL schema and content::
$ python manage.py syncdb
$ python manage.py migrate postfix-autoreply
- $ export PYTHONPATH=$PWD/..
- $ DJANGO_SETTINGS_MODULE=modoboa.settings ./scripts/postfix_autoreply/upgrade_autoreply.py
The user that executes the autoreply script needs to access
*settings.py*. You must apply proper permissions on this file. For
diff --git a/doc/conf.py b/doc/conf.py
index ba9ba8d6c..5733e4059 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,9 +48,9 @@ copyright = u'2012, Antoine Nguyen'
# built documents.
#
# The short X.Y version.
-version = '0.8.8'
+version = '0.9'
# The full version, including alpha/beta/rc tags.
-release = '0.8.8'
+release = '0.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/plugin_api.rst b/doc/plugin_api.rst
index e44a99105..711c77613 100644
--- a/doc/plugin_api.rst
+++ b/doc/plugin_api.rst
@@ -182,32 +182,26 @@ displayed. Possible values are:
See :ref:`usermenudisplay` for a description of what callbacks that
listen to this event must return.
-AdminFooterDisplay
-------------------
+GetStaticContent
+----------------
-Raised when the footer (ie. bottom of the page) of listing pages
-inside the ``admin`` application is displayed. Plugins can listen to
-this event when they want to add specific information into that place.
+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, object_type): pass
+ def callback(user): pass
-* ``user`` is a ``User`` instance (the one that is displaying the page)
-* ``object_type`` is a string indicating which kind of object is
- displayed. Possible values are:
+* ``user`` is a ``User`` instance corresponding to the currently
+ logged in user
- * ``domains``: a list of domains
- * ``domaliases``: a list of domain aliases
- * ``mailboxes``: a list of mailboxes
- * ``mbaliases``: a list of mailbox aliases
+Callbacks listening to this event must return a list of string.
-Registered callbacks must return a list of string objects.
+CanCreate
+---------
-CanCreateDomain
----------------
-
-Raised just before a user tries to access the ``newdomain`` function.
+Raised just before a user tries to create a new object.
*Callback prototype*::
@@ -241,20 +235,6 @@ Raised when an existing domain is about to be deleted.
* ``domain`` is a ``Domain`` instance
-CanCreateDomainAlias
---------------------
-
-Raised just before a user tries to access the ``newdomalias`` function.
-
-*Callback prototype*::
-
- def callback(user): pass
-
-* ``user`` is a ``User`` instance
-
-Return ``True`` or ``False`` to indicate that the user can
-respectively create or not create a new domain alias.
-
DomainAliasCreated
------------------
@@ -278,20 +258,6 @@ Raised when an existing domain alias is about to be deleted.
* ``domain_alias`` is a ``DomainAlias`` instance
-CanCreateMailbox
-----------------
-
-Raised just before a user tries to access the ``newmailbox`` function.
-
-*Callback prototype*::
-
- def callback(user): pass
-
-* ``user`` is a ``User`` instance
-
-Return ``True`` or ``False`` to indicate that the user can
-respectively create or not create a new mailbox.
-
CreateMailbox
-------------
@@ -327,20 +293,6 @@ Raised when an existing mailbox is modified.
* ``newmailbox`` is a ``Mailbox`` instance containing the new values
* ``oldmailbox`` is a ``Mailbox`` instance containing the old values
-CanCreateMailboxAlias
----------------------
-
-Raised just before a user tries to access the ``newmbalias`` function.
-
-*Callback prototype*::
-
- def callback(user): pass
-
-* ``user`` is a ``User`` instance
-
-Return ``True`` or ``False`` to indicate that the user can
-respectively create or not create a new mailbox alias.
-
MailboxAliasCreated
-------------------
@@ -364,64 +316,115 @@ Raised when an existing mailbox alias is about to be deleted.
* ``mailbox_alias`` is an ``Alias`` instance
-.. _permsgettables:
-
-PermsGetTables
+AccountCreated
--------------
-Raised when the different permission lists (one per role) are about to
-be displayed.
+Raised when a new account is created.
*Callback prototype*::
- def callback(request): pass
+ def callback(account): pass
-* ``request`` is a ``Request`` instance
+* ``account`` is the newly created account (``User`` instance)
-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)}
+AccountModified
+---------------
-For ``rel``, replace x and y with the *Add form* size (the one that
-appears when you click on the ``Add permission`` button.
+Raised when an existing account is modified.
-For ``content``, replace *MyPermClass* with the appropriate name.
+*Callback prototype*::
-.. _permsgetclass:
+ def callback(oldaccount, newaccount): pass
-PermsGetClass
+* ``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" : "",
+ "title" : "",
+ "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
-------------
-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.
-
-*Callback prototype*::
-
- def callback(role): pass
-
-* ``role`` is a string indicating the role beeing added
-
-Callbacks listening to this event must return the class object
-corresponding to the given ``role`` argument.
-
-SuperAdminPromotion
--------------------
-
-Raised when an existing user becomes a super administrator. Each group
-that user belongs to is still available (inside the ``user.groups``
-queryset) at the time the event is raised.
+Let extensions define new administrative roles.
*Callback prototype*::
def callback(user): pass
-* ``user`` is a ``User`` instance
+* ``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
----------
@@ -458,7 +461,41 @@ Some places in the interface let plugins add their own announcement
* ``target`` is a string indicating the place where the announcement
will appear:
- * ``loginpage`` : corresponds to the login page
+* ``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.
@@ -516,16 +553,17 @@ disabled from the interface), use the following function::
``appname`` corresponds to your plugin's name, ie. the name of the
directory containing the source code.
-Custom permission levels
-========================
+Custom administrative roles
+===========================
-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`` class (see file
-``admin/permissions.py``) and implement all its methods.
+Modoboa uses Django's internal permission system. Administrative roles
+are nothing more than groups (``Group`` instances).
-.. note::
- See :ref:`permsgettables` and :ref:`permsgetclass` to learn how to
- integrate your custom roles.
+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 `_
+ 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
diff --git a/extensions/postfix_autoreply/scripts/upgrade_autoreply.py b/extensions/postfix_autoreply/scripts/upgrade_autoreply.py
deleted file mode 100755
index 780fa54e3..000000000
--- a/extensions/postfix_autoreply/scripts/upgrade_autoreply.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-from django.conf import settings
-from modoboa.admin.models import Domain, Mailbox
-
-if __name__ == "__main__":
- if not "modoboa.extensions.postfix_autoreply" in settings.INSTALLED_APPS:
- print "Postfix autoreply extension is not activated"
- sys.exit(1)
-
- import modoboa.extensions.postfix_autoreply.main as par
- from modoboa.extensions.postfix_autoreply.models import Transport, Alias
-
- par.load()
- doms = Domain.objects.all()
- for dom in doms:
- try:
- trans = Transport.objects.get(domain="autoreply.%s" % dom.name)
- continue
- except Transport.DoesNotExist:
- par.onCreateDomain(dom=dom)
- mboxes = Mailbox.objects.filter(domain=dom.id)
- for mb in mboxes:
- try:
- alias = Alias.objects.get(full_address=mb.full_address)
- continue
- except Alias.DoesNotExist:
- par.onCreateMailbox(mbox=mb)
-
diff --git a/lib/events.py b/lib/events.py
index 4df9cbda3..c4d0a4c28 100644
--- a/lib/events.py
+++ b/lib/events.py
@@ -32,7 +32,6 @@ events = [
"AccountCreated",
"AccountModified",
"AccountDeleted",
- "DomainAdminActions",
"PasswordChange",