mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Merge branch 'master' into feature/score-and-sorting-in-quarantine
Conflicts: modoboa/extensions/amavis/lib.py modoboa/extensions/amavis/views.py modoboa/extensions/webmail/lib.py modoboa/extensions/webmail/views.py
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
|
||||
env:
|
||||
- DJANGO=1.5
|
||||
|
||||
install:
|
||||
- pip install -q Django==$DJANGO --use-mirrors
|
||||
- python setup.py -q install
|
||||
- pip install -q MySQL-python --use-mirrors
|
||||
- pip install -q pexpect --use-mirrors
|
||||
|
||||
before_script:
|
||||
- mysql -e 'create database modoboa_test;'
|
||||
|
||||
script:
|
||||
- PYTHONPATH=tests python -m unittest install_from_scratch.DeployTest.test_silent
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
Modoboa
|
||||
#######
|
||||
|
||||
|latest-version| |downloads|
|
||||
|travis| |latest-version| |downloads|
|
||||
|
||||
Modoboa is a mail hosting and management platform including a modern
|
||||
and simplified Web User Interface. It provides useful components such
|
||||
@@ -65,9 +65,9 @@ The following external libraries are provided with Modoboa:
|
||||
.. |downloads| image:: https://pypip.in/d/modoboa/badge.png
|
||||
:alt: Downloads from Pypi
|
||||
:target: https://crate.io/packages/modoboa/
|
||||
|
||||
.. |travis| image:: https://travis-ci.org/tonioo/modoboa.png?branch=master
|
||||
:target: https://travis-ci.org/tonioo/modoboa
|
||||
|
||||
.. image:: https://d2weczhvl823v0.cloudfront.net/tonioo/modoboa/trend.png
|
||||
:alt: Bitdeli badge
|
||||
:target: https://bitdeli.com/free
|
||||
|
||||
|
||||
+403
-52
@@ -11,10 +11,10 @@ Introduction
|
||||
Modoboa provides a simple API to interact with events. It understands
|
||||
two kinds of events:
|
||||
|
||||
* Those that return a value
|
||||
* Those that return nothing
|
||||
* Those returning a value
|
||||
* Those returning nothing
|
||||
|
||||
Listening to a specific event is achieved as follow::
|
||||
Listening to a specific event is achieved as follows::
|
||||
|
||||
from modoboa.lib import events
|
||||
|
||||
@@ -41,12 +41,32 @@ To stop listening to as specific event, you must use the
|
||||
|
||||
The parameters are the same than those used with ``register``.
|
||||
|
||||
To unregister all events declared by a specific extension, use the
|
||||
``unregister_extension`` function::
|
||||
|
||||
events.unregister_extension([name])
|
||||
|
||||
``name`` is the extension's name but it is optional. Leave it empty to
|
||||
let the function guess the name.
|
||||
|
||||
Read further to get a complete list and description of all available events.
|
||||
|
||||
****************
|
||||
Supported events
|
||||
****************
|
||||
|
||||
AccountAutoCreated
|
||||
==================
|
||||
|
||||
Raised when a new account is automatically created (example: LDAP
|
||||
synchronization).
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(account): pass
|
||||
|
||||
* ``account`` is the newly created account (``User`` instance)
|
||||
|
||||
AccountCreated
|
||||
==============
|
||||
|
||||
@@ -69,6 +89,32 @@ Raised when an existing account is deleted.
|
||||
|
||||
* ``account`` is the account that is going to be deleted
|
||||
* ``byuser`` is the adminstrator deleting ``account``
|
||||
|
||||
AccountExported
|
||||
===============
|
||||
|
||||
Raised when an account is exported to CSV.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(account): pass
|
||||
|
||||
* ``account`` is the account being exported
|
||||
|
||||
Must return a list of values to include in the export.
|
||||
|
||||
AccountImported
|
||||
===============
|
||||
|
||||
Raised when an account is imported from CSV.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, account, row): pass
|
||||
|
||||
* ``user`` is the user importing the account
|
||||
* ``account`` is the account being imported
|
||||
* ``row`` is a list containing what remains from the CSV definition
|
||||
|
||||
AccountModified
|
||||
===============
|
||||
@@ -127,6 +173,21 @@ Raised just before a user tries to create a new object.
|
||||
Return ``True`` or ``False`` to indicate if this user can respectively
|
||||
create or not create a new ``Domain`` object.
|
||||
|
||||
CheckDomainName
|
||||
===============
|
||||
|
||||
Raised before the unicity of a domain name is checked. By default,
|
||||
modoboa prevents duplicate names between domains and domain aliases
|
||||
but extensions have the possibility to extend this rule using this
|
||||
event.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(): pass
|
||||
|
||||
Must return a list of 2uple, each one containing a model class and an
|
||||
associated label.
|
||||
|
||||
CheckExtraAccountForm
|
||||
=====================
|
||||
|
||||
@@ -144,52 +205,6 @@ this account is concerned by a specific form.
|
||||
Callbacks listening to this event must return a list containing one
|
||||
Boolean.
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
DeleteDomain
|
||||
============
|
||||
|
||||
Raised when an existing domain is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(domain): pass
|
||||
|
||||
* ``domain`` is a ``Domain`` instance
|
||||
|
||||
DeleteMailbox
|
||||
=============
|
||||
|
||||
Raised when an existing mailbox is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(mailbox): pass
|
||||
|
||||
* ``mailbox`` is a ``Mailbox`` instance
|
||||
|
||||
DomainAliasCreated
|
||||
==================
|
||||
|
||||
@@ -213,6 +228,29 @@ Raised when an existing domain alias is about to be deleted.
|
||||
|
||||
* ``domain_alias`` is a ``DomainAlias`` instance
|
||||
|
||||
DomainCreated
|
||||
=============
|
||||
|
||||
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
|
||||
|
||||
DomainDeleted
|
||||
=============
|
||||
|
||||
Raised when an existing domain is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(domain): pass
|
||||
|
||||
* ``domain`` is a ``Domain`` instance
|
||||
|
||||
DomainModified
|
||||
==============
|
||||
|
||||
@@ -223,7 +261,20 @@ Raised when a domain has been modified.
|
||||
def callback(domain): pass
|
||||
|
||||
* ``domain`` is the modified ``Domain`` instance, it contains an extra
|
||||
``oldname`` field which contains the old domain name
|
||||
``oldname`` field which contains the old name
|
||||
|
||||
DomainOwnershipRemoved
|
||||
======================
|
||||
|
||||
Raised before the ownership of a domain is removed from its original
|
||||
creator.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(owner, domain): pass
|
||||
|
||||
* ``owner`` is the original creator
|
||||
* ``domain`` is the ``Domain`` instance being modified
|
||||
|
||||
ExtDisabled
|
||||
===========
|
||||
@@ -289,6 +340,37 @@ Let extensions add extra content into the admin panel.
|
||||
|
||||
Callbacks listening to this event must return a list of string.
|
||||
|
||||
ExtraDomainEntries
|
||||
==================
|
||||
|
||||
Raised to request extra entries to display inside the *domains*
|
||||
listing.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, domfilter, searchquery, **extrafilters): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
* ``domfilter`` is a string indicating which domain type the user needs
|
||||
* ``searchquery`` is a string containing a search query
|
||||
* ``extrafilters`` is a set of keyword arguments that may contain additional filters
|
||||
|
||||
Must return a valid ``QuerySet``.
|
||||
|
||||
ExtraDomainFilters
|
||||
==================
|
||||
|
||||
Raised to request extra filters for the *domains* listing page. For
|
||||
example, the *postfix_relay_domains* extension let users filter
|
||||
entries based on service types.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(): pass
|
||||
|
||||
Must return a list of valid filter names (string).
|
||||
|
||||
ExtraDomainForm
|
||||
===============
|
||||
|
||||
@@ -304,6 +386,59 @@ tabs).
|
||||
|
||||
* ``domain`` is the domain beeing modified (``Domain`` 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}
|
||||
|
||||
ExtraDomainImportHelp
|
||||
=====================
|
||||
|
||||
Raised to request extra help text to display inside the domain import
|
||||
form.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(): pass
|
||||
|
||||
Must return a list a string.
|
||||
|
||||
ExtraDomainMenuEntries
|
||||
======================
|
||||
|
||||
Raised to request extra entries to include in the left menu of the
|
||||
*domains* listing page.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
|
||||
Must return a list of dictionaries. Each dictionary must contain at
|
||||
least three keys::
|
||||
|
||||
{"name": "<menu name>",
|
||||
"label": "<menu label>",
|
||||
"url": "<menu url>"}
|
||||
|
||||
ExtraRelayDomainForm
|
||||
====================
|
||||
|
||||
Let extensions add new forms to the relay domain edition form (the one
|
||||
with tabs).
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, rdomain): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
* ``rdomain`` is the relay domain being modified (``RelayDomain`` instance)
|
||||
|
||||
Callbacks listening to the event must return a list of dictionnaries,
|
||||
each one must contain at least three keys::
|
||||
|
||||
@@ -344,6 +479,24 @@ forms.
|
||||
|
||||
* ``domain`` is the ``Domain`` instance beeing modified
|
||||
|
||||
* ``instances`` is a dictionnary where the callback will add
|
||||
information needed to fill a specific form
|
||||
|
||||
FillRelayDomainInstances
|
||||
========================
|
||||
|
||||
When a relay domain is being modified, this event is raised to fill extra
|
||||
forms.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, rdomain, instances): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
|
||||
* ``rdomain`` is the ``RelayDomain`` instance being modified
|
||||
|
||||
* ``instances`` is a dictionnary where the callback will add
|
||||
information needed to fill a specific form
|
||||
|
||||
@@ -364,6 +517,73 @@ Some places in the interface let plugins add their own announcement
|
||||
|
||||
Callbacks listening to this event must return a list of string.
|
||||
|
||||
GetDomainActions
|
||||
================
|
||||
|
||||
Raised to request the list of actions available for the *domains*
|
||||
listing entry being displayed.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, rdomain): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
* ``rdomain`` is the ``RelayDomain`` instance being displayed
|
||||
|
||||
Must return a list of dictionaries, each dictionary containing at
|
||||
least the following entries::
|
||||
|
||||
{"name": "<action name>",
|
||||
"url": "<action url>",
|
||||
"title": "<action title>",
|
||||
"img": "<action icon>"}
|
||||
|
||||
GetDomainModifyLink
|
||||
===================
|
||||
|
||||
Raised to request the modification url of the *domains* listing entry
|
||||
being displayed.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(domain): pass
|
||||
|
||||
* ``domain`` is a model instance (``RelayDomain`` for example)
|
||||
|
||||
Must return a dictionary containing at least the following entry::
|
||||
|
||||
{'url': '<modification url>'}
|
||||
|
||||
GetExtraLimitTemplates
|
||||
======================
|
||||
|
||||
Raised to request extra limit templates. For example, the
|
||||
*postfix_relay_domains* extension define a template to limit the
|
||||
number of relay domains an administrator can create.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(): pass
|
||||
|
||||
Must return a list of set. Each set must contain at least three entries::
|
||||
|
||||
[('<limit_name>', '<limit label>', '<limit help text>')]
|
||||
|
||||
GetExtraParameters
|
||||
==================
|
||||
|
||||
Raised to request extra parameters for a given parameters form.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(application, level): pass
|
||||
|
||||
* ``application`` is the name of the form's application (ie. admin, amavis, etc.)
|
||||
* ``level`` is the form's level: ``A`` for admin or ``U`` for user
|
||||
|
||||
Must return a dictionary. Each entry must be a valid Django form field.
|
||||
|
||||
.. _getextraroles:
|
||||
|
||||
GetExtraRoles
|
||||
@@ -397,6 +617,27 @@ template but need javascript stuff.
|
||||
|
||||
Callbacks listening to this event must return a list of string.
|
||||
|
||||
ImportObject
|
||||
============
|
||||
|
||||
Raised to request the function handling an object being imported from CSV.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(objtype): pass
|
||||
|
||||
``objtype`` is the type of object being imported
|
||||
|
||||
Must return a list of function. A valid import function must respect
|
||||
the following prototype::
|
||||
|
||||
def import_function(user, row, formopts): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
* ``row`` is a string containing the object's definition (CSV format)
|
||||
* ``formopts`` is a dictionary that may contain options
|
||||
|
||||
MailboxAliasCreated
|
||||
===================
|
||||
|
||||
@@ -420,8 +661,31 @@ Raised when an existing mailbox alias is about to be deleted.
|
||||
|
||||
* ``mailbox_alias`` is an ``Alias`` instance
|
||||
|
||||
ModifyMailbox
|
||||
=============
|
||||
MailboxCreated
|
||||
==============
|
||||
|
||||
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)
|
||||
|
||||
MailboxDeleted
|
||||
==============
|
||||
|
||||
Raised when an existing mailbox is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(mailbox): pass
|
||||
|
||||
* ``mailbox`` is a ``Mailbox`` instance
|
||||
|
||||
MailboxModified
|
||||
===============
|
||||
|
||||
Raised when an existing mailbox is modified.
|
||||
|
||||
@@ -506,3 +770,90 @@ contain at least the following keys::
|
||||
{"name" : "a_name_without_spaces",
|
||||
"label" : _("The menu label"),
|
||||
"url" : reverse("your_view")}
|
||||
|
||||
RelayDomainAliasCreated
|
||||
=======================
|
||||
|
||||
Raised when a new relay domain alias is created.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, rdomain_alias): pass
|
||||
|
||||
* ``user`` is the new relay domain alias owner (``User`` instance)
|
||||
* ``rdomain_alias`` is the new relay domain alias (``DomainAlias`` instance)
|
||||
|
||||
RelayDomainAliasDeleted
|
||||
=======================
|
||||
|
||||
Raised when an existing relay domain alias is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(rdomain_alias): pass
|
||||
|
||||
* ``rdomain_alias`` is a ``RelayDomainAlias`` instance
|
||||
|
||||
RelayDomainCreated
|
||||
==================
|
||||
|
||||
Raised when a new relay domain is created.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(user, rdomain): pass
|
||||
|
||||
* ``user`` corresponds to the ``User`` object creating the relay domain (its owner)
|
||||
* ``rdomain`` is a ``RelayDomain`` instance
|
||||
|
||||
RelayDomainDeleted
|
||||
==================
|
||||
|
||||
Raised when an existing relay domain is about to be deleted.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(rdomain): pass
|
||||
|
||||
* ``rdomain`` is a ``RelayDomain`` instance
|
||||
|
||||
RelayDomainModified
|
||||
===================
|
||||
|
||||
Raised when a relay domain has been modified.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(rdomain): pass
|
||||
|
||||
* ``rdomain`` is the modified ``RelayDomain`` instance, it contains an
|
||||
extra ``oldname`` field which contains the old name
|
||||
|
||||
RoleChanged
|
||||
===========
|
||||
|
||||
Raised when the role of an account is about to be changed.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(account, role): pass
|
||||
|
||||
* ``account`` is the account being modified
|
||||
* ``role`` is the new role (string)
|
||||
|
||||
UserCanSetRole
|
||||
==============
|
||||
|
||||
Raised to check if a user is allowed to set a given role to an
|
||||
account.
|
||||
|
||||
*Callback prototype*::
|
||||
|
||||
def callback(account, role): pass
|
||||
|
||||
* ``user`` is the ``User`` instance corresponding to the currently
|
||||
logged in user
|
||||
* ``role`` is the role ``user`` tries to set
|
||||
|
||||
Must return a list containing ``True`` or ``False`` to indicate if
|
||||
this user can is allowed to set ``role``.
|
||||
|
||||
@@ -32,6 +32,20 @@ associated value to -1.
|
||||
Default limits applied to new administrators can be changed through
|
||||
the *Modoboa > Parameters > Limits* page.
|
||||
|
||||
*****************************
|
||||
Postfix relay domains support
|
||||
*****************************
|
||||
|
||||
This plugin adds the support for relay domains using postfix. This
|
||||
feature is useful when the MTA managed by Modoboa is not the final
|
||||
destination of one or many domains.
|
||||
|
||||
If activated, two new objects will be available from the *domains*
|
||||
listing page: *relay domain* and *relay domain alias*.
|
||||
|
||||
This extension is compatible with the *amavis* and *limits*
|
||||
ones. Resellers will be able to create both new objects.
|
||||
|
||||
.. _amavis_frontend:
|
||||
|
||||
********************
|
||||
|
||||
@@ -53,6 +53,9 @@ Finally, follow the common procedure::
|
||||
1.1.0: FIXME
|
||||
============
|
||||
|
||||
#. Add ``'modoboa.extensions.postfix_relay_domains'`` to
|
||||
``MODOBOA_APPS``
|
||||
|
||||
Due to code refactoring, some modifications need to be done into
|
||||
:file:`settings.py`:
|
||||
|
||||
|
||||
@@ -138,11 +138,11 @@ MySQL users
|
||||
|
||||
default_pass_scheme = CRYPT
|
||||
|
||||
password_query = SELECT email AS user, password FROM admin_user WHERE email='%u' and is_active=1
|
||||
password_query = SELECT email AS user, password FROM core_user WHERE email='%u' and is_active=1
|
||||
|
||||
user_query = SELECT '<mailboxes storage directory>/%d/%n' AS home, <uid> as uid, <gid> as gid, concat('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE mb.address='%n' AND dom.name='%d'
|
||||
|
||||
iterate_query = SELECT email AS username FROM admin_user
|
||||
iterate_query = SELECT email AS username FROM core_user
|
||||
|
||||
.. _dovecot_pg_queries:
|
||||
|
||||
@@ -157,11 +157,11 @@ PostgreSQL users
|
||||
|
||||
default_pass_scheme = CRYPT
|
||||
|
||||
password_query = SELECT email AS user, password FROM admin_user WHERE email='%u' and is_active
|
||||
password_query = SELECT email AS user, password FROM core_user WHERE email='%u' and is_active
|
||||
|
||||
user_query = SELECT '<mailboxes storage directory>/%d/%n' AS home, <uid> as uid, <gid> as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE mb.address='%n' AND dom.name='%d'
|
||||
|
||||
iterate_query = SELECT email AS username FROM admin_user
|
||||
iterate_query = SELECT email AS username FROM core_user
|
||||
|
||||
SQLite users
|
||||
------------
|
||||
@@ -174,11 +174,11 @@ SQLite users
|
||||
|
||||
default_pass_scheme = CRYPT
|
||||
|
||||
password_query = SELECT email AS user, password FROM admin_user WHERE email='%u' and is_active=1
|
||||
password_query = SELECT email AS user, password FROM core_user WHERE email='%u' and is_active=1
|
||||
|
||||
user_query = SELECT '<mailboxes storage directory>/%d/%n' AS home, <uid> as uid, <gid> as gid, ('*:bytes=' || mb.quota || 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE mb.address='%n' AND dom.name='%d'
|
||||
|
||||
iterate_query = SELECT email AS username FROM admin_user
|
||||
iterate_query = SELECT email AS username FROM core_user
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -325,7 +325,7 @@ work for Modoboa. You should use the following one instead:
|
||||
DECLARE
|
||||
mboxid INTEGER;
|
||||
BEGIN
|
||||
SELECT admin_mailbox.id INTO STRICT mboxid FROM admin_mailbox INNER JOIN admin_user ON admin_mailbox.user_id=admin_user.id WHERE admin_user.username=NEW.username;
|
||||
SELECT admin_mailbox.id INTO STRICT mboxid FROM admin_mailbox INNER JOIN core_user ON admin_mailbox.user_id=core_user.id WHERE core_user.username=NEW.username;
|
||||
UPDATE admin_quota SET mbox_id = mboxid
|
||||
WHERE username = NEW.username;
|
||||
RETURN NULL;
|
||||
|
||||
@@ -15,6 +15,7 @@ base_events = [
|
||||
"RoleChanged",
|
||||
"GetExtraRoles",
|
||||
"PasswordChange",
|
||||
"UserCanSetRole",
|
||||
|
||||
"UserMenuDisplay",
|
||||
"AdminMenuDisplay",
|
||||
@@ -32,12 +33,19 @@ base_events = [
|
||||
"ExtraAdminContent",
|
||||
|
||||
"ExtraUprefsRoutes",
|
||||
"ExtraUprefsJS"
|
||||
"ExtraUprefsJS",
|
||||
|
||||
"GetExtraParameters"
|
||||
]
|
||||
|
||||
|
||||
def load_settings():
|
||||
from .app_settings import GeneralParametersForm, UserSettings
|
||||
def load_core_settings():
|
||||
"""Load core settings.
|
||||
|
||||
This function must be manually called (see :file:`urls.py`) in
|
||||
order to load base settings.
|
||||
"""
|
||||
from modoboa.core.app_settings import GeneralParametersForm, UserSettings
|
||||
|
||||
parameters.register(GeneralParametersForm, ugettext_lazy("General"))
|
||||
parameters.register(UserSettings, ugettext_lazy("General"))
|
||||
@@ -52,4 +60,4 @@ def unset_default_topredirection(extension):
|
||||
"""
|
||||
topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION")
|
||||
if topredirection == extension.name:
|
||||
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "userprefs")
|
||||
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "core")
|
||||
|
||||
@@ -17,12 +17,16 @@ def enabled_applications():
|
||||
:return: a list
|
||||
"""
|
||||
from modoboa.core.models import Extension
|
||||
from modoboa.core.extensions import exts_pool
|
||||
from modoboa.lib.dbutils import db_table_exists
|
||||
|
||||
result = [("core", "core")]
|
||||
result = [("user", "user")]
|
||||
if db_table_exists("core_extension"):
|
||||
exts = Extension.objects.filter(enabled=True)
|
||||
result += [(ext.name, ext.name) for ext in exts]
|
||||
for ext in exts:
|
||||
extclass = exts_pool.get_extension(ext.name)
|
||||
if extclass.available_for_topredirection:
|
||||
result.append((ext.name, ext.name))
|
||||
return sorted(result, key=lambda e: e[0])
|
||||
|
||||
|
||||
@@ -98,7 +102,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
|
||||
label=ugettext_lazy("Bind password"),
|
||||
initial='',
|
||||
help_text=ugettext_lazy("The password to use when binding to the LDAP server (with 'Bind DN')"),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(render_value=True),
|
||||
required=False
|
||||
)
|
||||
|
||||
@@ -152,7 +156,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
|
||||
default_top_redirection = forms.ChoiceField(
|
||||
label=ugettext_lazy("Default top redirection"),
|
||||
choices=[],
|
||||
initial="core",
|
||||
initial="user",
|
||||
help_text=ugettext_lazy("The default redirection used when no application is specified")
|
||||
)
|
||||
|
||||
@@ -176,6 +180,13 @@ class GeneralParametersForm(parameters.AdminParametersForm):
|
||||
super(GeneralParametersForm, self).__init__(*args, **kwargs)
|
||||
self.fields["default_top_redirection"].choices = enabled_applications()
|
||||
|
||||
def clean_secret_key(self):
|
||||
if len(self.cleaned_data["secret_key"]) not in [16, 24, 32]:
|
||||
raise forms.ValidationError(
|
||||
_("Key must be either 16, 24, or 32 bytes long")
|
||||
)
|
||||
return self.cleaned_data["secret_key"]
|
||||
|
||||
def clean_ldap_user_dn_template(self):
|
||||
tpl = self.cleaned_data["ldap_user_dn_template"]
|
||||
try:
|
||||
|
||||
@@ -106,6 +106,7 @@ TEMPLATE_LOADERS = (
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.transaction.TransactionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
@@ -154,6 +155,7 @@ MODOBOA_APPS = (
|
||||
'modoboa.lib',
|
||||
# Modoboa extensions here.
|
||||
'modoboa.extensions.admin',
|
||||
'modoboa.extensions.postfix_relay_domains',
|
||||
'modoboa.extensions.limits',
|
||||
'modoboa.extensions.postfix_autoreply',
|
||||
'modoboa.extensions.webmail',
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
"""
|
||||
:mod:`exceptions` --- Custom exceptions of the admin. application
|
||||
-----------------------------------------------------------------
|
||||
"""
|
||||
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
|
||||
|
||||
class AdminError(ModoboaException):
|
||||
"""
|
||||
Just a Custom exception to identify errors coming from the admin
|
||||
application.
|
||||
"""
|
||||
pass
|
||||
+34
-11
@@ -15,6 +15,7 @@ class ModoExtension(object):
|
||||
url = None
|
||||
needs_media = False
|
||||
always_active = False
|
||||
available_for_topredirection = False
|
||||
|
||||
def infos(self):
|
||||
return dict(
|
||||
@@ -37,7 +38,7 @@ class ExtensionsPool(object):
|
||||
"""The extensions manager"""
|
||||
|
||||
def __init__(self):
|
||||
self.extensions = dict()
|
||||
self.extensions = {}
|
||||
|
||||
def register_extension(self, ext, show=True):
|
||||
self.extensions[ext.name] = dict(cls=ext, show=show)
|
||||
@@ -49,6 +50,13 @@ class ExtensionsPool(object):
|
||||
self.extensions[name]["instance"] = self.extensions[name]["cls"]()
|
||||
return self.extensions[name]["instance"]
|
||||
|
||||
def is_extension_enabled(self, name):
|
||||
from modoboa.core.models import Extension
|
||||
|
||||
if not name in self.extensions:
|
||||
return False
|
||||
return Extension.objects.get(name=name).enabled
|
||||
|
||||
def get_extension_infos(self, name):
|
||||
instance = self.get_extension(name)
|
||||
if instance is None:
|
||||
@@ -56,13 +64,33 @@ class ExtensionsPool(object):
|
||||
return instance.infos()
|
||||
|
||||
def load_all(self):
|
||||
"""Load all enabled extensions.
|
||||
|
||||
Each extension must be loaded in order to integrate with
|
||||
Modoboa. Only enabled and special extensions are loaded but
|
||||
urls are always returned. The reason is urls are imported only
|
||||
once so must know all of them when the python process
|
||||
starts. Otherwise, it would lead to unexpected 404 errors :p
|
||||
|
||||
:return: a list of url maps
|
||||
"""
|
||||
from modoboa.core.models import Extension
|
||||
|
||||
result = []
|
||||
for ext in settings.MODOBOA_APPS:
|
||||
__import__(ext)
|
||||
result = []
|
||||
for extname in self.extensions.keys():
|
||||
extname = ext.split('.')[-1]
|
||||
extinstance = self.get_extension(extname)
|
||||
if extinstance is None:
|
||||
continue
|
||||
try:
|
||||
baseurl = extinstance.url \
|
||||
if extinstance.url is not None else extname
|
||||
result += [(r'^%s/' % (baseurl),
|
||||
include("%s.urls" % extinstance.__module__))]
|
||||
except ImportError:
|
||||
# No urls for this extension
|
||||
pass
|
||||
if not extinstance.always_active:
|
||||
try:
|
||||
ext = Extension.objects.get(name=extname)
|
||||
@@ -71,21 +99,16 @@ class ExtensionsPool(object):
|
||||
except Extension.DoesNotExist:
|
||||
continue
|
||||
extinstance.load()
|
||||
try:
|
||||
baseurl = extinstance.url if extinstance.url is not None else extname
|
||||
result += [(r'^%s/' % (baseurl),
|
||||
include("%s.urls" % extinstance.__module__))]
|
||||
except ImportError:
|
||||
# No urls for this extension
|
||||
pass
|
||||
return result
|
||||
|
||||
def list_all(self):
|
||||
"""List all defined extensions.
|
||||
"""
|
||||
result = []
|
||||
for extname, extdef in self.extensions.iteritems():
|
||||
if not extdef["show"]:
|
||||
continue
|
||||
infos = extdef["instance"].infos()
|
||||
infos = self.get_extension_infos(extname)
|
||||
infos["id"] = extname
|
||||
result += [infos]
|
||||
return sorted(result, key=lambda i: i["name"])
|
||||
|
||||
+45
-22
@@ -4,9 +4,11 @@ import re
|
||||
import hashlib
|
||||
import crypt
|
||||
import base64
|
||||
import logging
|
||||
from random import Random
|
||||
import reversion
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
@@ -17,13 +19,14 @@ from django.contrib.auth.models import (
|
||||
UserManager, Group, AbstractBaseUser, PermissionsMixin
|
||||
)
|
||||
from modoboa.lib import events, md5crypt, parameters
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.exceptions import (
|
||||
PermDeniedException, InternalError, BadRequest
|
||||
)
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.core.extensions import exts_pool
|
||||
from modoboa.core.exceptions import AdminError
|
||||
|
||||
try:
|
||||
from modoboa.lib.ldaputils import *
|
||||
from modoboa.lib.ldaputils import LDAPAuthBackend
|
||||
ldap_available = True
|
||||
except ImportError:
|
||||
ldap_available = False
|
||||
@@ -72,7 +75,9 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
get_object_owner, grant_access_to_object, ungrant_access_to_object
|
||||
|
||||
if fromuser == self:
|
||||
raise AdminError(_("You can't delete your own account"))
|
||||
raise PermDeniedException(
|
||||
_("You can't delete your own account")
|
||||
)
|
||||
|
||||
if not fromuser.can_access(self):
|
||||
raise PermDeniedException
|
||||
@@ -105,6 +110,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
elif scheme == "md5crypt":
|
||||
# The salt may vary from 12 to 48 bits. (Using all six bytes here
|
||||
# with a subset of characters means we get only 35 random bits.)
|
||||
import string
|
||||
salt = "".join(Random().sample(string.letters + string.digits, 6))
|
||||
result = md5crypt(raw_value, salt)
|
||||
prefix = "" # the result already has $1$ prepended to it
|
||||
@@ -133,15 +139,12 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
self.password = self._crypt_password(raw_value)
|
||||
else:
|
||||
if not ldap_available:
|
||||
raise AdminError(
|
||||
raise InternalError(
|
||||
_("Failed to update password: LDAP module not installed")
|
||||
)
|
||||
|
||||
ab = LDAPAuthBackend()
|
||||
try:
|
||||
ab.update_user_password(self.username, curvalue, raw_value)
|
||||
except LDAPException, e:
|
||||
raise AdminError(_("Failed to update password: %s" % str(e)))
|
||||
LDAPAuthBackend().update_user_password(
|
||||
self.username, curvalue, raw_value
|
||||
)
|
||||
events.raiseEvent(
|
||||
"PasswordUpdated", self, raw_value, self.pk is None
|
||||
)
|
||||
@@ -197,7 +200,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
try:
|
||||
return self.groups.all()[0].name
|
||||
except IndexError:
|
||||
return "SimpleUsers"
|
||||
return "---"
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -318,7 +321,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
:param crypt_password:
|
||||
"""
|
||||
if len(row) < 7:
|
||||
raise AdminError(_("Invalid line"))
|
||||
raise BadRequest(_("Invalid line"))
|
||||
role = row[6].strip()
|
||||
if not user.is_superuser and not role in ["SimpleUsers", "DomainAdmins"]:
|
||||
raise PermDeniedException(
|
||||
@@ -327,11 +330,11 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
self.username = row[1].strip()
|
||||
if role == "SimpleUsers":
|
||||
if (len(row) < 8 or not row[7].strip()):
|
||||
raise AdminError(
|
||||
raise BadRequest(
|
||||
_("The simple user '%s' must have a valid email address" % self.username)
|
||||
)
|
||||
if self.username != row[7].strip():
|
||||
raise AdminError(
|
||||
raise BadRequest(
|
||||
_("username and email fields must not differ for '%s'" % self.username)
|
||||
)
|
||||
|
||||
@@ -460,24 +463,44 @@ class Log(models.Model):
|
||||
|
||||
@receiver(reversion.post_revision_commit)
|
||||
def post_revision_commit(sender, **kwargs):
|
||||
import logging
|
||||
|
||||
if kwargs["revision"].user is None:
|
||||
return
|
||||
logger = logging.getLogger("modoboa.admin")
|
||||
for version in kwargs["versions"]:
|
||||
if version.type == reversion.models.VERSION_ADD:
|
||||
prev_revisions = reversion.get_for_object(version.object)
|
||||
if prev_revisions.count() == 1:
|
||||
action = _("added")
|
||||
level = "info"
|
||||
elif version.type == reversion.models.VERSION_CHANGE:
|
||||
else:
|
||||
action = _("modified")
|
||||
level = "warning"
|
||||
else:
|
||||
action = _("deleted")
|
||||
level = "critical"
|
||||
message = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
|
||||
"object": unicode(version.content_type).capitalize(),
|
||||
"name": version.object_repr, "action": action,
|
||||
"user": kwargs["revision"].user.username
|
||||
}
|
||||
getattr(logger, level)(message)
|
||||
|
||||
|
||||
@receiver(post_delete)
|
||||
def post_delete_hook(sender, instance, **kwargs):
|
||||
"""Custom post-delete hook.
|
||||
|
||||
We want to know who was responsible for an object deletion.
|
||||
"""
|
||||
from reversion.models import Version
|
||||
|
||||
if not reversion.is_registered(sender):
|
||||
return
|
||||
del_list = reversion.get_deleted(sender)
|
||||
try:
|
||||
version = del_list.get(object_id=instance.id)
|
||||
except Version.DoesNotExist:
|
||||
return
|
||||
logger = logging.getLogger("modoboa.admin")
|
||||
msg = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
|
||||
"object": unicode(version.content_type).capitalize(),
|
||||
"name": version.object_repr, "action": _("deleted"),
|
||||
"user": version.revision.user.username
|
||||
}
|
||||
logger.critical(msg)
|
||||
|
||||
@@ -45,7 +45,7 @@ def settings_menu(selection, user):
|
||||
"label": _("Information")},
|
||||
{"name": "logs",
|
||||
"class": "ajaxlink",
|
||||
"url": "logs/",
|
||||
"url": "logs/&sort_order=-date_created",
|
||||
"label": _("Logs")},
|
||||
{"name": "parameters",
|
||||
"class": "ajaxlink",
|
||||
@@ -230,18 +230,6 @@ def get_modoboa_logo():
|
||||
return logo
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def extra_head_content(user):
|
||||
tpl = template.Template(
|
||||
"{% for sc in static_content %}{{ sc|safe }}{% endfor %}"
|
||||
)
|
||||
return tpl.render(
|
||||
template.Context({
|
||||
'static_content': events.raiseQueryEvent("GetStaticContent", user)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def load_optionalmenu(user):
|
||||
menu = events.raiseQueryEvent("UserMenuDisplay", "top_menu_middle", user)
|
||||
|
||||
+11
-19
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import (
|
||||
)
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_simple_response, _render_to_string
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.listing import get_sort_order, get_listing_page
|
||||
from modoboa.core.models import Extension, Log
|
||||
@@ -24,8 +24,7 @@ def viewsettings(request, tplname='core/settings_header.html'):
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def viewparameters(request, tplname='core/parameters.html'):
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"left_selection": "parameters",
|
||||
"content": _render_to_string(request, tplname, {
|
||||
"forms": parameters.get_admin_forms
|
||||
@@ -42,12 +41,10 @@ def saveparameters(request):
|
||||
form.save()
|
||||
form.to_django_settings()
|
||||
continue
|
||||
return ajax_simple_response({
|
||||
"status": "ko", "prefix": form.app, "errors": form.errors
|
||||
})
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Parameters saved")
|
||||
))
|
||||
return render_to_json_response(
|
||||
{'form_errors': form.errors, 'prefix': form.app}, status=400
|
||||
)
|
||||
return render_to_json_response(_("Parameters saved"))
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -68,8 +65,7 @@ def viewextensions(request, tplname='core/extensions.html'):
|
||||
ext["selection"] = False
|
||||
|
||||
tbl = ExtensionsTable(request, exts)
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, tplname, {"extensions": tbl})
|
||||
})
|
||||
|
||||
@@ -82,7 +78,7 @@ def saveextensions(request):
|
||||
for k in request.POST.keys():
|
||||
if k.startswith("select_"):
|
||||
parts = k.split("_", 1)
|
||||
dbext = Extension.objects.get(name=parts[1])
|
||||
dbext = Extension.objects.get(name=parts[1])
|
||||
if not dbext in actived_exts:
|
||||
dbext.on()
|
||||
else:
|
||||
@@ -91,16 +87,13 @@ def saveextensions(request):
|
||||
if not ext in found:
|
||||
ext.off()
|
||||
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Modifications applied.")
|
||||
))
|
||||
return render_to_json_response(_("Modifications applied."))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def information(request, tplname="core/information.html"):
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": render_to_string(tplname, {})
|
||||
})
|
||||
|
||||
@@ -118,8 +111,7 @@ def logs(request, tplname="core/logs.html"):
|
||||
Log.objects.all().order_by("%s%s" % (sort_dir, sort_order)),
|
||||
request.GET.get("page", 1)
|
||||
)
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": render_to_string(tplname, {
|
||||
"logs": page.object_list,
|
||||
}),
|
||||
|
||||
@@ -35,7 +35,7 @@ def dologin(request):
|
||||
|
||||
nextlocation = request.POST.get("next", None)
|
||||
if nextlocation is None or nextlocation == "None":
|
||||
if user.group != "SimpleUsers":
|
||||
if user.group == "SimpleUsers":
|
||||
nextlocation = reverse("modoboa.lib.webutils.topredirection")
|
||||
else:
|
||||
nextlocation = reverse("domains")
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_simple_response, _render_to_string
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.cryptutils import encrypt
|
||||
from modoboa.core.forms import ProfileForm
|
||||
@@ -32,17 +32,11 @@ def profile(request, tplname='core/user_profile.html'):
|
||||
form.save()
|
||||
if update_password and form.cleaned_data["confirmation"] != "":
|
||||
request.session["password"] = encrypt(form.cleaned_data["confirmation"])
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Profile updated")
|
||||
))
|
||||
return ajax_simple_response({
|
||||
"status": "ko",
|
||||
"errors": form.errors
|
||||
})
|
||||
return render_to_json_response(_("Profile updated"))
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
form = ProfileForm(update_password, instance=request.user)
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, tplname, {
|
||||
"form": form
|
||||
})
|
||||
@@ -57,16 +51,13 @@ def preferences(request):
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
continue
|
||||
return ajax_simple_response({
|
||||
"status": "ko", "prefix": form.app, "errors": form.errors
|
||||
})
|
||||
return render_to_json_response({
|
||||
"prefix": form.app, "form_errors": form.errors
|
||||
}, status=400)
|
||||
|
||||
return ajax_simple_response({
|
||||
"status": "ok", "respmsg": _("Preferences saved")
|
||||
})
|
||||
return render_to_json_response(_("Preferences saved"))
|
||||
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, "core/user_preferences.html", {
|
||||
"forms": parameters.get_user_forms(request.user)
|
||||
})
|
||||
|
||||
@@ -43,12 +43,13 @@ def password_change(user):
|
||||
|
||||
|
||||
@events.observe("GetStaticContent")
|
||||
def get_static_content(user):
|
||||
if not user.mailbox_set.count():
|
||||
def get_static_content(caller, user):
|
||||
if caller != 'top' and not user.mailbox_set.count():
|
||||
return []
|
||||
return """<script type="text/javascript">
|
||||
|
||||
return ["""<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(document).on('click', 'a[name=sendspam]', simple_ajax_request);
|
||||
$(document).on('click', 'a[name=sendvirus]', simple_ajax_request);
|
||||
});
|
||||
</script>"""
|
||||
</script>"""]
|
||||
|
||||
@@ -3,10 +3,11 @@ import os
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.webutils import ajax_simple_response
|
||||
from modoboa.lib.webutils import render_to_json_response
|
||||
from modoboa.lib.emailutils import sendmail_simple, sendmail_fromfile
|
||||
from modoboa.lib.decorators import needs_mailbox
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def send_virus(request):
|
||||
@@ -14,8 +15,9 @@ def send_virus(request):
|
||||
os.path.join(settings.MODOBOA_DIR,
|
||||
"tmp/virus.msg"))
|
||||
if status:
|
||||
return ajax_simple_response(dict(status="ok", respmsg=_("Message sent")))
|
||||
return ajax_simple_response(dict(status="ko", respmsg=error))
|
||||
return render_to_json_response(_("Message sent"))
|
||||
return render_to_json_response(error, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@@ -38,5 +40,5 @@ XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
|
||||
You should send this test mail from an account outside of your network.
|
||||
""")
|
||||
if status:
|
||||
return ajax_simple_response(dict(status="ok", respmsg=_("Message sent")))
|
||||
return ajax_simple_response(dict(status="ko", respmsg=error))
|
||||
return render_to_json_response(_("Message sent"))
|
||||
return render_to_json_response(respmsg=error, status=500)
|
||||
|
||||
@@ -3,25 +3,30 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.exceptions import PermDeniedException, BadRequest
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Mailbox, Alias
|
||||
)
|
||||
|
||||
|
||||
admin_events = [
|
||||
"CreateDomain",
|
||||
"DomainCreated",
|
||||
"DomainModified",
|
||||
"DeleteDomain",
|
||||
"DomainDeleted",
|
||||
"DomainOwnershipRemoved",
|
||||
"ExtraDomainEntries",
|
||||
"ExtraDomainMenuEntries",
|
||||
"ExtraDomainFilters",
|
||||
"GetDomainActions",
|
||||
"GetDomainModifyLink",
|
||||
"CheckDomainName",
|
||||
|
||||
"DomainAliasCreated",
|
||||
"DomainAliasDeleted",
|
||||
|
||||
"CreateMailbox",
|
||||
"DeleteMailbox",
|
||||
"ModifyMailbox",
|
||||
"MailboxCreated",
|
||||
"MailboxDeleted",
|
||||
"MailboxModified",
|
||||
|
||||
"MailboxAliasCreated",
|
||||
"MailboxAliasDeleted",
|
||||
@@ -32,6 +37,9 @@ admin_events = [
|
||||
"ExtraAccountForm",
|
||||
"CheckExtraAccountForm",
|
||||
"FillAccountInstances",
|
||||
|
||||
"ExtraDomainImportHelp",
|
||||
"ImportObject"
|
||||
]
|
||||
|
||||
|
||||
@@ -139,7 +147,7 @@ def import_account_mailbox(user, account, row):
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(
|
||||
raise BadRequest(
|
||||
_("Account import failed (%s): domain does not exist" % account.username)
|
||||
)
|
||||
if not user.can_access(domain):
|
||||
@@ -193,6 +201,11 @@ def user_logged_in(request, username, password):
|
||||
|
||||
@events.observe("AccountDeleted")
|
||||
def account_deleted(account, byuser, **kwargs):
|
||||
"""'AccountDeleted' listener.
|
||||
|
||||
When an account is deleted, we also need to remove its mailbox (if
|
||||
any).
|
||||
"""
|
||||
if not account.mailbox_set.count():
|
||||
return
|
||||
mb = account.mailbox_set.all()[0]
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
:mod:`exceptions` --- Custom exceptions of the admin. application
|
||||
-----------------------------------------------------------------
|
||||
"""
|
||||
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
|
||||
|
||||
class AdminError(ModoboaException):
|
||||
"""
|
||||
Just a Custom exception to identify errors coming from the admin
|
||||
application.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BadDestination(Exception):
|
||||
pass
|
||||
@@ -2,14 +2,13 @@ from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.http import QueryDict
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
|
||||
from modoboa.lib.permissions import get_account_roles
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.lib.formutils import (
|
||||
DomainNameField, DynamicForm, TabForms
|
||||
)
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Mailbox, Alias
|
||||
)
|
||||
@@ -22,7 +21,9 @@ class AccountFormGeneral(forms.ModelForm):
|
||||
choices=[('', ugettext_lazy("Choose"))],
|
||||
help_text=ugettext_lazy("What level of permission this user will have")
|
||||
)
|
||||
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
password1 = forms.CharField(
|
||||
label=_("Password"), widget=forms.PasswordInput
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=ugettext_lazy("Confirmation"),
|
||||
widget=forms.PasswordInput,
|
||||
@@ -45,20 +46,20 @@ class AccountFormGeneral(forms.ModelForm):
|
||||
widget=forms.HiddenInput, required=False
|
||||
)
|
||||
else:
|
||||
self.fields["role"].choices = \
|
||||
[('', ugettext_lazy("Choose"))] + get_account_roles(user)
|
||||
self.fields["role"].choices = [('', ugettext_lazy("Choose"))]
|
||||
self.fields["role"].choices += \
|
||||
get_account_roles(user, kwargs['instance']) \
|
||||
if 'instance' in kwargs else get_account_roles(user)
|
||||
|
||||
if "instance" in kwargs:
|
||||
if len(args) \
|
||||
if args \
|
||||
and (args[0].get("password1", "") == ""
|
||||
and args[0].get("password2", "") == ""):
|
||||
self.fields["password1"].required = False
|
||||
self.fields["password2"].required = False
|
||||
|
||||
u = kwargs["instance"]
|
||||
self.fields["role"].initial = u.group
|
||||
|
||||
if not u.is_local \
|
||||
account = kwargs["instance"]
|
||||
self.fields["role"].initial = account.group
|
||||
if not account.is_local \
|
||||
and parameters.get_admin("LDAP_AUTH_METHOD") == "directbind":
|
||||
del self.fields["password1"]
|
||||
del self.fields["password2"]
|
||||
@@ -90,7 +91,7 @@ class AccountFormGeneral(forms.ModelForm):
|
||||
def save(self, commit=True):
|
||||
account = super(AccountFormGeneral, self).save(commit=False)
|
||||
if self.user == account and not self.cleaned_data["is_active"]:
|
||||
raise AdminError(_("You can't disable your own account"))
|
||||
raise PermDeniedException(_("You can't disable your own account"))
|
||||
if commit:
|
||||
if "password1" in self.cleaned_data \
|
||||
and self.cleaned_data["password1"] != "":
|
||||
@@ -105,14 +106,21 @@ class AccountFormMail(forms.Form, DynamicForm):
|
||||
quota = forms.IntegerField(
|
||||
label=ugettext_lazy("Quota"),
|
||||
required=False,
|
||||
help_text=_("Quota in MB for this mailbox. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),
|
||||
help_text=_("Quota in MB for this mailbox. Define a custom value or "
|
||||
"use domain's default one. Leave empty to define an "
|
||||
"unlimited value (not allowed for domain "
|
||||
"administrators)."),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "span1"})
|
||||
)
|
||||
quota_act = forms.BooleanField(required=False)
|
||||
aliases = forms.EmailField(
|
||||
label=ugettext_lazy("Alias(es)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy("Alias(es) of this mailbox. Indicate only one address per input, press ENTER to add a new input. Use the '*' character to create a 'catchall' alias (ex: *@domain.tld).")
|
||||
help_text=ugettext_lazy(
|
||||
"Alias(es) of this mailbox. Indicate only one address per input, "
|
||||
"press ENTER to add a new input. Use the '*' character to create "
|
||||
"a 'catchall' alias (ex: *@domain.tld)."
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -143,6 +151,52 @@ class AccountFormMail(forms.Form, DynamicForm):
|
||||
"""Ensure lower case emails"""
|
||||
return self.cleaned_data["email"].lower()
|
||||
|
||||
def create_mailbox(self, user, account):
|
||||
locpart, domname = split_mailbox(self.cleaned_data["email"])
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise NotFound(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
try:
|
||||
Mailbox.objects.get(address=locpart, domain=domain)
|
||||
except Mailbox.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict(
|
||||
_("Mailbox %s already exists" % self.cleaned_data["email"])
|
||||
)
|
||||
events.raiseEvent("CanCreate", user, "mailboxes")
|
||||
self.mb = Mailbox(address=locpart, domain=domain, user=account,
|
||||
use_domain_quota=self.cleaned_data["quota_act"])
|
||||
self.mb.set_quota(self.cleaned_data["quota"],
|
||||
user.has_perm("admin.add_domain"))
|
||||
self.mb.save(creator=user)
|
||||
|
||||
def update_mailbox(self, user, account):
|
||||
newaddress = None
|
||||
if self.cleaned_data["email"] != self.mb.full_address:
|
||||
newaddress = self.cleaned_data["email"]
|
||||
elif account.group == "SimpleUsers" and account.username != self.mb.full_address:
|
||||
newaddress = account.username
|
||||
if newaddress is not None:
|
||||
local_part, domname = split_mailbox(newaddress)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise NotFound(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
self.mb.rename(local_part, domain)
|
||||
|
||||
self.mb.use_domain_quota = self.cleaned_data["quota_act"]
|
||||
override_rules = True \
|
||||
if not self.mb.quota or user.has_perm("admin.add_domain") \
|
||||
else False
|
||||
self.mb.set_quota(self.cleaned_data["quota"], override_rules)
|
||||
self.mb.save()
|
||||
|
||||
def save(self, user, account):
|
||||
if self.cleaned_data["email"] == "":
|
||||
return None
|
||||
@@ -151,74 +205,43 @@ class AccountFormMail(forms.Form, DynamicForm):
|
||||
self.cleaned_data["quota"] = None
|
||||
|
||||
if not hasattr(self, "mb") or self.mb is None:
|
||||
locpart, domname = split_mailbox(self.cleaned_data["email"])
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
try:
|
||||
Mailbox.objects.get(address=locpart, domain=domain)
|
||||
except Mailbox.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise AdminError(_("Mailbox %s already exists" % self.cleaned_data["email"]))
|
||||
events.raiseEvent("CanCreate", user, "mailboxes")
|
||||
self.mb = Mailbox(address=locpart, domain=domain, user=account,
|
||||
use_domain_quota=self.cleaned_data["quota_act"])
|
||||
self.mb.set_quota(self.cleaned_data["quota"],
|
||||
user.has_perm("admin.add_domain"))
|
||||
self.mb.save(creator=user)
|
||||
self.create_mailbox(user, account)
|
||||
else:
|
||||
newaddress = None
|
||||
if self.cleaned_data["email"] != self.mb.full_address:
|
||||
newaddress = self.cleaned_data["email"]
|
||||
elif account.group == "SimpleUsers" and account.username != self.mb.full_address:
|
||||
newaddress = account.username
|
||||
if newaddress is not None:
|
||||
local_part, domname = split_mailbox(newaddress)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
self.mb.rename(local_part, domain)
|
||||
|
||||
self.mb.use_domain_quota = self.cleaned_data["quota_act"]
|
||||
override_rules = True \
|
||||
if not self.mb.quota or user.has_perm("admin.add_domain") \
|
||||
else False
|
||||
self.mb.set_quota(self.cleaned_data["quota"], override_rules)
|
||||
self.mb.save()
|
||||
self.update_mailbox(user, account)
|
||||
|
||||
account.email = self.cleaned_data["email"]
|
||||
account.save()
|
||||
|
||||
aliases = []
|
||||
for name, value in self.cleaned_data.iteritems():
|
||||
if not name.startswith("aliases"):
|
||||
continue
|
||||
if value == "":
|
||||
continue
|
||||
local_part, domname = split_mailbox(value)
|
||||
try:
|
||||
self.mb.alias_set.get(address=local_part, domain__name=domname)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
events.raiseEvent("CanCreate", user, "mailbox_aliases")
|
||||
al = Alias(address=local_part, enabled=account.is_active)
|
||||
al.domain = Domain.objects.get(name=domname)
|
||||
al.save(int_rcpts=[self.mb], creator=user)
|
||||
aliases.append(value)
|
||||
|
||||
for alias in self.mb.alias_set.all():
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
continue
|
||||
if not len(filter(lambda name: self.cleaned_data[name] == alias.full_address,
|
||||
self.cleaned_data.keys())):
|
||||
if not alias.full_address in aliases:
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
continue
|
||||
alias.delete()
|
||||
else:
|
||||
aliases.remove(alias.full_address)
|
||||
if aliases:
|
||||
events.raiseEvent(
|
||||
"CanCreate", user, "mailbox_aliases", len(aliases)
|
||||
)
|
||||
for alias in aliases:
|
||||
local_part, domname = split_mailbox(alias)
|
||||
try:
|
||||
self.mb.alias_set.get(address=local_part, domain__name=domname)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
al = Alias(address=local_part, enabled=account.is_active)
|
||||
al.domain = Domain.objects.get(name=domname)
|
||||
al.save(int_rcpts=[self.mb], creator=user)
|
||||
|
||||
return self.mb
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.http import QueryDict
|
||||
from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.lib.formutils import (
|
||||
DynamicForm
|
||||
)
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Mailbox, Alias
|
||||
)
|
||||
@@ -14,11 +14,17 @@ from modoboa.extensions.admin.models import (
|
||||
class AliasForm(forms.ModelForm, DynamicForm):
|
||||
email = forms.EmailField(
|
||||
label=ugettext_lazy("Email address"),
|
||||
help_text=ugettext_lazy("The distribution list address. Use the '*' character to create a 'catchall' address (ex: *@domain.tld).")
|
||||
help_text=ugettext_lazy(
|
||||
"The distribution list address. Use the '*' character to create a "
|
||||
"'catchall' address (ex: *@domain.tld)."
|
||||
)
|
||||
)
|
||||
recipients = forms.EmailField(
|
||||
label=ugettext_lazy("Recipients"), required=False,
|
||||
help_text=ugettext_lazy("Mailbox(es) this alias will point to. Indicate only one address per input, press ENTER to add a new input.")
|
||||
help_text=ugettext_lazy(
|
||||
"Mailbox(es) this alias will point to. Indicate only one address "
|
||||
"per input, press ENTER to add a new input."
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -58,7 +64,9 @@ class AliasForm(forms.ModelForm, DynamicForm):
|
||||
except Domain.DoesNotExist:
|
||||
raise forms.ValidationError(_("Domain does not exist"))
|
||||
if not self.user.can_access(domain):
|
||||
raise forms.ValidationError(_("You don't have access to this domain"))
|
||||
raise forms.ValidationError(
|
||||
_("You don't have access to this domain")
|
||||
)
|
||||
return self.cleaned_data["email"]
|
||||
|
||||
def set_recipients(self):
|
||||
@@ -78,7 +86,9 @@ class AliasForm(forms.ModelForm, DynamicForm):
|
||||
continue
|
||||
local_part, domname = split_mailbox(v)
|
||||
if domname is None:
|
||||
raise AdminError("%s %s" % (_("Invalid mailbox"), v))
|
||||
raise BadRequest(
|
||||
u"%s %s" % (_("Invalid mailbox"), v)
|
||||
)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
@@ -94,20 +104,26 @@ class AliasForm(forms.ModelForm, DynamicForm):
|
||||
try:
|
||||
rcpt = Mailbox.objects.get(domain=domain, address=local_part)
|
||||
except Mailbox.DoesNotExist:
|
||||
raise AdminError(_("Local recipient %s not found" % v))
|
||||
raise NotFound(
|
||||
_("Local recipient %s not found" % v)
|
||||
)
|
||||
if rcpt in self.int_rcpts:
|
||||
raise AdminError(_("Recipient %s already present" % v))
|
||||
raise Conflict(
|
||||
_("Recipient %s already present" % v)
|
||||
)
|
||||
self.int_rcpts += [rcpt]
|
||||
total += 1
|
||||
continue
|
||||
|
||||
if v in self.ext_rcpts:
|
||||
raise AdminError(_("Recipient %s already present" % v))
|
||||
raise Conflict(
|
||||
_("Recipient %s already present" % v)
|
||||
)
|
||||
self.ext_rcpts += [v]
|
||||
total += 1
|
||||
|
||||
if total == 0:
|
||||
raise AdminError(_("No recipient defined"))
|
||||
raise BadRequest(_("No recipient defined"))
|
||||
|
||||
def save(self, commit=True):
|
||||
alias = super(AliasForm, self).save(commit=False)
|
||||
|
||||
@@ -2,11 +2,11 @@ from django import forms
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import Conflict
|
||||
from modoboa.lib.formutils import (
|
||||
DomainNameField, YesNoField, DynamicForm, TabForms
|
||||
)
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Mailbox, Alias, Quota
|
||||
)
|
||||
@@ -16,7 +16,10 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
aliases = DomainNameField(
|
||||
label=ugettext_lazy("Alias(es)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy("Alias(es) of this domain. Indicate only one name per input, press ENTER to add a new input.")
|
||||
help_text=ugettext_lazy(
|
||||
"Alias(es) of this domain. Indicate only one name per input, "
|
||||
"press ENTER to add a new input."
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -41,35 +44,48 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
self._create_field(forms.CharField, name, dalias.name, 3)
|
||||
|
||||
def clean(self):
|
||||
"""Custom fields validation.
|
||||
|
||||
We want to prevent duplicate names between domains and domain
|
||||
aliases. Extensions have the possibility to declare other
|
||||
objects (see *CheckDomainName* event).
|
||||
|
||||
The validation way is not very smart...
|
||||
"""
|
||||
super(DomainFormGeneral, self).clean()
|
||||
if len(self._errors):
|
||||
if self._errors:
|
||||
raise forms.ValidationError(self._errors)
|
||||
|
||||
cleaned_data = self.cleaned_data
|
||||
name = cleaned_data["name"]
|
||||
|
||||
try:
|
||||
DomainAlias.objects.get(name=name)
|
||||
except DomainAlias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self._errors["name"] = self.error_class([_("An alias with this name already exists")])
|
||||
del cleaned_data["name"]
|
||||
|
||||
dtypes = events.raiseQueryEvent('CheckDomainName')
|
||||
for dtype, label in [(DomainAlias, _('domain alias'))] + dtypes:
|
||||
try:
|
||||
dtype.objects.get(name=name)
|
||||
except dtype.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self._errors["name"] = self.error_class(
|
||||
[_("A %s with this name already exists" % unicode(label))]
|
||||
)
|
||||
del cleaned_data["name"]
|
||||
break
|
||||
for k in cleaned_data.keys():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if cleaned_data[k] == "":
|
||||
del cleaned_data[k]
|
||||
continue
|
||||
try:
|
||||
Domain.objects.get(name=cleaned_data[k])
|
||||
except Domain.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self._errors[k] = self.error_class([_("A domain with this name already exists")])
|
||||
for dtype, label in [(Domain, _('domain'))] + dtypes:
|
||||
try:
|
||||
dtype.objects.get(name=cleaned_data[k])
|
||||
except dtype.DoesNotExist:
|
||||
continue
|
||||
self._errors[k] = self.error_class(
|
||||
[_("A %s with this name already exists" % unicode(label))]
|
||||
)
|
||||
del cleaned_data[k]
|
||||
|
||||
break
|
||||
return cleaned_data
|
||||
|
||||
def update_mailbox_quotas(self, domain):
|
||||
@@ -91,7 +107,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
username = q['username'].replace('@%s' % self.oldname, '@%s' % domain.name)
|
||||
Quota.objects.filter(username=q['username']).update(username=username)
|
||||
|
||||
def save(self, user, commit=True):
|
||||
def save(self, user, commit=True, domalias_post_create=False):
|
||||
"""Custom save method
|
||||
|
||||
Updating a domain may have consequences on other objects
|
||||
@@ -110,25 +126,29 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
d.save()
|
||||
Mailbox.objects.filter(domain=d, use_domain_quota=True) \
|
||||
.update(quota=d.quota)
|
||||
aliases = []
|
||||
for k, v in self.cleaned_data.iteritems():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if v in ["", None]:
|
||||
continue
|
||||
try:
|
||||
d.domainalias_set.get(name=v)
|
||||
except DomainAlias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
events.raiseEvent("CanCreate", user, "domain_aliases")
|
||||
al = DomainAlias(name=v, target=d, enabled=d.enabled)
|
||||
al.save(creator=user)
|
||||
|
||||
aliases.append(v)
|
||||
for dalias in d.domainalias_set.all():
|
||||
if not len(filter(lambda name: self.cleaned_data[name] == dalias.name,
|
||||
self.cleaned_data.keys())):
|
||||
if not dalias.name in aliases:
|
||||
dalias.delete()
|
||||
else:
|
||||
aliases.remove(dalias.name)
|
||||
if aliases:
|
||||
events.raiseEvent("CanCreate", user, "domain_aliases", len(aliases))
|
||||
for alias in aliases:
|
||||
try:
|
||||
d.domainalias_set.get(name=alias)
|
||||
except DomainAlias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
al = DomainAlias(name=alias, target=d, enabled=d.enabled)
|
||||
al.save(creator=user) if domalias_post_create else al.save()
|
||||
|
||||
if old_mail_homes is not None:
|
||||
self.update_mailbox_quotas(d)
|
||||
@@ -148,7 +168,10 @@ class DomainFormOptions(forms.Form):
|
||||
dom_admin_username = forms.CharField(
|
||||
label=ugettext_lazy("Name"),
|
||||
initial="admin",
|
||||
help_text=ugettext_lazy("The administrator's name. Don't include the domain's name here, it will be automatically appended."),
|
||||
help_text=ugettext_lazy(
|
||||
"The administrator's name. Don't include the domain's name here, "
|
||||
"it will be automatically appended."
|
||||
),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "input-small"}),
|
||||
required=False
|
||||
)
|
||||
@@ -160,8 +183,11 @@ class DomainFormOptions(forms.Form):
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(DomainFormOptions, self).__init__(*args, **kwargs)
|
||||
if False in events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins'):
|
||||
self.fields = {}
|
||||
return
|
||||
if args:
|
||||
if args[0].get("create_dom_admin", "no") == "yes":
|
||||
self.fields["dom_admin_username"].required = True
|
||||
@@ -173,6 +199,8 @@ class DomainFormOptions(forms.Form):
|
||||
return self.cleaned_data["dom_admin_username"]
|
||||
|
||||
def save(self, user, domain):
|
||||
if not self.fields:
|
||||
return
|
||||
if self.cleaned_data["create_dom_admin"] == "no":
|
||||
return
|
||||
username = "%s@%s" % (self.cleaned_data["dom_admin_username"], domain.name)
|
||||
@@ -181,7 +209,8 @@ class DomainFormOptions(forms.Form):
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise AdminError(_("User '%s' already exists" % username))
|
||||
raise Conflict(_("User '%s' already exists" % username))
|
||||
events.raiseEvent("CanCreate", user, "mailboxes")
|
||||
da = User(username=username, email=username, is_active=True)
|
||||
da.set_password("password")
|
||||
da.save()
|
||||
@@ -193,6 +222,7 @@ class DomainFormOptions(forms.Form):
|
||||
mb.save(creator=user)
|
||||
|
||||
if self.cleaned_data["create_aliases"] == "yes":
|
||||
events.raiseEvent("CanCreate", user, "mailbox_aliases")
|
||||
al = Alias(address="postmaster", domain=domain, enabled=True)
|
||||
al.save(int_rcpts=[mb], creator=user)
|
||||
|
||||
@@ -223,5 +253,6 @@ class DomainForm(TabForms):
|
||||
As forms interact with each other, it is easier to make custom
|
||||
code to save them.
|
||||
"""
|
||||
for f in self.forms:
|
||||
self.forms[0]['instance'].save(user, domalias_post_create=True)
|
||||
for f in self.forms[1:]:
|
||||
f["instance"].save(user)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib.exceptions import BadRequest, PermDeniedException
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.extensions.admin.exceptions import BadDestination
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain
|
||||
)
|
||||
@@ -28,10 +28,14 @@ class ForwardForm(forms.Form):
|
||||
for d in rawdata.split(","):
|
||||
local_part, domname = split_mailbox(d)
|
||||
if not local_part or not domname or not len(domname):
|
||||
raise BadDestination("Invalid mailbox syntax for %s" % d)
|
||||
raise BadRequest("Invalid mailbox syntax for %s" % d)
|
||||
try:
|
||||
Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
self.dests += [d]
|
||||
else:
|
||||
raise BadDestination(_("You can't define a forward to a local destination. Please ask your administrator to create an alias instead."))
|
||||
raise PermDeniedException(
|
||||
_("You can't define a forward to a local destination. "
|
||||
"Please ask your administrator to create an alias "
|
||||
"instead.")
|
||||
)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# coding: utf-8
|
||||
from functools import wraps
|
||||
from itertools import chain
|
||||
from django.db.models import Q
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.extensions.admin.models import Alias
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Alias
|
||||
)
|
||||
|
||||
|
||||
def needs_mailbox():
|
||||
@@ -21,7 +25,7 @@ def needs_mailbox():
|
||||
def wrapped_f(request, *args, **kwargs):
|
||||
if request.user.mailbox_set.count():
|
||||
return f(request, *args, **kwargs)
|
||||
raise ModoboaException()
|
||||
raise PermDeniedException(_("A mailbox is required"))
|
||||
return wrapped_f
|
||||
return decorator
|
||||
|
||||
@@ -46,6 +50,16 @@ def get_sort_order(qdict, default, allowed_values=None):
|
||||
|
||||
|
||||
def get_listing_page(objects, pagenum):
|
||||
"""Return specific a listing page.
|
||||
|
||||
A page contains a limited number of elements (see
|
||||
ITEMS_PER_PAGE). If the given page number is wrong, the first page
|
||||
will be always returned.
|
||||
|
||||
:param list objects: object list to paginate
|
||||
:param int pagenum: page number
|
||||
:return: a ``Page`` object
|
||||
"""
|
||||
paginator = Paginator(
|
||||
objects, int(parameters.get_admin("ITEMS_PER_PAGE", app="core"))
|
||||
)
|
||||
@@ -65,8 +79,6 @@ def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
|
||||
:param list grpfilter: group names filters
|
||||
:return: a queryset
|
||||
"""
|
||||
from itertools import chain
|
||||
|
||||
accounts = []
|
||||
if idtfilter is None or not idtfilter or idtfilter == "account":
|
||||
ids = user.objectaccess_set \
|
||||
@@ -104,3 +116,24 @@ def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
|
||||
if idtfilter is not None and idtfilter:
|
||||
aliases = [al for al in aliases if al.type == idtfilter]
|
||||
return chain(accounts, aliases)
|
||||
|
||||
|
||||
def get_domains(user, domfilter=None, searchquery=None, **extrafilters):
|
||||
"""Return all the domains the user can access.
|
||||
|
||||
:param ``User`` user: user object
|
||||
:param str searchquery: filter
|
||||
:rtype: list
|
||||
:return: a list of domains and/or relay domains
|
||||
"""
|
||||
domains = []
|
||||
if domfilter is None or not domfilter or domfilter == 'domain':
|
||||
domains = Domain.objects.get_for_admin(user)
|
||||
if searchquery is not None:
|
||||
q = Q(name__contains=searchquery)
|
||||
q |= Q(domainalias__name__contains=searchquery)
|
||||
domains = domains.filter(q).distinct()
|
||||
extra_domain_entries = events.raiseQueryEvent(
|
||||
'ExtraDomainEntries', user, domfilter, searchquery, **extrafilters
|
||||
)
|
||||
return chain(domains, extra_domain_entries)
|
||||
|
||||
@@ -4,8 +4,8 @@ from optparse import make_option
|
||||
from django.core.management.base import BaseCommand
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.lib.exceptions import InternalError
|
||||
from modoboa.extensions.admin import AdminConsole
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import MailboxOperation
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class Command(BaseCommand):
|
||||
continue
|
||||
try:
|
||||
f(ope)
|
||||
except (OperationError, AdminError) as e:
|
||||
except (OperationError, InternalError) as e:
|
||||
self.logger.critical('%s failed (reason: %s)',
|
||||
ope, str(e).encode('utf-8'))
|
||||
else:
|
||||
|
||||
@@ -75,6 +75,13 @@ class Migration(SchemaMigration):
|
||||
'use_domain_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.User']"})
|
||||
},
|
||||
u'admin.mailboxoperation': {
|
||||
'Meta': {'object_name': 'MailboxOperation'},
|
||||
'argument': ('django.db.models.fields.TextField', [], {}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['admin.Mailbox']", 'null': 'True', 'blank': 'True'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '20'})
|
||||
},
|
||||
'admin.objectdates': {
|
||||
'Meta': {'object_name': 'ObjectDates'},
|
||||
'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from .domain_alias import DomainAlias
|
||||
from .mailbox import Mailbox, Quota, MailboxOperation
|
||||
from .alias import Alias
|
||||
|
||||
__all__ = [
|
||||
'Domain', 'DomainAlias', 'Mailbox', 'Quota', 'Alias', 'MailboxOperation'
|
||||
'Domain', 'DomainAlias', 'Mailbox', 'Quota', 'Alias',
|
||||
'MailboxOperation', 'AdminObject'
|
||||
]
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import reversion
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from .base import DatesAware
|
||||
from modoboa.lib.exceptions import PermDeniedException, BadRequest
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from .mailbox import Mailbox
|
||||
|
||||
|
||||
class Alias(DatesAware):
|
||||
class Alias(AdminObject):
|
||||
"""
|
||||
Mailbox alias.
|
||||
"""
|
||||
address = models.CharField(
|
||||
ugettext_lazy('address'), max_length=254,
|
||||
help_text=ugettext_lazy("The alias address (without the domain part). For a 'catch-all' address, just enter an * character.")
|
||||
help_text=ugettext_lazy(
|
||||
"The alias address (without the domain part). For a 'catch-all' "
|
||||
"address, just enter an * character."
|
||||
)
|
||||
)
|
||||
domain = models.ForeignKey(Domain)
|
||||
mboxes = models.ManyToManyField(
|
||||
@@ -29,6 +33,7 @@ class Alias(DatesAware):
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy("Check to activate this alias")
|
||||
)
|
||||
_objectname = 'MailboxAlias'
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
@@ -80,8 +85,7 @@ class Alias(DatesAware):
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
grant_access_to_object(creator, self, is_owner=True)
|
||||
events.raiseEvent("MailboxAliasCreated", creator, self)
|
||||
super(Alias, self).post_create(creator)
|
||||
if creator.is_superuser:
|
||||
for admin in self.domain.admins:
|
||||
grant_access_to_object(admin, self)
|
||||
@@ -96,14 +100,7 @@ class Alias(DatesAware):
|
||||
del kwargs['int_rcpts']
|
||||
else:
|
||||
int_rcpts = []
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(Alias, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
curaliases = self.aliases.all()
|
||||
curmboxes = self.mboxes.all()
|
||||
for t in int_rcpts:
|
||||
@@ -120,13 +117,6 @@ class Alias(DatesAware):
|
||||
if not t in int_rcpts:
|
||||
self.mboxes.remove(t)
|
||||
|
||||
def delete(self):
|
||||
from modoboa.lib.permissions import ungrant_access_to_object
|
||||
|
||||
events.raiseEvent("MailboxAliasDeleted", self)
|
||||
ungrant_access_to_object(self)
|
||||
super(Alias, self).delete()
|
||||
|
||||
def get_recipients(self):
|
||||
"""Return the recipients list
|
||||
|
||||
@@ -153,12 +143,12 @@ class Alias(DatesAware):
|
||||
|
||||
"""
|
||||
if len(row) < expected_elements:
|
||||
raise AdminError(_("Invalid line: %s" % row))
|
||||
raise BadRequest(_("Invalid line: %s" % row))
|
||||
localpart, domname = split_mailbox(row[1].strip())
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(_("Domain '%s' does not exist" % domname))
|
||||
raise BadRequest(_("Domain '%s' does not exist" % domname))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
self.address = localpart
|
||||
@@ -185,7 +175,7 @@ class Alias(DatesAware):
|
||||
target = Mailbox.objects.get(address=localpart,
|
||||
domain__name=domname)
|
||||
except Mailbox.DoesNotExist:
|
||||
raise AdminError(_("Local recipient %s not found" % rcpt))
|
||||
raise BadRequest(_("Local recipient %s not found" % rcpt))
|
||||
int_rcpts += [target]
|
||||
self.save(int_rcpts=int_rcpts, ext_rcpts=ext_rcpts, creator=user)
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from django.db import models
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.permissions import (
|
||||
grant_access_to_object, ungrant_access_to_object
|
||||
)
|
||||
|
||||
|
||||
class ObjectDates(models.Model):
|
||||
@@ -27,7 +31,7 @@ class ObjectDates(models.Model):
|
||||
obj.dates = dates
|
||||
|
||||
|
||||
class DatesAware(models.Model):
|
||||
class AdminObject(models.Model):
|
||||
"""Abstract model to support dates
|
||||
|
||||
Inherit from this model to automatically add the "dates" feature
|
||||
@@ -35,14 +39,11 @@ class DatesAware(models.Model):
|
||||
saves.
|
||||
"""
|
||||
dates = models.ForeignKey(ObjectDates)
|
||||
_objectname = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
ObjectDates.set_for_object(self)
|
||||
super(DatesAware, self).save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def creation(self):
|
||||
return self.dates.creation
|
||||
@@ -50,3 +51,29 @@ class DatesAware(models.Model):
|
||||
@property
|
||||
def last_modification(self):
|
||||
return self.dates.last_modification
|
||||
|
||||
@property
|
||||
def objectname(self):
|
||||
if self._objectname is None:
|
||||
return self.__class__.__name__
|
||||
return self._objectname
|
||||
|
||||
def post_create(self, creator):
|
||||
grant_access_to_object(creator, self, is_owner=True)
|
||||
events.raiseEvent("%sCreated" % self.objectname, creator, self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
ObjectDates.set_for_object(self)
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(AdminObject, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
|
||||
def delete(self):
|
||||
events.raiseEvent("%sDeleted" % self.objectname, self)
|
||||
ungrant_access_to_object(self)
|
||||
super(AdminObject, self).delete()
|
||||
|
||||
@@ -4,9 +4,9 @@ from django.db.models.manager import Manager
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.contrib.contenttypes import generic
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.exceptions import BadRequest
|
||||
from modoboa.core.models import User, ObjectAccess
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from .base import DatesAware
|
||||
from .base import AdminObject
|
||||
|
||||
|
||||
class DomainManager(Manager):
|
||||
@@ -22,7 +22,9 @@ class DomainManager(Manager):
|
||||
return self.get_query_set().filter(owners__user=admin)
|
||||
|
||||
|
||||
class Domain(DatesAware):
|
||||
class Domain(AdminObject):
|
||||
"""Mail domain.
|
||||
"""
|
||||
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy("The domain name"))
|
||||
quota = models.IntegerField(
|
||||
@@ -56,6 +58,10 @@ class Domain(DatesAware):
|
||||
def mbalias_count(self):
|
||||
return self.alias_set.count()
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return [{"name": "domain", "label": _("Domain"), "type": "dom"}]
|
||||
|
||||
@property
|
||||
def admins(self):
|
||||
"""Return the domain administrators of this domain
|
||||
@@ -64,6 +70,10 @@ class Domain(DatesAware):
|
||||
"""
|
||||
return [oa.user for oa in self.owners.filter(user__is_superuser=False)]
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
return self.domainalias_set
|
||||
|
||||
def add_admin(self, account):
|
||||
"""Add a new administrator for this domain
|
||||
|
||||
@@ -80,11 +90,15 @@ class Domain(DatesAware):
|
||||
grant_access_to_object(account, al)
|
||||
|
||||
def remove_admin(self, account):
|
||||
"""Remove an administrator for this domain
|
||||
"""Remove an administrator of this domain.
|
||||
|
||||
:param User account: the administrotor to remove
|
||||
:param User account: administrator to remove
|
||||
"""
|
||||
from modoboa.lib.permissions import ungrant_access_to_object
|
||||
from modoboa.lib.permissions import \
|
||||
ungrant_access_to_object, get_object_owner
|
||||
|
||||
if get_object_owner(self) == account:
|
||||
events.raiseEvent('DomainOwnershipRemoved', account, self)
|
||||
ungrant_access_to_object(self, account)
|
||||
for mb in self.mailbox_set.all():
|
||||
if mb.user.has_perm("admin.add_domain"):
|
||||
@@ -92,60 +106,55 @@ class Domain(DatesAware):
|
||||
ungrant_access_to_object(mb, account)
|
||||
ungrant_access_to_object(mb.user, account)
|
||||
for al in self.alias_set.all():
|
||||
ungrant_access_to_object(al, account)
|
||||
ungrant_access_to_object(al, account)
|
||||
|
||||
def delete(self, fromuser, keepdir=False):
|
||||
from modoboa.lib.permissions import \
|
||||
ungrant_access_to_object, ungrant_access_to_objects
|
||||
"""Custom delete method.
|
||||
"""
|
||||
from modoboa.lib.permissions import ungrant_access_to_objects
|
||||
from .mailbox import Quota
|
||||
|
||||
if self.domainalias_set.count():
|
||||
events.raiseEvent("DomainAliasDeleted", self.domainalias_set.all())
|
||||
ungrant_access_to_objects(self.domainalias_set.all())
|
||||
if self.mailbox_set.count():
|
||||
Quota.objects.filter(username__contains='@%s' % self.name).delete()
|
||||
events.raiseEvent("DeleteMailbox", self.mailbox_set.all())
|
||||
ungrant_access_to_objects(self.mailbox_set.all())
|
||||
if self.alias_set.count():
|
||||
events.raiseEvent("MailboxAliasDelete", self.alias_set.all())
|
||||
events.raiseEvent("MailboxAliasDeleted", self.alias_set.all())
|
||||
ungrant_access_to_objects(self.alias_set.all())
|
||||
if parameters.get_admin("AUTO_ACCOUNT_REMOVAL") == "yes":
|
||||
for account in User.objects.filter(mailbox__domain__name=self.name):
|
||||
account.delete(fromuser, keepdir)
|
||||
events.raiseEvent("DeleteDomain", self)
|
||||
ungrant_access_to_object(self)
|
||||
elif self.mailbox_set.count():
|
||||
Quota.objects.filter(username__contains='@%s' % self.name).delete()
|
||||
events.raiseEvent("MailboxDeleted", self.mailbox_set.all())
|
||||
ungrant_access_to_objects(self.mailbox_set.all())
|
||||
super(Domain, self).delete()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
grant_access_to_object(creator, self, is_owner=True)
|
||||
events.raiseEvent("CreateDomain", creator, self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(Domain, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
|
||||
def from_csv(self, user, row):
|
||||
if len(row) < 4:
|
||||
raise AdminError(_("Invalid line"))
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
try:
|
||||
self.quota = int(row[2].strip())
|
||||
except ValueError:
|
||||
raise AdminError(_("Invalid quota value for domain '%s'" % self.name))
|
||||
raise BadRequest(_("Invalid quota value for domain '%s'" % self.name))
|
||||
self.enabled = (row[3].strip() == 'True')
|
||||
self.save(creator=user)
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
csvwriter.writerow(["domain", self.name, self.quota, self.enabled])
|
||||
for dalias in self.domainalias_set.all():
|
||||
dalias.to_csv(csvwriter)
|
||||
|
||||
def post_create(self, creator):
|
||||
"""Post creation actions.
|
||||
|
||||
:param ``User`` creator: user whos created this domain
|
||||
"""
|
||||
super(Domain, self).post_create(creator)
|
||||
for domalias in self.domainalias_set.all():
|
||||
domalias.post_create(creator)
|
||||
|
||||
reversion.register(Domain)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import reversion
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from .base import DatesAware
|
||||
from modoboa.lib.exceptions import BadRequest
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
|
||||
|
||||
class DomainAlias(DatesAware):
|
||||
class DomainAlias(AdminObject):
|
||||
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy("The alias name"))
|
||||
target = models.ForeignKey(
|
||||
@@ -28,27 +27,6 @@ class DomainAlias(DatesAware):
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
grant_access_to_object(creator, self, is_owner=True)
|
||||
events.raiseEvent("DomainAliasCreated", creator, self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(DomainAlias, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
|
||||
def delete(self):
|
||||
from modoboa.lib.permissions import ungrant_access_to_object
|
||||
events.raiseEvent("DomainAliasDeleted", self)
|
||||
ungrant_access_to_object(self)
|
||||
super(DomainAlias, self).delete()
|
||||
|
||||
def from_csv(self, user, row):
|
||||
"""Create a domain alias from a CSV row
|
||||
|
||||
@@ -58,13 +36,13 @@ class DomainAlias(DatesAware):
|
||||
:param row: a list containing the alias definition
|
||||
"""
|
||||
if len(row) < 4:
|
||||
raise AdminError(_("Invalid line"))
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
domname = row[2].strip()
|
||||
try:
|
||||
self.target = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(_("Unknown domain %s" % domname))
|
||||
raise BadRequest(_("Unknown domain %s" % domname))
|
||||
self.enabled = row[3].strip() == 'True'
|
||||
self.save(creator=user)
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import BadRequest, InternalError
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from .base import DatesAware
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class MailboxManager(Manager):
|
||||
return self.get_query_set().filter(qf)
|
||||
|
||||
|
||||
class Mailbox(DatesAware):
|
||||
class Mailbox(AdminObject):
|
||||
address = models.CharField(
|
||||
ugettext_lazy('address'), max_length=252,
|
||||
help_text=ugettext_lazy("Mailbox address (without the @domain.tld part)")
|
||||
@@ -110,7 +110,7 @@ class Mailbox(DatesAware):
|
||||
"doveadm user %s -f home" % self.full_address, **options
|
||||
)
|
||||
if code:
|
||||
raise AdminError(_("Failed to retrieve mailbox location (%s)" % output))
|
||||
raise InternalError(_("Failed to retrieve mailbox location (%s)" % output))
|
||||
self.__mail_home = output.strip()
|
||||
return self.__mail_home
|
||||
|
||||
@@ -158,13 +158,13 @@ class Mailbox(DatesAware):
|
||||
else:
|
||||
self.quota = 0
|
||||
elif int(value) > self.domain.quota and not override_rules:
|
||||
raise AdminError(
|
||||
raise BadRequest(
|
||||
_("Quota is greater than the allowed domain's limit (%dM)" % self.domain.quota)
|
||||
)
|
||||
else:
|
||||
self.quota = value
|
||||
if not self.quota and self.domain.quota and not override_rules:
|
||||
raise AdminError(_("A quota is required"))
|
||||
raise BadRequest(_("A quota is required"))
|
||||
|
||||
def get_quota(self):
|
||||
"""Get quota limit.
|
||||
@@ -186,8 +186,7 @@ class Mailbox(DatesAware):
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
grant_access_to_object(creator, self, True)
|
||||
events.raiseEvent("CreateMailbox", creator, self)
|
||||
super(Mailbox, self).post_create(creator)
|
||||
if creator.is_superuser and not self.user.has_perm("admin.add_domain"):
|
||||
# A super user is creating a new mailbox. Give
|
||||
# access to that mailbox (and the associated
|
||||
@@ -199,14 +198,7 @@ class Mailbox(DatesAware):
|
||||
grant_access_to_object(admin, self.user)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(Mailbox, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
try:
|
||||
q = self.quota_value
|
||||
except Quota.DoesNotExist:
|
||||
@@ -246,7 +238,7 @@ def mailbox_deleted_handler(sender, **kwargs):
|
||||
from modoboa.lib.permissions import ungrant_access_to_object
|
||||
|
||||
mb = kwargs['instance']
|
||||
events.raiseEvent("DeleteMailbox", mb)
|
||||
events.raiseEvent("MailboxDeleted", mb)
|
||||
ungrant_access_to_object(mb)
|
||||
for alias in mb.alias_set.all():
|
||||
alias.mboxes.remove(mb)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @module admin
|
||||
* @desc Utility classes for the admin application
|
||||
*/
|
||||
|
||||
var Admin = function(options) {
|
||||
Listing.call(this, options);
|
||||
};
|
||||
@@ -12,6 +17,7 @@ Admin.prototype = {
|
||||
Listing.prototype.initialize.call(this, options);
|
||||
this.options = $.extend({}, this.defaults, this.options);
|
||||
this.options.defcallback = $.proxy(this.list_cb, this);
|
||||
this.tag_handlers = {};
|
||||
|
||||
this.navobj = new History(this.options);
|
||||
|
||||
@@ -37,12 +43,61 @@ Admin.prototype = {
|
||||
list_cb: function(data) {
|
||||
$("#listing").html(data.table);
|
||||
this.update_listing(data);
|
||||
$("a.filter").click($.proxy(this.filter_by_tag, this));
|
||||
},
|
||||
|
||||
listen: function() {
|
||||
$("#searchform").submit($.proxy(this.do_search, this));
|
||||
},
|
||||
|
||||
register_tag_handler: function(name, handler) {
|
||||
this.tag_handlers[name] = handler;
|
||||
if (this.navobj.getparam(name + "filter") != undefined) {
|
||||
var text = this.navobj.getparam(name + "filter");
|
||||
$("#searchform").parent().after(this.make_tag(text, name));
|
||||
}
|
||||
},
|
||||
|
||||
generic_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") == undefined && $link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj.setparam(tag + "filter", text).update();
|
||||
$("#searchform").parent().after(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
make_tag: function(text, type) {
|
||||
var $tag = $("<a />", {"name": type, "class" : "btn btn-mini", "html": text});
|
||||
var $i = $("<i />", {"class" : "icon-remove"}).prependTo($tag);
|
||||
|
||||
$tag.click($.proxy(this.remove_tag, this));
|
||||
return $tag;
|
||||
},
|
||||
|
||||
remove_tag: function(e) {
|
||||
var $tag = $(e.target);
|
||||
|
||||
if ($tag.is("i")) {
|
||||
$tag = $tag.parent();
|
||||
}
|
||||
e.preventDefault();
|
||||
this.navobj.delparam($tag.attr("name") + "filter").update();
|
||||
$tag.remove();
|
||||
},
|
||||
|
||||
filter_by_tag: function(e) {
|
||||
var $link = $(e.target);
|
||||
e.preventDefault();
|
||||
|
||||
for (var name in this.tag_handlers) {
|
||||
if (this.tag_handlers[name].apply(this, [name, $link])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
do_search: function(e) {
|
||||
e.preventDefault();
|
||||
var squery = $("#searchquery").val();
|
||||
@@ -70,7 +125,7 @@ Admin.prototype = {
|
||||
$("#import_status").css("display", "none");
|
||||
if (status == "ok") {
|
||||
$("#modalbox").modal('hide');
|
||||
this.reload_listing({respmsg: msg});
|
||||
this.reload_listing(msg);
|
||||
} else {
|
||||
$("#import_result").addClass("alert alert-error");
|
||||
$("#import_result").html(msg);
|
||||
@@ -88,8 +143,8 @@ Admin.prototype = {
|
||||
|
||||
reload_listing: function(data) {
|
||||
this.navobj.update(true);
|
||||
if (data.respmsg) {
|
||||
$("body").notify("success", data.respmsg, 2000);
|
||||
if (data) {
|
||||
$("body").notify("success", data, 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -104,6 +159,11 @@ var Domains = function(options) {
|
||||
};
|
||||
|
||||
Domains.prototype = {
|
||||
initialize: function(options) {
|
||||
Admin.prototype.initialize.call(this, options);
|
||||
this.register_tag_handler("dom", this.generic_tag_handler);
|
||||
},
|
||||
|
||||
list_cb: function(data) {
|
||||
Admin.prototype.list_cb.call(this, data);
|
||||
var deloptions = (data.handle_mailboxes)
|
||||
@@ -120,6 +180,7 @@ Domains.prototype = {
|
||||
checkboxes: deloptions,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
$(document).trigger('domain_listing_refresh');
|
||||
},
|
||||
|
||||
change_inputs_state: function(value) {
|
||||
@@ -152,11 +213,11 @@ Domains.prototype = {
|
||||
|
||||
domadminsform_init: function() {
|
||||
$("a[name=removeperm]").click(function(e) {
|
||||
var $tr = $(this).parent().parent();
|
||||
var $tr = $(this).parents('tr');
|
||||
simple_ajax_request.apply(this, [e, {
|
||||
ok_cb: function(resp) {
|
||||
$tr.remove();
|
||||
if (!$("#domadmins").children("tr").length) {
|
||||
if (!$("#domadmins").find("tr").length) {
|
||||
$("#admins").html('<div class="alert alert-info">'
|
||||
+ gettext("No domain administrator defined") + "</div>");
|
||||
}
|
||||
@@ -174,7 +235,6 @@ Domains.prototype = {
|
||||
1: this.optionsform_prefill
|
||||
},
|
||||
error_callbacks: {
|
||||
1: this.generalform_init,
|
||||
2: $.proxy(this.optionsform_init, this)
|
||||
},
|
||||
success_callback: $.proxy(this.reload_listing, this)
|
||||
@@ -194,7 +254,6 @@ Domains.prototype = {
|
||||
}, this));
|
||||
$(document).trigger('domform_init');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Domains.prototype = $.extend({}, Admin.prototype, Domains.prototype);
|
||||
@@ -209,14 +268,8 @@ var Identities = function(options) {
|
||||
Identities.prototype = {
|
||||
initialize: function(options) {
|
||||
Admin.prototype.initialize.call(this, options);
|
||||
if (this.navobj.getparam("idtfilter") != undefined) {
|
||||
var text = this.navobj.getparam("idtfilter");
|
||||
$("#searchform").parent().after(this.make_tag(text, "idt"));
|
||||
}
|
||||
if (this.navobj.getparam("grpfilter") != undefined) {
|
||||
var text = this.navobj.getparam("grpfilter");
|
||||
$("#searchform").parent().after(this.make_tag(text, "grp"));
|
||||
}
|
||||
this.register_tag_handler("idt", this.generic_tag_handler);
|
||||
this.register_tag_handler("grp", this.grp_tag_handler);
|
||||
},
|
||||
|
||||
listen: function() {
|
||||
@@ -237,7 +290,6 @@ Identities.prototype = {
|
||||
if (data.handle_mailboxes == "yes") {
|
||||
deloptions = {keepdir: gettext("Do not delete mailbox directory")};
|
||||
}
|
||||
$("a.filter").click($.proxy(this.filter_by_tag, this));
|
||||
|
||||
$("a[name=delaccount]").confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
@@ -252,46 +304,20 @@ Identities.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
make_tag: function(text, type) {
|
||||
var $tag = $("<a />", {"name": type, "class" : "btn btn-mini", "html": text});
|
||||
var $i = $("<i />", {"class" : "icon-remove"}).prependTo($tag);
|
||||
|
||||
$tag.click($.proxy(this.remove_tag, this));
|
||||
return $tag;
|
||||
},
|
||||
|
||||
remove_tag: function(e) {
|
||||
var $tag = $(e.target);
|
||||
|
||||
if ($tag.is("i")) {
|
||||
$tag = $tag.parent();
|
||||
}
|
||||
e.preventDefault();
|
||||
this.navobj.delparam($tag.attr("name") + "filter").update();
|
||||
$tag.remove();
|
||||
},
|
||||
|
||||
filter_by_tag: function(e) {
|
||||
var $link = $(e.target);
|
||||
e.preventDefault();
|
||||
|
||||
if (this.navobj.getparam("idtfilter") == undefined && $link.hasClass("idt")) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj.setparam("idtfilter", text).update();
|
||||
$("#searchform").parent().after(this.make_tag(text, "idt"));
|
||||
return;
|
||||
}
|
||||
if (this.navobj.getparam("grpfilter") == undefined && $link.hasClass("grp")) {
|
||||
grp_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") == undefined && $link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj
|
||||
.setparam("idtfilter", "account")
|
||||
.setparam("grpfilter", text)
|
||||
.setparam(tag + "filter", text)
|
||||
.update();
|
||||
if ($("a[name=idt]").length == 0) {
|
||||
$("#searchform").parent().after(this.make_tag("account", "idt"));
|
||||
}
|
||||
$("#searchform").parent().after(this.make_tag(text, "grp"));
|
||||
$("#searchform").parent().after(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
simpleuser_mode: function() {
|
||||
@@ -368,20 +394,15 @@ Identities.prototype = {
|
||||
transition_callbacks: {
|
||||
1: this.mailform_prefill
|
||||
},
|
||||
error_callbacks: {
|
||||
1: $.proxy(this.generalform_init, this),
|
||||
2: $.proxy(this.mailform_init, this)
|
||||
},
|
||||
success_callback: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
},
|
||||
|
||||
editaccount_cb: function() {
|
||||
this.accountform_init();
|
||||
$('.submit').one('click', $.proxy(function(e) {
|
||||
$('.submit').on('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "accountform",
|
||||
error_cb: $.proxy(this.editaccount_cb, this),
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
@@ -394,10 +415,9 @@ Identities.prototype = {
|
||||
choices: get_domains_list
|
||||
});
|
||||
$("#id_recipients").dynamic_input();
|
||||
$(".submit").one('click', $.proxy(function(e) {
|
||||
$(".submit").on('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "aliasform",
|
||||
error_cb: $.proxy(this.aliasform_cb, this),
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
|
||||
@@ -1,45 +1,6 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import tables
|
||||
from templatetags.admin_tags import domain_actions
|
||||
|
||||
|
||||
class DomainsTable(tables.Table):
|
||||
tableid = "objects_table"
|
||||
idkey = "id"
|
||||
styles = "table"
|
||||
|
||||
name = tables.LinkColumn(
|
||||
"name", label=ugettext_lazy("Name"),
|
||||
urlpattern="modoboa.extensions.admin.views.domain.editdomain",
|
||||
title=_("Edit domain"), modal=True, modalcb="admin.domainform_cb",
|
||||
sortable=True, sort_order="name"
|
||||
)
|
||||
domaliases = tables.Column(
|
||||
"domainalias_set", label=ugettext_lazy("Alias(es)"), safe=True,
|
||||
sortable=True, sort_order="domainalias__name"
|
||||
)
|
||||
actions = tables.ActionColumn("actions", label=ugettext_lazy("Actions"),
|
||||
defvalue=domain_actions)
|
||||
|
||||
cols_order = ["name", "domaliases", "actions"]
|
||||
|
||||
def __init__(self, request, doms):
|
||||
super(DomainsTable, self).__init__(request)
|
||||
self.populate(self._rows_from_model(doms))
|
||||
|
||||
def parse_domainalias_set(self, aliases):
|
||||
if not len(aliases.all()):
|
||||
return "---"
|
||||
res = ""
|
||||
for da in aliases.all():
|
||||
res += "%s<br/>" % da.name
|
||||
return res
|
||||
|
||||
def row_class(self, request, domain):
|
||||
if not domain.enabled:
|
||||
return "muted"
|
||||
return ""
|
||||
|
||||
|
||||
class ExtensionsTable(tables.Table):
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
{% extends "common/generic_modal_form.html" %}
|
||||
{% load lib_tags %}
|
||||
{% extends "common/generic_modal_form.html" %}{% load lib_tags %}
|
||||
{% block formbody %}
|
||||
{% render_field form.email %}
|
||||
{% render_fields_group form "recipients" %}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load i18n lib_tags %}
|
||||
{% load i18n lib_tags %}{% if form.fields %}
|
||||
{% render_field form.create_dom_admin %}
|
||||
{% render_field_appended form.dom_admin_username "@" %}
|
||||
{% render_field form.create_aliases %}
|
||||
{% else %}<div class="alert alert-warning">{% trans "You can't create a new domain administrator" %}</div>{% endif %}
|
||||
|
||||
@@ -7,5 +7,6 @@ $(document).ready(function() {
|
||||
admin = new Domains();
|
||||
});
|
||||
</script>
|
||||
{% extra_static_content "domains" user %}
|
||||
{% endblock %}
|
||||
{% block actions %}{% domains_menu "domains" user %}{% endblock %}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{% load i18n admin_tags lib_tags %}
|
||||
<table id="{{ tableid }}" class="table {{ styles }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
|
||||
<th id="aliases" width="">{% trans "Alias(es)" %}</th>
|
||||
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for domain in domains %}
|
||||
<tr id="{{ domain.id }}" class="{% if not domain.enabled %}muted{% endif %}" name="{{ forloop.counter }}">
|
||||
<td name="name">{% domain_modify_link domain %}</td>
|
||||
<td name="aliases">{% domain_aliases domain %} </td>
|
||||
<td name="tags">{% render_tags domain.tags %}</td>
|
||||
<td name="actions">{% domain_actions user domain %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -20,5 +20,6 @@ $(document).ready(function() {
|
||||
admin = new Identities();
|
||||
});
|
||||
</script>
|
||||
{% extra_static_content "identities" user %}
|
||||
{% endblock %}
|
||||
{% block actions %}{% identities_menu user %}{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "admin/header.html" %}{% load i18n admin_tags lib_tags %}
|
||||
{% extends "admin/header.html" %}{% load i18n admin_tags %}
|
||||
{% block html_head %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/jquery.sortable.css" />
|
||||
|
||||
@@ -15,6 +15,16 @@ genders = {
|
||||
|
||||
@register.simple_tag
|
||||
def domains_menu(selection, user):
|
||||
"""Specific menu for domain related operations.
|
||||
|
||||
Corresponds to the menu visible on the left column when you go to
|
||||
Domains.
|
||||
|
||||
:param str selection: menu entry currently selected
|
||||
:param ``User`` user: connected user
|
||||
:rtype: str
|
||||
:return: rendered menu (as HTML)
|
||||
"""
|
||||
if not user.has_perm("admin.add_domain"):
|
||||
return ""
|
||||
|
||||
@@ -25,6 +35,9 @@ def domains_menu(selection, user):
|
||||
"modal": True,
|
||||
"modalcb": "admin.newdomain_cb",
|
||||
"url": reverse("modoboa.extensions.admin.views.domain.newdomain")},
|
||||
]
|
||||
entries += events.raiseQueryEvent("ExtraDomainMenuEntries", user)
|
||||
entries += [
|
||||
{"name": "import",
|
||||
"label": _("Import"),
|
||||
"img": "icon-folder-open",
|
||||
@@ -111,23 +124,28 @@ def identities_menu(user):
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_actions(user, domid):
|
||||
from modoboa.extensions.admin.models import Domain
|
||||
def domain_actions(user, domain):
|
||||
actions = []
|
||||
if domain.__class__.__name__ == 'Domain':
|
||||
actions = [
|
||||
{"name": "listidentities",
|
||||
"url": reverse("modoboa.extensions.admin.views.identity.identities") + "#list/?searchquery=@%s" % domain.name,
|
||||
"title": _("View the domain's identities"),
|
||||
"img": "icon-user"}
|
||||
]
|
||||
if user.has_perm("admin.delete_domain"):
|
||||
actions.append({
|
||||
"name": "deldomain",
|
||||
"url": reverse(
|
||||
"modoboa.extensions.admin.views.domain.deldomain",
|
||||
args=[domain.id]
|
||||
),
|
||||
"title": _("Delete %s?" % domain.name),
|
||||
"img": "icon-trash"
|
||||
})
|
||||
else:
|
||||
actions = events.raiseQueryEvent('GetDomainActions', user, domain)
|
||||
|
||||
domain = Domain.objects.get(pk=domid)
|
||||
actions = [
|
||||
{"name": "listidentities",
|
||||
"url": reverse("modoboa.extensions.admin.views.identity.identities") + "#list/?searchquery=@%s" % domain.name,
|
||||
"title": _("View the domain's identities"),
|
||||
"img": "icon-user"}
|
||||
]
|
||||
if user.has_perm("admin.delete_domain"):
|
||||
actions.append({
|
||||
"name": "deldomain",
|
||||
"url": reverse("modoboa.extensions.admin.views.domain.deldomain", args=[domid]),
|
||||
"title": _("Delete %s?" % domain.name),
|
||||
"img": "icon-trash"
|
||||
})
|
||||
return render_actions(actions)
|
||||
|
||||
|
||||
@@ -153,8 +171,48 @@ def identity_actions(user, ident):
|
||||
return render_actions(actions)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_modify_link(domain):
|
||||
linkdef = {"label": domain.name, "modal": True}
|
||||
if domain.__class__.__name__ == "Domain":
|
||||
linkdef["url"] = reverse(
|
||||
"modoboa.extensions.admin.views.domain.editdomain",
|
||||
args=[domain.id]
|
||||
)
|
||||
linkdef["modalcb"] = "admin.domainform_cb"
|
||||
else:
|
||||
tmp = events.raiseDictEvent('GetDomainModifyLink', domain)
|
||||
for key in ['url', 'modalcb']:
|
||||
linkdef[key] = tmp[key]
|
||||
return render_link(linkdef)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_aliases(domain):
|
||||
"""Display domain aliases of this domain.
|
||||
|
||||
:param domain:
|
||||
:rtype: str
|
||||
"""
|
||||
if not domain.aliases.count():
|
||||
return '---'
|
||||
res = ''
|
||||
for alias in domain.aliases.all():
|
||||
res += '%s<br/>' % alias.name
|
||||
return res
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def identity_modify_link(identity, active_tab='default'):
|
||||
"""Return the appropriate modification link.
|
||||
|
||||
According to the identity type, a specific modification link (URL)
|
||||
must be used.
|
||||
|
||||
:param identity: a ``User`` or ``Alias`` instance
|
||||
:param str active_tab: the tab to display
|
||||
:rtype: str
|
||||
"""
|
||||
linkdef = {"label": identity.identity, "modal": True}
|
||||
if identity.__class__.__name__ == "User":
|
||||
linkdef["url"] = reverse(
|
||||
|
||||
@@ -19,9 +19,9 @@ class AccountTestCase(ModoTestCase):
|
||||
username="tester@test.com", first_name="Tester", last_name="Toto",
|
||||
password1="toto", password2="toto", role="SimpleUsers",
|
||||
quota_act=True,
|
||||
is_active=True, email="tester@test.com", stepid=2
|
||||
is_active=True, email="tester@test.com", stepid='step2'
|
||||
)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.newaccount"),
|
||||
values
|
||||
)
|
||||
@@ -39,26 +39,26 @@ class AccountTestCase(ModoTestCase):
|
||||
self.assertEqual(mb.domain.mailbox_count, 3)
|
||||
|
||||
values["username"] = "pouet@test.com"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[account.id]),
|
||||
values
|
||||
)
|
||||
mb = Mailbox.objects.get(pk=mb.id)
|
||||
self.assertEqual(mb.full_address, "pouet@test.com")
|
||||
self.check_ajax_get(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount",
|
||||
args=[account.id]),
|
||||
{}
|
||||
)
|
||||
|
||||
def _set_quota(self, email, value, expected_status="ok"):
|
||||
def _set_quota(self, email, value, expected_status=200):
|
||||
account = User.objects.get(username=email)
|
||||
values = {
|
||||
"username": email, "role": "SimpleUsers", "quota_act": False,
|
||||
"is_active": True, "quota": value, "email": email
|
||||
}
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[account.id]),
|
||||
values, status=expected_status
|
||||
@@ -70,7 +70,7 @@ class AccountTestCase(ModoTestCase):
|
||||
def test_set_nul_quota_as_domainadmin(self):
|
||||
self.clt.logout()
|
||||
self.assertTrue(self.clt.login(username="admin@test.com", password="toto"))
|
||||
self._set_quota("user@test.com", 0, "ko")
|
||||
self._set_quota("user@test.com", 0, 400)
|
||||
self.clt.logout()
|
||||
self.assertTrue(self.clt.login(username="admin@test2.com", password="toto"))
|
||||
self._set_quota("user@test2.com", 0)
|
||||
@@ -92,25 +92,24 @@ class PermissionsTestCase(ModoTestCase):
|
||||
self.clt.logout()
|
||||
|
||||
def test_domain_admins(self):
|
||||
self.values["role"] = "DomainAdmins"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertEqual(self.user.group == "DomainAdmins", True)
|
||||
self.assertEqual(self.user.group, "DomainAdmins")
|
||||
|
||||
self.values["role"] = "SimpleUsers"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertEqual(self.user.group == 'DomainAdmins', False)
|
||||
self.assertNotEqual(self.user.group, 'DomainAdmins')
|
||||
|
||||
def test_superusers(self):
|
||||
self.values["role"] = "SuperAdmins"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[self.user.id]),
|
||||
self.values
|
||||
@@ -118,7 +117,7 @@ class PermissionsTestCase(ModoTestCase):
|
||||
self.assertEqual(User.objects.get(username="user@test.com").is_superuser, True)
|
||||
|
||||
self.values["role"] = "SimpleUsers"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[self.user.id]),
|
||||
self.values
|
||||
@@ -135,7 +134,7 @@ class PermissionsTestCase(ModoTestCase):
|
||||
password1="", password2="",
|
||||
quota=10, is_active=True, email="admin@test.com"
|
||||
)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[admin.id]),
|
||||
values
|
||||
@@ -144,7 +143,7 @@ class PermissionsTestCase(ModoTestCase):
|
||||
self.assertEqual(admin.can_access(Domain.objects.get(name="test.com")), True)
|
||||
|
||||
values["role"] = "SuperAdmins"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[admin.id]),
|
||||
values
|
||||
@@ -178,25 +177,23 @@ class PermissionsTestCase(ModoTestCase):
|
||||
username="superadmin2@test.com", first_name="Super",
|
||||
last_name="Admin", password1="toto", password2="toto",
|
||||
role="SuperAdmins", is_active=True,
|
||||
email="superadmin2@test.com", stepid=2
|
||||
email="superadmin2@test.com", stepid='step2'
|
||||
)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.newaccount"),
|
||||
values
|
||||
)
|
||||
account = User.objects.get(username="superadmin2@test.com")
|
||||
self.clt.logout()
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
self.check_ajax_get(
|
||||
self.ajax_get(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount",
|
||||
args=[account.id]),
|
||||
{},
|
||||
status="ko", respmsg="Permission denied"
|
||||
{}, 403
|
||||
)
|
||||
self.check_ajax_get(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount",
|
||||
args=[account.id]), {},
|
||||
status="ko", respmsg="Permission denied"
|
||||
args=[account.id]), {}, 403
|
||||
)
|
||||
|
||||
def test_domainadmin_dlist_local_domain_not_owned(self):
|
||||
@@ -208,6 +205,6 @@ class PermissionsTestCase(ModoTestCase):
|
||||
recipients_1="user@test2.com",
|
||||
enabled=True
|
||||
)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.newdlist"), values
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ class AliasTestCase(ModoTestCase):
|
||||
is_active=user.is_active, email="user@test.com",
|
||||
aliases="toto@test.com", aliases_1="titi@test.com"
|
||||
)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
@@ -29,7 +29,7 @@ class AliasTestCase(ModoTestCase):
|
||||
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
|
||||
|
||||
del values["aliases_1"]
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
@@ -42,7 +42,7 @@ class AliasTestCase(ModoTestCase):
|
||||
recipients_1="admin@test.com",
|
||||
recipients_2="ext@titi.com",
|
||||
enabled=True)
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.newdlist"), values
|
||||
)
|
||||
user = User.objects.get(username="user@test.com")
|
||||
@@ -53,14 +53,14 @@ class AliasTestCase(ModoTestCase):
|
||||
dlist = Alias.objects.get(address="all", domain__name="test.com")
|
||||
self.assertEqual(len(dlist.get_recipients()), 3)
|
||||
del values["recipients_1"]
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.editalias",
|
||||
args=[dlist.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(dlist.get_recipients_count(), 2)
|
||||
|
||||
self.check_ajax_get(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.delalias") + "?selection=%d" \
|
||||
% dlist.id, {}
|
||||
)
|
||||
@@ -69,21 +69,21 @@ class AliasTestCase(ModoTestCase):
|
||||
|
||||
def test_forward(self):
|
||||
values = dict(email="forward2@test.com", recipients="rcpt@dest.com")
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.newforward"), values
|
||||
)
|
||||
fwd = Alias.objects.get(address="forward2", domain__name="test.com")
|
||||
self.assertEqual(fwd.get_recipients_count(), 1)
|
||||
|
||||
values["recipients"] = "rcpt2@dest.com"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.editalias",
|
||||
args=[fwd.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(fwd.get_recipients_count(), 1)
|
||||
|
||||
self.check_ajax_get(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.delalias") + "?selection=%d" \
|
||||
% fwd.id, {}
|
||||
)
|
||||
@@ -92,7 +92,7 @@ class AliasTestCase(ModoTestCase):
|
||||
|
||||
def test_forward_and_local_copies(self):
|
||||
values = dict(email="user@test.com", recipients="rcpt@dest.com")
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.newforward"), values
|
||||
)
|
||||
fwd = Alias.objects.get(address="user", domain__name="test.com")
|
||||
@@ -100,7 +100,7 @@ class AliasTestCase(ModoTestCase):
|
||||
|
||||
values["recipients"] = "rcpt@dest.com"
|
||||
values["recipients_1"] = "user@test.com"
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.editalias", args=[fwd.id]),
|
||||
values
|
||||
)
|
||||
|
||||
@@ -19,9 +19,9 @@ class DomainTestCase(ModoTestCase):
|
||||
"""
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 100, "create_dom_admin": "no",
|
||||
"stepid": 2
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.check_ajax_post(reverse("modoboa.extensions.admin.views.domain.newdomain"), values)
|
||||
self.ajax_post(reverse("modoboa.extensions.admin.views.domain.newdomain"), values)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
self.assertEqual(dom.name, "pouet.com")
|
||||
self.assertEqual(dom.quota, 100)
|
||||
@@ -35,9 +35,9 @@ class DomainTestCase(ModoTestCase):
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 100, "create_dom_admin": "yes",
|
||||
"dom_admin_username": "toto", "create_aliases": "yes",
|
||||
"stepid": 2
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.newdomain"),
|
||||
values
|
||||
)
|
||||
@@ -53,9 +53,9 @@ class DomainTestCase(ModoTestCase):
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 0, "create_dom_admin": "yes",
|
||||
"dom_admin_username": "toto", "create_aliases": "yes",
|
||||
"stepid": 2
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.newdomain"),
|
||||
values
|
||||
)
|
||||
@@ -75,7 +75,7 @@ class DomainTestCase(ModoTestCase):
|
||||
"name": "pouet.com", "quota": 100, "enabled": True
|
||||
}
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.editdomain",
|
||||
args=[dom.id]),
|
||||
values
|
||||
@@ -88,7 +88,7 @@ class DomainTestCase(ModoTestCase):
|
||||
"""Test the removal of a domain
|
||||
"""
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
self.check_ajax_get(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.deldomain",
|
||||
args=[dom.id]),
|
||||
{}
|
||||
|
||||
@@ -31,7 +31,7 @@ class DomainAliasTestCase(ModoTestCase):
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
values = dict(name=dom.name, quota=dom.quota, enabled=dom.enabled,
|
||||
aliases="domalias.net", aliases_1="domalias.com")
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.editdomain",
|
||||
args=[dom.id]),
|
||||
values
|
||||
@@ -39,7 +39,7 @@ class DomainAliasTestCase(ModoTestCase):
|
||||
self.assertEqual(dom.domainalias_set.count(), 2)
|
||||
|
||||
del values["aliases_1"]
|
||||
self.check_ajax_post(
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.editdomain",
|
||||
args=[dom.id]),
|
||||
values
|
||||
|
||||
@@ -6,59 +6,50 @@ from django.contrib.auth.decorators import (
|
||||
login_required, permission_required
|
||||
)
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.webutils import ajax_response, ajax_simple_response
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.lib.webutils import render_to_json_response
|
||||
from modoboa.lib.exceptions import PermDeniedException, Conflict
|
||||
from modoboa.extensions.admin.forms import AliasForm
|
||||
from modoboa.extensions.admin.models import Alias
|
||||
|
||||
|
||||
def _validate_alias(request, form, successmsg, tplname, commonctx, callback=None):
|
||||
def _validate_alias(request, form, successmsg, callback=None):
|
||||
"""Alias validation
|
||||
|
||||
Common function shared between creation and modification actions.
|
||||
"""
|
||||
error = None
|
||||
if form.is_valid():
|
||||
form.set_recipients()
|
||||
try:
|
||||
alias = form.save()
|
||||
except IntegrityError:
|
||||
raise AdminError(_("Alias with this name already exists"))
|
||||
raise Conflict(_("Alias with this name already exists"))
|
||||
if callback:
|
||||
callback(request.user, alias)
|
||||
return ajax_simple_response({"status": "ok", "respmsg": successmsg})
|
||||
return render_to_json_response(successmsg)
|
||||
|
||||
if "targets" in request.POST:
|
||||
targets = request.POST.getlist("targets")
|
||||
commonctx["targets"] = targets[:-1]
|
||||
|
||||
commonctx["form"] = form
|
||||
commonctx["error"] = error
|
||||
return ajax_response(request, status="ko", template=tplname, **commonctx)
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
|
||||
def _new_alias(request, title, action, successmsg,
|
||||
tplname="common/generic_modal_form.html"):
|
||||
tplname="admin/aliasform.html"):
|
||||
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
|
||||
ctx = {
|
||||
"title": title,
|
||||
"action": action,
|
||||
"formid": "aliasform",
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit"
|
||||
}
|
||||
if request.method == "POST":
|
||||
def callback(user, alias):
|
||||
alias.post_create(user)
|
||||
|
||||
form = AliasForm(request.user, request.POST)
|
||||
return _validate_alias(
|
||||
request, form, successmsg, tplname, ctx, callback
|
||||
request, form, successmsg, callback
|
||||
)
|
||||
|
||||
form = AliasForm(request.user)
|
||||
ctx["form"] = form
|
||||
ctx = {
|
||||
"title": title,
|
||||
"action": action,
|
||||
"formid": "aliasform",
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"form": AliasForm(request.user)
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@@ -94,29 +85,29 @@ def newforward(request):
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.change_alias")
|
||||
def editalias(request, alid, tplname="common/generic_modal_form.html"):
|
||||
@transaction.commit_on_success
|
||||
def editalias(request, alid, tplname="admin/aliasform.html"):
|
||||
alias = Alias.objects.get(pk=alid)
|
||||
if not request.user.can_access(alias):
|
||||
raise PermDeniedException
|
||||
ctx = dict(
|
||||
action=reverse(editalias, args=[alias.id]),
|
||||
formid="aliasform",
|
||||
title=alias.full_address,
|
||||
action_label=_("Update"),
|
||||
action_classes="submit"
|
||||
)
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
successmsg = _("Distribution list modified")
|
||||
elif alias.extmboxes != "":
|
||||
successmsg = _("Forward modified")
|
||||
else:
|
||||
successmsg = _("Alias modified")
|
||||
if request.method == "POST":
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
successmsg = _("Distribution list modified")
|
||||
elif alias.extmboxes != "":
|
||||
successmsg = _("Forward modified")
|
||||
else:
|
||||
successmsg = _("Alias modified")
|
||||
form = AliasForm(request.user, request.POST, instance=alias)
|
||||
return _validate_alias(request, form, successmsg, tplname, ctx)
|
||||
return _validate_alias(request, form, successmsg)
|
||||
|
||||
form = AliasForm(request.user, instance=alias)
|
||||
ctx["form"] = form
|
||||
ctx = {
|
||||
'action': reverse(editalias, args=[alias.id]),
|
||||
'formid': 'aliasform',
|
||||
'title': alias.full_address,
|
||||
'action_label': _('Update'),
|
||||
'action_classes': 'submit',
|
||||
'form': AliasForm(request.user, instance=alias)
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@@ -141,4 +132,4 @@ def delalias(request):
|
||||
alias.delete()
|
||||
|
||||
msg = ungettext(msg, msgs, len(selection))
|
||||
return ajax_simple_response({"status": "ok", "respmsg": msg})
|
||||
return render_to_json_response(msg)
|
||||
|
||||
@@ -2,7 +2,6 @@ from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.contrib.auth.decorators import (
|
||||
@@ -10,18 +9,19 @@ from django.contrib.auth.decorators import (
|
||||
)
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_simple_response, _render_to_string
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.formutils import CreationWizard
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.exceptions import (
|
||||
ModoboaException, PermDeniedException, BadRequest
|
||||
)
|
||||
from modoboa.lib.templatetags.lib_tags import pagination_bar
|
||||
from modoboa.extensions.admin.tables import DomainsTable
|
||||
from modoboa.extensions.admin.lib import get_sort_order, get_listing_page
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.models import Domain, Mailbox
|
||||
from modoboa.extensions.admin.forms import (
|
||||
DomainForm, DomainFormGeneral, DomainFormOptions
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_domains
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -35,17 +35,29 @@ def index(request):
|
||||
)
|
||||
def _domains(request):
|
||||
sort_order, sort_dir = get_sort_order(request.GET, "name")
|
||||
domains = Domain.objects.get_for_admin(request.user)
|
||||
squery = request.GET.get("searchquery", None)
|
||||
if squery is not None:
|
||||
q = Q(name__contains=squery)
|
||||
q |= Q(domainalias__name__contains=squery)
|
||||
domains = domains.filter(q).distinct()
|
||||
if sort_order in ["name", "domainalias__name"]:
|
||||
domains = domains.order_by("%s%s" % (sort_dir, sort_order))
|
||||
page = get_listing_page(domains, request.GET.get("page", 1))
|
||||
return ajax_simple_response({
|
||||
"table": DomainsTable(request, page.object_list).render(),
|
||||
filters = dict(
|
||||
(flt, request.GET.get(flt, None))
|
||||
for flt in ['domfilter', 'searchquery']
|
||||
+ events.raiseQueryEvent('ExtraDomainFilters')
|
||||
)
|
||||
request.session['domains_filters'] = filters
|
||||
domainlist = get_domains(
|
||||
request.user, **filters
|
||||
)
|
||||
if sort_order == 'name':
|
||||
domainlist = sorted(
|
||||
domainlist,
|
||||
key=lambda d: getattr(d, sort_order), reverse=sort_dir == '-'
|
||||
)
|
||||
else:
|
||||
domainlist = sorted(domainlist, key=lambda d: d.tags[0],
|
||||
reverse=sort_dir == '-')
|
||||
page = get_listing_page(domainlist, request.GET.get("page", 1))
|
||||
return render_to_json_response({
|
||||
"table": _render_to_string(request, 'admin/domains_table.html', {
|
||||
'domains': page.object_list,
|
||||
'tableid': 'domains'
|
||||
}),
|
||||
"page": page.number,
|
||||
"paginbar": pagination_bar(page),
|
||||
"handle_mailboxes": parameters.get_admin("HANDLE_MAILBOXES",
|
||||
@@ -70,7 +82,7 @@ def domains(request, tplname="admin/domains.html"):
|
||||
@permission_required("core.add_user")
|
||||
def domains_list(request):
|
||||
doms = [dom.name for dom in Domain.objects.get_for_admin(request.user)]
|
||||
return ajax_simple_response(doms)
|
||||
return render_to_json_response(doms)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -79,50 +91,49 @@ def domains_list(request):
|
||||
def newdomain(request, tplname="common/wizard_forms.html"):
|
||||
events.raiseEvent("CanCreate", request.user, "domains")
|
||||
|
||||
def newdomain_cb(steps):
|
||||
genform = steps[0]["form"]
|
||||
genform.is_valid()
|
||||
domain = genform.save(request.user)
|
||||
domain.post_create(request.user)
|
||||
steps[1]["form"].save(request.user, domain)
|
||||
|
||||
commonctx = {"title": _("New domain"),
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(newdomain),
|
||||
"formid": "domform"}
|
||||
cwizard = CreationWizard(newdomain_cb)
|
||||
cwizard = CreationWizard()
|
||||
cwizard.add_step(DomainFormGeneral, _("General"),
|
||||
[dict(classes="btn-inverse next", label=_("Next"))],
|
||||
formtpl="admin/domain_general_form.html")
|
||||
cwizard.add_step(DomainFormOptions, _("Options"),
|
||||
[dict(classes="btn-primary submit", label=_("Create")),
|
||||
dict(classes="btn-inverse prev", label=_("Previous"))],
|
||||
formtpl="admin/domain_options_form.html")
|
||||
cwizard.add_step(
|
||||
DomainFormOptions, _("Options"),
|
||||
[dict(classes="btn-primary submit", label=_("Create")),
|
||||
dict(classes="btn-inverse prev", label=_("Previous"))],
|
||||
formtpl="admin/domain_options_form.html",
|
||||
new_args=[request.user]
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
retcode, data = cwizard.validate_step(request)
|
||||
if retcode == -1:
|
||||
raise AdminError(data)
|
||||
raise BadRequest(data)
|
||||
if retcode == 1:
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", title=cwizard.get_title(data + 1), stepid=data
|
||||
))
|
||||
if retcode == 2:
|
||||
return ajax_simple_response(
|
||||
dict(status="ok", respmsg=_("Domain created"))
|
||||
return render_to_json_response(
|
||||
{'title': cwizard.get_title(data + 1), 'stepid': data}
|
||||
)
|
||||
if retcode == 2:
|
||||
genform = cwizard.steps[0]["form"]
|
||||
domain = genform.save(request.user)
|
||||
domain.post_create(request.user)
|
||||
try:
|
||||
cwizard.steps[1]["form"].save(request.user, domain)
|
||||
except ModoboaException as e:
|
||||
transaction.rollback()
|
||||
raise
|
||||
return render_to_json_response(_("Domain created"))
|
||||
return render_to_json_response({
|
||||
'stepid': data, 'form_errors': cwizard.errors
|
||||
}, status=400)
|
||||
|
||||
from modoboa.lib.templatetags.lib_tags import render_form
|
||||
return ajax_simple_response(dict(
|
||||
status="ko", stepid=data,
|
||||
form=render_form(cwizard.steps[data]["form"])
|
||||
))
|
||||
|
||||
ctx = {"title": _("New domain"),
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(newdomain),
|
||||
"formid": "domform"}
|
||||
cwizard.create_forms()
|
||||
commonctx.update(steps=cwizard.steps)
|
||||
commonctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
|
||||
return render(request, tplname, commonctx)
|
||||
ctx.update(steps=cwizard.steps)
|
||||
ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -133,44 +144,32 @@ def editdomain(request, dom_id, tplname="admin/editdomainform.html"):
|
||||
if not request.user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
|
||||
instances = dict(general=domain)
|
||||
events.raiseEvent("FillDomainInstances", request.user, domain, instances)
|
||||
if request.method == "POST":
|
||||
domain.oldname = domain.name
|
||||
form = DomainForm(request.user, request.POST, instances=instances)
|
||||
if form.is_valid():
|
||||
form.save(request.user)
|
||||
events.raiseEvent("DomainModified", domain)
|
||||
return render_to_json_response(_("Domain modified"))
|
||||
return render_to_json_response({
|
||||
'form_errors': form.errors
|
||||
}, status=400)
|
||||
|
||||
domadmins = [u for u in domain.admins
|
||||
if request.user.can_access(u) and not u.is_superuser]
|
||||
if not request.user.is_superuser:
|
||||
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
|
||||
|
||||
instances = dict(general=domain)
|
||||
events.raiseEvent("FillDomainInstances", request.user, domain, instances)
|
||||
|
||||
commonctx = {"title": domain.name,
|
||||
"action_label": _("Update"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(editdomain, args=[dom_id]),
|
||||
"formid": "domform",
|
||||
"domain": domain}
|
||||
if request.method == "POST":
|
||||
error = None
|
||||
domain.oldname = domain.name
|
||||
form = DomainForm(request.user, request.POST, instances=instances)
|
||||
if form.is_valid():
|
||||
try:
|
||||
form.save(request.user)
|
||||
except AdminError, e:
|
||||
error = str(e)
|
||||
else:
|
||||
events.raiseEvent("DomainModified", domain)
|
||||
return ajax_simple_response({
|
||||
"status": "ok", "respmsg": _("Domain modified")
|
||||
})
|
||||
|
||||
commonctx["tabs"] = form
|
||||
return ajax_simple_response({
|
||||
"status": "ko", "respmsg": error,
|
||||
"content": _render_to_string(request, tplname, commonctx)
|
||||
})
|
||||
|
||||
commonctx["tabs"] = DomainForm(request.user, instances=instances)
|
||||
commonctx["domadmins"] = domadmins
|
||||
return render(request, tplname, commonctx)
|
||||
ctx = {"title": domain.name,
|
||||
"action_label": _("Update"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(editdomain, args=[dom_id]),
|
||||
"formid": "domform",
|
||||
"domain": domain,
|
||||
"tabs": DomainForm(request.user, instances=instances),
|
||||
"domadmins": domadmins}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -187,8 +186,8 @@ def deldomain(request, dom_id):
|
||||
if not request.user.can_access(dom):
|
||||
raise PermDeniedException
|
||||
if mb and mb.domain == dom:
|
||||
raise AdminError(_("You can't delete your own domain"))
|
||||
raise PermDeniedException(_("You can't delete your own domain"))
|
||||
dom.delete(request.user, keepdir)
|
||||
|
||||
msg = ungettext("Domain deleted", "Domains deleted", 1)
|
||||
return ajax_simple_response({"status": "ok", "respmsg": msg})
|
||||
return render_to_json_response(msg)
|
||||
|
||||
@@ -8,8 +8,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_identities
|
||||
from modoboa.extensions.admin.models import Domain
|
||||
from modoboa.extensions.admin.lib import get_domains, get_identities
|
||||
from modoboa.extensions.admin.forms import (
|
||||
ExportIdentitiesForm, ExportDomainsForm
|
||||
)
|
||||
@@ -73,10 +72,9 @@ def export_domains(request):
|
||||
form.is_valid()
|
||||
fp = cStringIO.StringIO()
|
||||
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
|
||||
for dom in Domain.objects.get_for_admin(request.user):
|
||||
for dom in get_domains(request.user,
|
||||
**request.session['domains_filters']):
|
||||
dom.to_csv(csvwriter)
|
||||
for da in dom.domainalias_set.all():
|
||||
da.to_csv(csvwriter)
|
||||
content = fp.getvalue()
|
||||
fp.close()
|
||||
return _export(content, form.cleaned_data["filename"])
|
||||
|
||||
@@ -7,9 +7,11 @@ from django.contrib.auth.decorators import (
|
||||
)
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.exceptions import (
|
||||
ModoboaException, PermDeniedException, BadRequest
|
||||
)
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_simple_response, _render_to_string, ajax_response
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.formutils import CreationWizard
|
||||
from modoboa.lib.templatetags.lib_tags import pagination_bar
|
||||
@@ -18,7 +20,6 @@ from modoboa.extensions.admin.models import Mailbox, Domain
|
||||
from modoboa.extensions.admin.lib import (
|
||||
get_sort_order, get_listing_page, get_identities
|
||||
)
|
||||
from modoboa.extensions.admin.exceptions import AdminError
|
||||
from modoboa.extensions.admin.forms import (
|
||||
AccountForm, AccountFormGeneral, AccountFormMail
|
||||
)
|
||||
@@ -42,7 +43,7 @@ def _identities(request):
|
||||
objects = sorted(idents_list, key=lambda o: o.tags[0],
|
||||
reverse=sort_dir == '-')
|
||||
page = get_listing_page(objects, request.GET.get("page", 1))
|
||||
return ajax_simple_response({
|
||||
return render_to_json_response({
|
||||
"table": _render_to_string(request, "admin/identities_table.html", {
|
||||
"identities": page.object_list,
|
||||
"tableid": "objects_table"
|
||||
@@ -72,7 +73,7 @@ def accounts_list(request):
|
||||
accs = User.objects.filter(is_superuser=False) \
|
||||
.exclude(groups__name='SimpleUsers')
|
||||
res = [a.username for a in accs.all()]
|
||||
return ajax_simple_response(res)
|
||||
return render_to_json_response(res)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -99,10 +100,9 @@ def list_quotas(request, tplname="admin/quotas.html"):
|
||||
order_by=["%s%s" % (sort_dir, sort_order)]
|
||||
)
|
||||
else:
|
||||
raise AdminError(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
page = get_listing_page(mboxes, request.GET.get("page", 1))
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"page": page.number,
|
||||
"paginbar": pagination_bar(page),
|
||||
"table": _render_to_string(request, tplname, {
|
||||
@@ -115,35 +115,15 @@ def list_quotas(request, tplname="admin/quotas.html"):
|
||||
@permission_required("core.add_user")
|
||||
@transaction.commit_on_success
|
||||
def newaccount(request, tplname='common/wizard_forms.html'):
|
||||
def create_account(steps):
|
||||
"""Account creation callback
|
||||
"""Create a new account.
|
||||
|
||||
Called when all creation steps have been validated.
|
||||
|
||||
:param steps: the steps data
|
||||
"""
|
||||
genform = steps[0]["form"]
|
||||
genform.is_valid()
|
||||
account = genform.save()
|
||||
account.post_create(request.user)
|
||||
|
||||
mailform = steps[1]["form"]
|
||||
try:
|
||||
mailform.save(request.user, account)
|
||||
except AdminError:
|
||||
# A bit uggly: transaction management doesn't work very
|
||||
# well with nested functions. Need to wait for django 1.6
|
||||
# and atomicity.
|
||||
account.delete(request.user, False)
|
||||
raise
|
||||
|
||||
ctx = dict(
|
||||
title=_("New account"),
|
||||
action=reverse(newaccount),
|
||||
formid="newaccount_form",
|
||||
submit_label=_("Create")
|
||||
)
|
||||
cwizard = CreationWizard(create_account)
|
||||
.. note:: An issue still remains int this code: if all validation
|
||||
steps are successful but an error occurs after we call 'save',
|
||||
the account will be created. It happens transaction management
|
||||
doesn't work very well with nested functions. Need to wait for
|
||||
django 1.6 and atomicity.
|
||||
"""
|
||||
cwizard = CreationWizard()
|
||||
cwizard.add_step(AccountFormGeneral, _("General"),
|
||||
[dict(classes="btn-inverse next", label=_("Next"))],
|
||||
new_args=[request.user])
|
||||
@@ -155,22 +135,28 @@ def newaccount(request, tplname='common/wizard_forms.html'):
|
||||
if request.method == "POST":
|
||||
retcode, data = cwizard.validate_step(request)
|
||||
if retcode == -1:
|
||||
raise AdminError(data)
|
||||
raise BadRequest(data)
|
||||
if retcode == 1:
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", title=cwizard.get_title(data + 1), stepid=data
|
||||
))
|
||||
return render_to_json_response(
|
||||
{'title': cwizard.get_title(data + 1), 'stepid': data}
|
||||
)
|
||||
if retcode == 2:
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Account created")
|
||||
))
|
||||
|
||||
from modoboa.lib.templatetags.lib_tags import render_form
|
||||
return ajax_simple_response(dict(
|
||||
status="ko", stepid=data,
|
||||
form=render_form(cwizard.steps[data]["form"])
|
||||
))
|
||||
genform = cwizard.steps[0]["form"]
|
||||
account = genform.save()
|
||||
account.post_create(request.user)
|
||||
mailform = cwizard.steps[1]["form"]
|
||||
mailform.save(request.user, account)
|
||||
return render_to_json_response(_("Account created"))
|
||||
return render_to_json_response({
|
||||
'stepid': data, 'form_errors': cwizard.errors
|
||||
}, status=400)
|
||||
|
||||
ctx = {
|
||||
'title': _("New account"),
|
||||
'action': reverse(newaccount),
|
||||
'formid': 'newaccount_form',
|
||||
'submit_label': _("Create")
|
||||
}
|
||||
cwizard.create_forms()
|
||||
ctx.update(steps=cwizard.steps)
|
||||
ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
|
||||
@@ -191,14 +177,6 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
|
||||
instances = dict(general=account, mail=mb, perms=account)
|
||||
events.raiseEvent("FillAccountInstances", request.user, account, instances)
|
||||
|
||||
ctx = dict(
|
||||
title=account.username,
|
||||
formid="accountform",
|
||||
action=reverse(editaccount, args=[accountid]),
|
||||
action_label=_("Update"),
|
||||
action_classes="submit"
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
classes = {}
|
||||
form = AccountForm(request.user, request.POST,
|
||||
@@ -209,15 +187,17 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
|
||||
if form.is_valid(optional_only=True):
|
||||
events.raiseEvent("AccountModified", account, form.account)
|
||||
form.save()
|
||||
return ajax_simple_response(
|
||||
dict(status="ok", respmsg=_("Account updated"))
|
||||
)
|
||||
transaction.rollback()
|
||||
return render_to_json_response(_("Account updated"))
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
ctx["tabs"] = form
|
||||
return ajax_response(request, status="ko", template=tplname, **ctx)
|
||||
|
||||
ctx["tabs"] = AccountForm(request.user, instances=instances)
|
||||
ctx = {
|
||||
'title': account.username,
|
||||
'formid': 'accountform',
|
||||
'action': reverse(editaccount, args=[accountid]),
|
||||
'action_label': _('Update'),
|
||||
'action_classes': 'submit',
|
||||
'tabs': AccountForm(request.user, instances=instances)
|
||||
}
|
||||
active_tab_id = request.GET.get("active_tab", "default")
|
||||
if active_tab_id != "default":
|
||||
ctx["tabs"].active_id = active_tab_id
|
||||
@@ -229,11 +209,10 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
|
||||
@transaction.commit_on_success
|
||||
def delaccount(request, accountid):
|
||||
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
|
||||
|
||||
User.objects.get(pk=accountid).delete(request.user, keep_mb_dir=keepdir)
|
||||
|
||||
msg = ungettext("Account deleted", "Accounts deleted", 1)
|
||||
return ajax_simple_response({"status": "ok", "respmsg": msg})
|
||||
return render_to_json_response(
|
||||
ungettext("Account deleted", "Accounts deleted", 1)
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -242,13 +221,13 @@ def remove_permission(request):
|
||||
domid = request.GET.get("domid", None)
|
||||
daid = request.GET.get("daid", None)
|
||||
if domid is None or daid is None:
|
||||
raise AdminError(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
try:
|
||||
account = User.objects.get(pk=daid)
|
||||
domain = Domain.objects.get(pk=domid)
|
||||
except (User.DoesNotExist, Domain.DoesNotExist):
|
||||
raise AdminError(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
if not request.user.can_access(account) or not request.user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
domain.remove_admin(account)
|
||||
return ajax_simple_response({"status": "ok"})
|
||||
return render_to_json_response({})
|
||||
|
||||
@@ -6,7 +6,8 @@ from django.db import transaction, IntegrityError
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import ModoboaException, Conflict
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Alias
|
||||
@@ -39,10 +40,11 @@ def import_domains(request):
|
||||
<ul>
|
||||
<li><em>domain; name; quota; enabled</em></li>
|
||||
<li><em>domainalias; name; targeted domain; enabled</em></li>
|
||||
%s
|
||||
</ul>
|
||||
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
|
||||
<p>You can use a different character as separator.</p>
|
||||
""")
|
||||
""" % ''.join([unicode(hlp) for hlp in events.raiseQueryEvent('ExtraDomainImportHelp')]))
|
||||
|
||||
ctx = dict(
|
||||
title=_("Import domains"),
|
||||
@@ -100,8 +102,8 @@ def importdata(request, formclass=ImportDataForm):
|
||||
try:
|
||||
reader = csv.reader(request.FILES['sourcefile'],
|
||||
delimiter=form.cleaned_data['sepchar'])
|
||||
except csv.Error, e:
|
||||
error = str(e)
|
||||
except csv.Error as inst:
|
||||
error = str(inst)
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
@@ -112,13 +114,21 @@ def importdata(request, formclass=ImportDataForm):
|
||||
try:
|
||||
fct = globals()["import_%s" % row[0].strip()]
|
||||
except KeyError:
|
||||
continue
|
||||
fct = events.raiseQueryEvent(
|
||||
'ImportObject', row[0].strip()
|
||||
)
|
||||
if not fct:
|
||||
continue
|
||||
fct = fct[0]
|
||||
try:
|
||||
fct(request.user, row, form.cleaned_data)
|
||||
except IntegrityError, e:
|
||||
if form.cleaned_data["continue_if_exists"]:
|
||||
continue
|
||||
raise ModoboaException(_("Object already exists: %s" % row))
|
||||
raise Conflict(
|
||||
_("Object already exists: %s"
|
||||
% form.cleaned_data['sepchar'].join(row[:2]))
|
||||
)
|
||||
cpt += 1
|
||||
msg = _("%d objects imported successfully" % cpt)
|
||||
return render(request, "admin/import_done.html", {
|
||||
|
||||
@@ -2,10 +2,9 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_response, ajax_simple_response, _render_to_string
|
||||
render_to_json_response, _render_to_string
|
||||
)
|
||||
from modoboa.extensions.admin.lib import needs_mailbox
|
||||
from modoboa.extensions.admin.exceptions import BadDestination
|
||||
from modoboa.extensions.admin.models import Alias, Mailbox
|
||||
from modoboa.extensions.admin.forms import ForwardForm
|
||||
|
||||
@@ -13,12 +12,7 @@ from modoboa.extensions.admin.forms import ForwardForm
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def forward(request, tplname='admin/forward.html'):
|
||||
try:
|
||||
mb = request.user.mailbox_set.all()[0]
|
||||
except IndexError:
|
||||
raise ModoboaException(
|
||||
_("You need a mailbox in order to define a forward")
|
||||
)
|
||||
mb = request.user.mailbox_set.all()[0]
|
||||
try:
|
||||
al = Alias.objects.get(address=mb.address,
|
||||
domain__name=mb.domain.name)
|
||||
@@ -28,28 +22,23 @@ def forward(request, tplname='admin/forward.html'):
|
||||
form = ForwardForm(request.POST)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
try:
|
||||
if al is None:
|
||||
al = Alias()
|
||||
al.address = mb.address
|
||||
al.domain = mb.domain
|
||||
al.enabled = mb.user.is_active
|
||||
intdests = []
|
||||
if form.cleaned_data["keepcopies"]:
|
||||
intdests += [mb]
|
||||
form.parse_dest()
|
||||
al.save(int_rcpts=intdests, ext_rcpts=form.dests)
|
||||
if request.user.group != "SimpleUsers":
|
||||
al.post_create(request.user)
|
||||
return ajax_response(request, respmsg=_("Forward updated"))
|
||||
except BadDestination, e:
|
||||
error = str(e)
|
||||
if al is None:
|
||||
al = Alias()
|
||||
al.address = mb.address
|
||||
al.domain = mb.domain
|
||||
al.enabled = mb.user.is_active
|
||||
intdests = []
|
||||
if form.cleaned_data["keepcopies"]:
|
||||
intdests += [mb]
|
||||
form.parse_dest()
|
||||
al.save(int_rcpts=intdests, ext_rcpts=form.dests)
|
||||
if request.user.group != "SimpleUsers":
|
||||
al.post_create(request.user)
|
||||
return render_to_json_response(_("Forward updated"))
|
||||
|
||||
return ajax_simple_response(dict(
|
||||
status="ko",
|
||||
errors=form.errors,
|
||||
respmsg=error
|
||||
))
|
||||
return render_to_json_response(
|
||||
{'form_errors': form.errors}, status=400
|
||||
)
|
||||
|
||||
form = ForwardForm()
|
||||
if al is not None:
|
||||
@@ -60,8 +49,7 @@ def forward(request, tplname='admin/forward.html'):
|
||||
pass
|
||||
else:
|
||||
form.fields["keepcopies"].initial = True
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, tplname, {
|
||||
"form": form
|
||||
})
|
||||
|
||||
@@ -8,9 +8,8 @@ Provides:
|
||||
* Per-domain settings
|
||||
|
||||
"""
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import Template, Context
|
||||
import sys
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
|
||||
@@ -21,6 +20,7 @@ class Amavis(ModoExtension):
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Simple amavis management frontend")
|
||||
url = "quarantine"
|
||||
available_for_topredirection = True
|
||||
|
||||
def init(self):
|
||||
"""Init function
|
||||
@@ -29,141 +29,33 @@ class Amavis(ModoExtension):
|
||||
for existing domains to let Amavis consider them local.
|
||||
"""
|
||||
from modoboa.extensions.admin.models import Domain
|
||||
from .models import Users, Policy
|
||||
from modoboa.extensions.amavis.models import Users
|
||||
from modoboa.extensions.amavis.lib import (
|
||||
create_user_and_policy, create_user_and_use_policy
|
||||
)
|
||||
|
||||
for dom in Domain.objects.all():
|
||||
try:
|
||||
Users.objects.get(email="@%s" % dom.name)
|
||||
except Users.DoesNotExist:
|
||||
p = Policy.objects.create(policy_name=dom.name)
|
||||
Users.objects.create(email="@%s" % dom.name, fullname=dom.name,
|
||||
priority=7, policy=p)
|
||||
create_user_and_policy(dom.name)
|
||||
for domalias in dom.domainalias_set.all():
|
||||
try:
|
||||
Users.objects.get(email='@%s' % domalias.name)
|
||||
except Users.DoesNotExist:
|
||||
create_user_and_use_policy(domalias.name, dom.name)
|
||||
|
||||
def load(self):
|
||||
from .app_settings import ParametersForm, UserSettings
|
||||
|
||||
parameters.register(ParametersForm, "Amavis")
|
||||
parameters.register(UserSettings, ugettext_lazy("Quarantine"))
|
||||
from modoboa.extensions.amavis import general_callbacks
|
||||
if 'modoboa.extensions.amavis.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(Amavis)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target == "top_menu":
|
||||
return [
|
||||
{"name": "quarantine",
|
||||
"label": _("Quarantine"),
|
||||
"url": reverse('modoboa.extensions.amavis.views.index')}
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe("CreateDomain")
|
||||
def on_create_domain(user, domain):
|
||||
from .models import Users, Policy
|
||||
p = Policy.objects.create(policy_name=domain.name)
|
||||
Users.objects.create(email="@%s" % domain.name, fullname=domain.name,
|
||||
priority=7, policy=p)
|
||||
|
||||
|
||||
@events.observe("DomainModified")
|
||||
def on_domain_modified(domain):
|
||||
if domain.oldname != domain.name:
|
||||
from .models import Users
|
||||
u = Users.objects.get(email="@%s" % domain.oldname)
|
||||
u.email = "@%s" % domain.name
|
||||
u.fullname = domain.name
|
||||
u.policy.policy_name = domain.name
|
||||
u.policy.save()
|
||||
u.save()
|
||||
|
||||
|
||||
@events.observe("DeleteDomain")
|
||||
def on_delete_domain(domain):
|
||||
from .models import Users
|
||||
|
||||
try:
|
||||
u = Users.objects.get(email="@%s" % domain.name)
|
||||
except Users.DoesNotExist:
|
||||
return
|
||||
u.policy.delete()
|
||||
u.delete()
|
||||
|
||||
|
||||
@events.observe("GetStaticContent")
|
||||
def extra_static_content(user):
|
||||
if user.group == "SimpleUsers":
|
||||
return []
|
||||
|
||||
tpl = Template("""<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
{% if user_can_release == "no" %}var poller = new Poller("{{ url }}", {
|
||||
interval: {{ interval }},
|
||||
success_cb: function(data) {
|
||||
var $link = $("#nbrequests");
|
||||
if (data.requests > 0) {
|
||||
$link.html(data.requests + " " + "{{ text }}");
|
||||
$link.parent().removeClass('hidden');
|
||||
} else {
|
||||
$link.parent().addClass('hidden');
|
||||
}
|
||||
}
|
||||
});{% endif %}
|
||||
|
||||
$(document).bind('domform_init', function() {
|
||||
activate_widget.call($('#id_spam_subject_tag2_act'));
|
||||
});
|
||||
});
|
||||
</script>""")
|
||||
|
||||
return [tpl.render(
|
||||
Context(dict(
|
||||
url=reverse("modoboa.extensions.amavis.views.nbrequests"),
|
||||
interval=int(parameters.get_admin("CHECK_REQUESTS_INTERVAL")) * 1000,
|
||||
text=_("pending requests"),
|
||||
user_can_release=parameters.get_admin("USER_CAN_RELEASE")
|
||||
))
|
||||
)]
|
||||
|
||||
|
||||
@events.observe("TopNotifications")
|
||||
def display_requests(user):
|
||||
from .sql_listing import get_wrapper
|
||||
|
||||
if parameters.get_admin("USER_CAN_RELEASE") == "yes" \
|
||||
or user.group == "SimpleUsers":
|
||||
return []
|
||||
nbrequests = get_wrapper().get_pending_requests(user)
|
||||
|
||||
url = reverse("modoboa.extensions.amavis.views.index")
|
||||
url += "#listing/?viewrequests=1"
|
||||
tpl = Template('<div class="btn-group {{ css }}"><a id="nbrequests" href="{{ url }}" class="btn btn-danger">{{ label }}</a></div>')
|
||||
css = "hidden" if nbrequests == 0 else ""
|
||||
return [tpl.render(Context(dict(
|
||||
label=_("%d pending requests" % nbrequests), url=url, css=css
|
||||
)))]
|
||||
|
||||
|
||||
@events.observe("ExtraDomainForm")
|
||||
def extra_domain_form(user, domain):
|
||||
from .forms import DomainPolicyForm
|
||||
|
||||
if not user.has_perm("admin.view_domains"):
|
||||
return []
|
||||
return [
|
||||
dict(
|
||||
id="amavis", title=_("Content filter"), cls=DomainPolicyForm,
|
||||
formtpl="amavis/domain_content_filter.html"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@events.observe("FillDomainInstances")
|
||||
def fill_domain_instances(user, domain, instances):
|
||||
if not user.has_perm("admin.view_domains"):
|
||||
return
|
||||
instances["amavis"] = domain
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# coding: utf-8
|
||||
from django import forms
|
||||
from models import Policy, Users
|
||||
from modoboa.lib.formutils import InlineRadioSelect
|
||||
from modoboa.extensions.amavis.models import Policy, Users
|
||||
|
||||
|
||||
class DomainPolicyForm(forms.ModelForm):
|
||||
@@ -34,20 +34,21 @@ class DomainPolicyForm(forms.ModelForm):
|
||||
self.fields[f].required = False
|
||||
|
||||
def save(self, user, commit=True):
|
||||
p = super(DomainPolicyForm, self).save(commit=False)
|
||||
for f in ['bypass_spam_checks', 'bypass_virus_checks', 'bypass_banned_checks']:
|
||||
if getattr(p, f) == '':
|
||||
setattr(p, f, None)
|
||||
policy = super(DomainPolicyForm, self).save(commit=False)
|
||||
for field in ['bypass_spam_checks', 'bypass_virus_checks',
|
||||
'bypass_banned_checks']:
|
||||
if getattr(policy, field) == '':
|
||||
setattr(policy, field, None)
|
||||
|
||||
if self.cleaned_data['spam_subject_tag2_act']:
|
||||
p.spam_subject_tag2 = None
|
||||
policy.spam_subject_tag2 = None
|
||||
|
||||
if commit:
|
||||
p.save()
|
||||
policy.save()
|
||||
try:
|
||||
u = Users.objects.get(policy=p)
|
||||
u = Users.objects.get(fullname=policy.policy_name)
|
||||
except Users.DoesNotExist:
|
||||
u = Users.objects.get(email="@%s" % self.domain.name)
|
||||
u.policy = p
|
||||
p.save()
|
||||
return p
|
||||
u.policy = policy
|
||||
policy.save()
|
||||
return policy
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template import Template, Context
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions.admin.models import DomainAlias
|
||||
from modoboa.extensions.amavis.lib import (
|
||||
create_user_and_policy, update_user_and_policy, delete_user_and_policy,
|
||||
create_user_and_use_policy, delete_user
|
||||
)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target == "top_menu":
|
||||
return [
|
||||
{"name": "quarantine",
|
||||
"label": _("Quarantine"),
|
||||
"url": reverse('modoboa.extensions.amavis.views.index')}
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe("DomainCreated")
|
||||
def on_domain_created(user, domain):
|
||||
create_user_and_policy(domain.name)
|
||||
|
||||
|
||||
@events.observe("DomainModified")
|
||||
def on_domain_modified(domain):
|
||||
update_user_and_policy(domain.oldname, domain.name)
|
||||
|
||||
|
||||
@events.observe("DomainDeleted")
|
||||
def on_domain_deleted(domain):
|
||||
delete_user_and_policy(domain.name)
|
||||
|
||||
|
||||
@events.observe("DomainAliasCreated")
|
||||
def on_domain_alias_created(user, domainalias):
|
||||
create_user_and_use_policy(domainalias.name, domainalias.target.name)
|
||||
|
||||
|
||||
@events.observe("DomainAliasDeleted")
|
||||
def on_domain_alias_deleted(domainaliases):
|
||||
if isinstance(domainaliases, DomainAlias):
|
||||
domainaliases = [domainaliases]
|
||||
for domainalias in domainaliases:
|
||||
delete_user(domainalias.name)
|
||||
|
||||
|
||||
@events.observe("GetStaticContent")
|
||||
def extra_static_content(caller, user):
|
||||
if user.group == "SimpleUsers":
|
||||
return []
|
||||
|
||||
if caller == 'top' and parameters.get_admin("USER_CAN_RELEASE") == 'no':
|
||||
tpl = Template("""<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var poller = new Poller("{{ url }}", {
|
||||
interval: {{ interval }},
|
||||
success_cb: function(data) {
|
||||
var $link = $("#nbrequests");
|
||||
if (data.requests > 0) {
|
||||
$link.html(data.requests + " " + "{{ text }}");
|
||||
$link.parent().removeClass('hidden');
|
||||
} else {
|
||||
$link.parent().addClass('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
""")
|
||||
return [tpl.render(
|
||||
Context({
|
||||
'url': reverse("modoboa.extensions.amavis.views.nbrequests"),
|
||||
'interval': int(parameters.get_admin("CHECK_REQUESTS_INTERVAL")) * 1000,
|
||||
'text': _("pending requests"),
|
||||
})
|
||||
)]
|
||||
|
||||
if caller == 'domains':
|
||||
tpl = Template("""<script type="text/javascript">
|
||||
$(document).bind('domform_init', function() {
|
||||
activate_widget.call($('#id_spam_subject_tag2_act'));
|
||||
});
|
||||
</script>
|
||||
""")
|
||||
|
||||
return [tpl.render(Context({}))]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe("TopNotifications")
|
||||
def display_requests(user):
|
||||
from .sql_listing import get_wrapper
|
||||
|
||||
if parameters.get_admin("USER_CAN_RELEASE") == "yes" \
|
||||
or user.group == "SimpleUsers":
|
||||
return []
|
||||
nbrequests = get_wrapper().get_pending_requests(user)
|
||||
|
||||
url = reverse("modoboa.extensions.amavis.views.index")
|
||||
url += "#listing/?viewrequests=1"
|
||||
tpl = Template('<div class="btn-group {{ css }}"><a id="nbrequests" href="{{ url }}" class="btn btn-danger">{{ label }}</a></div>')
|
||||
css = "hidden" if nbrequests == 0 else ""
|
||||
return [tpl.render(Context(dict(
|
||||
label=_("%d pending requests" % nbrequests), url=url, css=css
|
||||
)))]
|
||||
|
||||
|
||||
def send_amavis_form():
|
||||
"""
|
||||
"""
|
||||
from .forms import DomainPolicyForm
|
||||
return [{
|
||||
'id': 'amavis', 'title': _("Content filter"), 'cls': DomainPolicyForm,
|
||||
'formtpl': 'amavis/domain_content_filter.html'
|
||||
}]
|
||||
|
||||
|
||||
@events.observe("ExtraDomainForm")
|
||||
def extra_domain_form(user, domain):
|
||||
if not user.has_perm("admin.view_domains"):
|
||||
return []
|
||||
return send_amavis_form()
|
||||
|
||||
|
||||
@events.observe('ExtraRelayDomainForm')
|
||||
def extra_relaydomain_form(user, rdomain):
|
||||
if not user.has_perm("postfix_relay_domains.add_relaydomain"):
|
||||
return []
|
||||
return send_amavis_form()
|
||||
|
||||
|
||||
@events.observe("FillDomainInstances")
|
||||
def fill_domain_instances(user, domain, instances):
|
||||
if not user.has_perm("admin.view_domains"):
|
||||
return
|
||||
instances["amavis"] = domain
|
||||
|
||||
|
||||
@events.observe("FillRelayDomainInstances")
|
||||
def fill_relaydomain_instances(user, rdomain, instances):
|
||||
if not user.has_perm("postfix_relay_domains.add_relaydomain"):
|
||||
return
|
||||
instances["amavis"] = rdomain
|
||||
@@ -7,8 +7,10 @@ from functools import wraps
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.exceptions import InternalError
|
||||
from modoboa.lib.webutils import NavigationParameters
|
||||
from modoboa.extensions.amavis.models import Users, Policy
|
||||
|
||||
|
||||
|
||||
def selfservice(ssfunc=None):
|
||||
@@ -51,7 +53,7 @@ class AMrelease(object):
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.connect(path)
|
||||
except socket.error, err:
|
||||
raise ModoboaException(
|
||||
raise InternalError(
|
||||
_("Connection to amavis failed: %s" % str(err))
|
||||
)
|
||||
|
||||
@@ -107,3 +109,71 @@ class QuarantineNavigationParameters(NavigationParameters):
|
||||
if params:
|
||||
url += "?%s" % ("&".join(params))
|
||||
return url
|
||||
|
||||
|
||||
def create_user_and_policy(name):
|
||||
"""Create records.
|
||||
|
||||
Create two records (a user and a policy) using :keyword:`name` as
|
||||
an identifier.
|
||||
|
||||
:param str name: name
|
||||
"""
|
||||
policy = Policy.objects.create(policy_name=name)
|
||||
Users.objects.create(
|
||||
email="@%s" % name, fullname=name,
|
||||
priority=7, policy=policy
|
||||
)
|
||||
|
||||
|
||||
def create_user_and_use_policy(name, policy_name):
|
||||
"""Create a *users* record and use an existing policy.
|
||||
|
||||
:param str name: user record name
|
||||
:param str policy_name: policy name
|
||||
"""
|
||||
policy = Policy.objects.get(policy_name=policy_name)
|
||||
Users.objects.create(
|
||||
email="@%s" % name, fullname=name,
|
||||
priority=7, policy=policy
|
||||
)
|
||||
|
||||
|
||||
def update_user_and_policy(oldname, newname):
|
||||
"""Update records.
|
||||
|
||||
:param str oldname: old name
|
||||
:param str newname: new name
|
||||
"""
|
||||
if oldname == newname:
|
||||
return
|
||||
u = Users.objects.get(email="@%s" % oldname)
|
||||
u.email = "@%s" % newname
|
||||
u.fullname = newname
|
||||
u.policy.policy_name = newname
|
||||
u.policy.save()
|
||||
u.save()
|
||||
|
||||
|
||||
def delete_user_and_policy(name):
|
||||
"""Delete records.
|
||||
|
||||
:param str name: identifier
|
||||
"""
|
||||
try:
|
||||
u = Users.objects.get(email="@%s" % name)
|
||||
except Users.DoesNotExist:
|
||||
return
|
||||
u.policy.delete()
|
||||
u.delete()
|
||||
|
||||
|
||||
def delete_user(name):
|
||||
"""Delete a *users* record.
|
||||
|
||||
:param str name: user record name
|
||||
"""
|
||||
try:
|
||||
Users.objects.get(email="@%s" % name).delete()
|
||||
except Users.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -10,9 +10,12 @@ class Migration(DataMigration):
|
||||
def forwards(self, orm):
|
||||
"Write your forwards methods here."
|
||||
orm["amavis.Users"].objects.all().delete()
|
||||
orm["amavis.Policy"].objects.create(id=1, policy_name='default', bypass_virus_checks=None,
|
||||
bypass_spam_checks=None, bypass_banned_checks=None,
|
||||
spam_subject_tag2=None)
|
||||
if not orm['amavis.Policy'].objects.all():
|
||||
orm["amavis.Policy"].objects.create(
|
||||
id=1, policy_name='default', bypass_virus_checks=None,
|
||||
bypass_spam_checks=None, bypass_banned_checks=None,
|
||||
spam_subject_tag2=None
|
||||
)
|
||||
for dom in orm["admin.Domain"].objects.all():
|
||||
orm["amavis.Users"].objects.create(email="@%s" % dom.name, fullname=dom.name,
|
||||
priority=7, policy_id=1)
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
"Write your forwards methods here."
|
||||
try:
|
||||
ext = orm["core.Extension"].objects.get(name="amavis")
|
||||
except ObjectDoesNotExist:
|
||||
return
|
||||
if not ext.enabled:
|
||||
return
|
||||
for domalias in orm["admin.DomainAlias"].objects.all():
|
||||
u = orm["amavis.Users"](
|
||||
email="@%s" % domalias.name, fullname=domalias.name, priority=7
|
||||
)
|
||||
u.policy = orm["amavis.Policy"].objects.get(policy_name=domalias.target.name)
|
||||
u.save()
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
raise RuntimeError("Cannot revert this migration")
|
||||
|
||||
models = {
|
||||
'admin.alias': {
|
||||
'Meta': {'ordering': "['domain__name', 'address']", 'unique_together': "(('address', 'domain'),)", 'object_name': 'Alias'},
|
||||
'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
|
||||
'aliases': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Alias']", 'symmetrical': 'False'}),
|
||||
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
|
||||
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'})
|
||||
},
|
||||
'admin.domain': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'Domain'},
|
||||
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'quota': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
'admin.domainalias': {
|
||||
'Meta': {'object_name': 'DomainAlias'},
|
||||
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"})
|
||||
},
|
||||
'admin.mailbox': {
|
||||
'Meta': {'object_name': 'Mailbox'},
|
||||
'address': ('django.db.models.fields.CharField', [], {'max_length': '252'}),
|
||||
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
|
||||
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'quota': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'use_domain_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.User']"})
|
||||
},
|
||||
'admin.mailboxoperation': {
|
||||
'Meta': {'object_name': 'MailboxOperation'},
|
||||
'argument': ('django.db.models.fields.TextField', [], {}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Mailbox']", 'null': 'True', 'blank': 'True'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '20'})
|
||||
},
|
||||
'admin.objectdates': {
|
||||
'Meta': {'object_name': 'ObjectDates'},
|
||||
'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
|
||||
},
|
||||
'admin.quota': {
|
||||
'Meta': {'object_name': 'Quota'},
|
||||
'bytes': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
|
||||
'mbox': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'quota_value'", 'unique': 'True', 'null': 'True', 'to': "orm['admin.Mailbox']"}),
|
||||
'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'username': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'primary_key': 'True'})
|
||||
},
|
||||
u'amavis.maddr': {
|
||||
'Meta': {'object_name': 'Maddr', 'db_table': "u'maddr'", 'managed': 'False'},
|
||||
'domain': ('django.db.models.fields.CharField', [], {'max_length': '765'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True'}),
|
||||
'partition_tag': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'amavis.mailaddr': {
|
||||
'Meta': {'object_name': 'Mailaddr', 'db_table': "u'mailaddr'", 'managed': 'False'},
|
||||
'email': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
|
||||
'priority': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
u'amavis.msgrcpt': {
|
||||
'Meta': {'unique_together': "(('partition_tag', 'mail', 'rseqnum'),)", 'object_name': 'Msgrcpt', 'db_table': "u'msgrcpt'", 'managed': 'False'},
|
||||
'bl': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
|
||||
'bspam_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
|
||||
'ds': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
|
||||
'is_local': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
|
||||
'mail': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['amavis.Msgs']", 'primary_key': 'True'}),
|
||||
'partition_tag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'rid': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['amavis.Maddr']", 'primary_key': 'True', 'db_column': "'rid'"}),
|
||||
'rs': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
|
||||
'rseqnum': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'smtp_resp': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'wl': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'})
|
||||
},
|
||||
u'amavis.msgs': {
|
||||
'Meta': {'unique_together': "(('partition_tag', 'mail_id'),)", 'object_name': 'Msgs', 'db_table': "u'msgs'", 'managed': 'False'},
|
||||
'am_id': ('django.db.models.fields.CharField', [], {'max_length': '60'}),
|
||||
'client_addr': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'content': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
|
||||
'dsn_sent': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
|
||||
'from_addr': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'max_length': '765'}),
|
||||
'mail_id': ('django.db.models.fields.CharField', [], {'max_length': '12', 'primary_key': 'True'}),
|
||||
'message_id': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'originating': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
|
||||
'partition_tag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'policy': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'quar_loc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'quar_type': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
|
||||
'secret_id': ('django.db.models.fields.CharField', [], {'max_length': '12', 'blank': 'True'}),
|
||||
'sid': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['amavis.Maddr']", 'db_column': "'sid'"}),
|
||||
'size': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'spam_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'subject': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'time_iso': ('django.db.models.fields.CharField', [], {'max_length': '48'}),
|
||||
'time_num': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
u'amavis.policy': {
|
||||
'Meta': {'object_name': 'Policy', 'db_table': "u'policy'", 'managed': 'False'},
|
||||
'addr_extension_bad_header': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'addr_extension_banned': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'addr_extension_spam': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'addr_extension_virus': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'archive_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'bad_header_admin': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'bad_header_lover': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'bad_header_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'banned_admin': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'banned_files_lover': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'banned_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'banned_rulenames': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'bypass_banned_checks': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '3', 'null': 'True'}),
|
||||
'bypass_header_checks': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'bypass_spam_checks': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '3', 'null': 'True'}),
|
||||
'bypass_virus_checks': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '3', 'null': 'True'}),
|
||||
'clean_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'disclaimer_options': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'forward_method': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'message_size_limit': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'newvirus_admin': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'policy_name': ('django.db.models.fields.CharField', [], {'max_length': '96', 'blank': 'True'}),
|
||||
'sa_userconf': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'sa_username': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_admin': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_dsn_cutoff_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'spam_kill_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'spam_lover': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_quarantine_cutoff_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'spam_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_subject_tag': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_subject_tag2': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_subject_tag3': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'spam_tag2_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'spam_tag3_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'spam_tag_level': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'unchecked_lover': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'unchecked_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'virus_admin': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'virus_lover': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'virus_quarantine_to': ('django.db.models.fields.CharField', [], {'max_length': '192', 'null': 'True', 'blank': 'True'}),
|
||||
'warnbadhrecip': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'warnbannedrecip': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
|
||||
'warnvirusrecip': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'amavis.quarantine': {
|
||||
'Meta': {'ordering': "['-mail__time_num']", 'unique_together': "(('partition_tag', 'mail', 'chunk_ind'),)", 'object_name': 'Quarantine', 'db_table': "u'quarantine'", 'managed': 'False'},
|
||||
'chunk_ind': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'mail': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['amavis.Msgs']", 'primary_key': 'True'}),
|
||||
'mail_text': ('django.db.models.fields.TextField', [], {}),
|
||||
'partition_tag': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'amavis.users': {
|
||||
'Meta': {'object_name': 'Users', 'db_table': "u'users'", 'managed': 'False'},
|
||||
'email': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'fullname': ('django.db.models.fields.CharField', [], {'max_length': '765', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'policy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['amavis.Policy']"}),
|
||||
'priority': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
u'amavis.wblist': {
|
||||
'Meta': {'object_name': 'Wblist', 'db_table': "u'wblist'", 'managed': 'False'},
|
||||
'rid': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
|
||||
'sid': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
|
||||
'wb': ('django.db.models.fields.CharField', [], {'max_length': '30'})
|
||||
},
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
u'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
u'core.extension': {
|
||||
'Meta': {'object_name': 'Extension'},
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
|
||||
},
|
||||
u'core.log': {
|
||||
'Meta': {'object_name': 'Log'},
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'level': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
|
||||
'logger': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
|
||||
'message': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
u'core.objectaccess': {
|
||||
'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.User']"})
|
||||
},
|
||||
u'core.user': {
|
||||
'Meta': {'ordering': "['username']", 'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_local': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '254'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['core', 'admin', 'amavis']
|
||||
symmetrical = True
|
||||
@@ -146,9 +146,8 @@ Quarantine.prototype = {
|
||||
url: $link.attr("href"),
|
||||
data: "action=" + name + "&selection=" + selection.join(","),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: $.proxy(this.action_cb, this)
|
||||
});
|
||||
dataType: 'json'
|
||||
}).done($.proxy(this.action_cb, this));
|
||||
},
|
||||
|
||||
release_selection: function(e) {
|
||||
@@ -168,9 +167,8 @@ Quarantine.prototype = {
|
||||
}
|
||||
$.ajax({
|
||||
url: $link.attr("href"),
|
||||
dataType: 'json',
|
||||
success: $.proxy(this.action_cb, this)
|
||||
});
|
||||
dataType: 'json'
|
||||
}).done($.proxy(this.action_cb, this));
|
||||
},
|
||||
|
||||
release: function(e) {
|
||||
@@ -204,11 +202,11 @@ Quarantine.prototype = {
|
||||
if (!$rawheaders.length) {
|
||||
$.ajax({
|
||||
url: $link.attr("href"),
|
||||
success: $.proxy(function(data) {
|
||||
$mailcontent.find("#table-container").append($(data));
|
||||
this.show_rawheaders($mailcontent);
|
||||
}, this)
|
||||
});
|
||||
global: false
|
||||
}).done($.proxy(function(data) {
|
||||
$mailcontent.find("#table-container").append($(data));
|
||||
this.show_rawheaders($mailcontent);
|
||||
}, this));
|
||||
return;
|
||||
}
|
||||
this.show_rawheaders($mailcontent);
|
||||
|
||||
@@ -7,14 +7,9 @@ function send_action(e, message) {
|
||||
}
|
||||
$.ajax({
|
||||
url: $link.attr("href"),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.status == "ok") {
|
||||
$("body").notify("success", data.respmsg, 2000);
|
||||
} else {
|
||||
$("body").notify("error", data.respmsg);
|
||||
}
|
||||
}
|
||||
dataType: 'json'
|
||||
}).done(function(data) {
|
||||
$("body").notify("success", data, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ from django.contrib.auth.decorators \
|
||||
import login_required, user_passes_test
|
||||
from django.db.models import Q
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.exceptions import ModoboaException, BadRequest
|
||||
from modoboa.lib.webutils import (
|
||||
getctx, ajax_response, ajax_simple_response
|
||||
getctx, ajax_response, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.templatetags.lib_tags import pagination_bar
|
||||
from modoboa.extensions.admin.models import Mailbox, Domain
|
||||
@@ -56,7 +56,7 @@ def _listing(request):
|
||||
rcptfilter = pattern
|
||||
continue
|
||||
else:
|
||||
raise Exception("unsupported search criteria %s" % c)
|
||||
raise BadRequest("unsupported search criteria %s" % c)
|
||||
flt = nfilter if flt is None else flt | nfilter
|
||||
|
||||
msgtype = navparams.get('msgtype', None)
|
||||
@@ -180,14 +180,14 @@ def check_mail_id(request, mail_id):
|
||||
def delete_selfservice(request, mail_id):
|
||||
rcpt = request.GET.get("rcpt", None)
|
||||
if rcpt is None:
|
||||
raise ModoboaException(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
try:
|
||||
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
|
||||
msgrcpt.rs = 'D'
|
||||
msgrcpt.save()
|
||||
except Msgrcpt.DoesNotExist:
|
||||
raise ModoboaException(_("Invalid request"))
|
||||
return ajax_simple_response(dict(status="ok", respmsg=_("Message deleted")))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
return render_to_json_response(_("Message deleted"))
|
||||
|
||||
|
||||
@selfservice(delete_selfservice)
|
||||
@@ -221,13 +221,13 @@ def release_selfservice(request, mail_id):
|
||||
rcpt = request.GET.get("rcpt", None)
|
||||
secret_id = request.GET.get("secret_id", None)
|
||||
if rcpt is None or secret_id is None:
|
||||
raise ModoboaException(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
try:
|
||||
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
|
||||
except Msgrcpt.DoesNotExist:
|
||||
raise ModoboaException(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
if secret_id != msgrcpt.mail.secret_id:
|
||||
raise ModoboaException(_("Invalid request"))
|
||||
raise BadRequest(_("Invalid request"))
|
||||
if parameters.get_admin("USER_CAN_RELEASE") == "no":
|
||||
msgrcpt.rs = 'p'
|
||||
msg = _("Request sent")
|
||||
@@ -238,9 +238,9 @@ def release_selfservice(request, mail_id):
|
||||
rcpt.rs = 'R'
|
||||
msg = _("Message released")
|
||||
else:
|
||||
raise ModoboaException(result)
|
||||
raise BadRequest(result)
|
||||
msgrcpt.save()
|
||||
return ajax_simple_response(dict(status="ok", respmsg=msg))
|
||||
return render_to_json_response(msg)
|
||||
|
||||
|
||||
@selfservice(release_selfservice)
|
||||
@@ -313,4 +313,4 @@ def process(request):
|
||||
@user_passes_test(lambda u: u.group != 'SimpleUsers')
|
||||
def nbrequests(request):
|
||||
result = get_wrapper().get_pending_requests(request.user)
|
||||
return ajax_simple_response(dict(status="ok", requests=result))
|
||||
return render_to_json_response({'requests': result})
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
The *limits* extension
|
||||
----------------------
|
||||
"""
|
||||
import sys
|
||||
from django.contrib.auth.models import Permission, Group
|
||||
from django.db import IntegrityError
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
from .forms import ResourcePoolForm
|
||||
import controls
|
||||
|
||||
levents = [
|
||||
'GetExtraLimitTemplates'
|
||||
]
|
||||
|
||||
|
||||
class Limits(ModoExtension):
|
||||
@@ -19,7 +22,8 @@ class Limits(ModoExtension):
|
||||
label = "Limits"
|
||||
version = "1.0"
|
||||
description = ugettext_lazy(
|
||||
"Per administrator resources to limit the number of objects they can create"
|
||||
"Per administrator resources to limit the number of objects "
|
||||
"they can create"
|
||||
)
|
||||
|
||||
def init(self):
|
||||
@@ -47,94 +51,17 @@ class Limits(ModoExtension):
|
||||
|
||||
def load(self):
|
||||
from modoboa.extensions.limits.app_settings import ParametersForm
|
||||
from modoboa.extensions.limits import controls
|
||||
|
||||
parameters.register(ParametersForm, ugettext_lazy("Limits"))
|
||||
events.declare(levents)
|
||||
from modoboa.extensions.limits import general_callbacks
|
||||
if 'modoboa.extensions.limits.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
Group.objects.get(name="Resellers").delete()
|
||||
|
||||
exts_pool.register_extension(Limits)
|
||||
|
||||
|
||||
@events.observe("GetExtraRoles")
|
||||
def get_extra_roles(user):
|
||||
if user.is_superuser:
|
||||
return [("Resellers", _("Reseller")), ]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe("ExtraAdminContent")
|
||||
def display_pool_usage(user, target, currentpage):
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
if target != "leftcol" or user.is_superuser:
|
||||
return []
|
||||
if currentpage == "identities":
|
||||
names = ["mailboxes_limit", "mailbox_aliases_limit"]
|
||||
if user.has_perm("admin.add_domain"):
|
||||
names += ["domain_admins_limit"]
|
||||
else:
|
||||
names = ["domains_limit", "domain_aliases_limit"]
|
||||
limits = user.limitspool.limit_set.filter(name__in=names, maxvalue__gt=0)
|
||||
if len(limits) == 0:
|
||||
return []
|
||||
return [render_to_string("limits/poolusage.html", dict(limits=limits))]
|
||||
|
||||
|
||||
@events.observe("ExtraAccountForm")
|
||||
def extra_account_form(user, account=None):
|
||||
if not user.group in ["SuperAdmins", "Resellers"]:
|
||||
return []
|
||||
if account is not None and not account.group in ["Resellers", "DomainAdmins"]:
|
||||
return []
|
||||
|
||||
return [
|
||||
dict(
|
||||
id="resources", title=_("Resources"), cls=ResourcePoolForm
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@events.observe("CheckExtraAccountForm")
|
||||
def check_form_access(account, form):
|
||||
if form["id"] != "resources":
|
||||
return [True]
|
||||
if not account.belongs_to_group("Resellers") and \
|
||||
not account.belongs_to_group("DomainAdmins"):
|
||||
return [False]
|
||||
return [True]
|
||||
|
||||
|
||||
@events.observe("FillAccountInstances")
|
||||
def fill_account_instances(user, account, instances):
|
||||
if not user.is_superuser and not user.belongs_to_group("Resellers"):
|
||||
return
|
||||
if not account.belongs_to_group("Resellers") and \
|
||||
not account.belongs_to_group("DomainAdmins"):
|
||||
return
|
||||
instances["resources"] = account
|
||||
|
||||
|
||||
@events.observe("GetStaticContent")
|
||||
def get_static_content(user):
|
||||
if user.group == "SimpleUsers":
|
||||
return []
|
||||
return """<style>
|
||||
.resource {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.resource .progress {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.resource .progress .bar {
|
||||
color: #000000;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(".progress").tooltip();
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
|
||||
@@ -39,3 +39,7 @@ class ParametersForm(AdminParametersForm):
|
||||
help_text=_("Maximum number of allowed aliases for a new administrator"),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "span1"})
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AdminParametersForm, self).__init__(*args, **kwargs)
|
||||
self._load_extra_parameters('A')
|
||||
|
||||
@@ -6,43 +6,27 @@
|
||||
"""
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.permissions import get_object_owner
|
||||
from .lib import LimitReached
|
||||
from .models import limits_tpl, LimitsPool
|
||||
from .lib import LimitReached, inc_limit_usage, dec_limit_usage
|
||||
from .models import LimitTemplates, LimitsPool
|
||||
|
||||
|
||||
def check_limit(user, lname):
|
||||
def check_limit(user, lname, count=1):
|
||||
"""Check if a user has reached a defined limit
|
||||
|
||||
We check if the user will not reach the given limit after this
|
||||
creation.
|
||||
|
||||
Raise a ``LimitReached`` exception if the limit is reached.
|
||||
|
||||
:param user: a ``User`` object
|
||||
:param int count: the number of objects the user tries to create
|
||||
:raises: ``LimitReached``
|
||||
"""
|
||||
try:
|
||||
if user.limitspool.will_be_reached(lname):
|
||||
if user.limitspool.will_be_reached(lname, count):
|
||||
raise LimitReached(user.limitspool.get_limit(lname))
|
||||
except LimitsPool.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def inc_limit(user, lname):
|
||||
try:
|
||||
user.limitspool.inc_curvalue(lname)
|
||||
except LimitsPool.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def dec_limit(user, lname):
|
||||
if user is None:
|
||||
return
|
||||
try:
|
||||
user.limitspool.dec_curvalue(lname)
|
||||
except LimitsPool.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def move_pool_resource(owner, user):
|
||||
"""Move resource from one pool to another
|
||||
|
||||
@@ -54,7 +38,7 @@ def move_pool_resource(owner, user):
|
||||
except LimitsPool.DoesNotExist:
|
||||
return
|
||||
if not owner.is_superuser:
|
||||
for ltpl in limits_tpl:
|
||||
for ltpl in LimitTemplates().templates:
|
||||
l = user.limitspool.get_limit(ltpl[0])
|
||||
if l.maxvalue < 0:
|
||||
continue
|
||||
@@ -66,15 +50,16 @@ def move_pool_resource(owner, user):
|
||||
user.limitspool.delete()
|
||||
|
||||
|
||||
@events.observe('CreateDomain')
|
||||
@events.observe('DomainCreated')
|
||||
def inc_nb_domains(user, domain):
|
||||
inc_limit(user, 'domains_limit')
|
||||
inc_limit_usage(user, 'domains_limit')
|
||||
|
||||
|
||||
@events.observe('DeleteDomain')
|
||||
def dec_nb_domains(domain):
|
||||
owner = get_object_owner(domain)
|
||||
dec_limit(owner, 'domains_limit')
|
||||
@events.observe('DomainDeleted')
|
||||
def dec_nb_domains(domain, owner=None):
|
||||
if owner is None:
|
||||
owner = get_object_owner(domain)
|
||||
dec_limit_usage(owner, 'domains_limit')
|
||||
for domalias in domain.domainalias_set.all():
|
||||
dec_nb_domaliases(domalias)
|
||||
for mailbox in domain.mailbox_set.all():
|
||||
@@ -85,7 +70,7 @@ def dec_nb_domains(domain):
|
||||
|
||||
@events.observe('DomainAliasCreated')
|
||||
def inc_nb_domaliases(user, domalias):
|
||||
inc_limit(user, 'domain_aliases_limit')
|
||||
inc_limit_usage(user, 'domain_aliases_limit')
|
||||
|
||||
|
||||
@events.observe('DomainAliasDeleted')
|
||||
@@ -96,15 +81,15 @@ def dec_nb_domaliases(domainaliases):
|
||||
domainaliases = [domainaliases]
|
||||
for domainalias in domainaliases:
|
||||
owner = get_object_owner(domainalias)
|
||||
dec_limit(owner, 'domain_aliases_limit')
|
||||
dec_limit_usage(owner, 'domain_aliases_limit')
|
||||
|
||||
|
||||
@events.observe('CreateMailbox')
|
||||
@events.observe('MailboxCreated')
|
||||
def inc_nb_mailboxes(user, mailbox):
|
||||
inc_limit(user, 'mailboxes_limit')
|
||||
inc_limit_usage(user, 'mailboxes_limit')
|
||||
|
||||
|
||||
@events.observe('DeleteMailbox')
|
||||
@events.observe('MailboxDeleted')
|
||||
def dec_nb_mailboxes(mailboxes):
|
||||
from modoboa.extensions.admin.models import Mailbox
|
||||
|
||||
@@ -112,40 +97,66 @@ def dec_nb_mailboxes(mailboxes):
|
||||
mailboxes = [mailboxes]
|
||||
for mailbox in mailboxes:
|
||||
owner = get_object_owner(mailbox)
|
||||
dec_limit(owner, 'mailboxes_limit')
|
||||
dec_limit_usage(owner, 'mailboxes_limit')
|
||||
|
||||
|
||||
@events.observe('MailboxAliasCreated')
|
||||
def inc_nb_mbaliases(user, mailboxalias):
|
||||
inc_limit(user, 'mailbox_aliases_limit')
|
||||
inc_limit_usage(user, 'mailbox_aliases_limit')
|
||||
|
||||
|
||||
@events.observe('MailboxAliasDeleted')
|
||||
def dec_nb_mbaliases(mailboxalias):
|
||||
owner = get_object_owner(mailboxalias)
|
||||
dec_limit(owner, 'mailbox_aliases_limit')
|
||||
def dec_nb_mbaliases(mailboxaliases):
|
||||
from modoboa.extensions.admin.models import Alias
|
||||
|
||||
if isinstance(mailboxaliases, Alias):
|
||||
mailboxaliases = [mailboxaliases]
|
||||
for alias in mailboxaliases:
|
||||
owner = get_object_owner(alias)
|
||||
dec_limit_usage(owner, 'mailbox_aliases_limit')
|
||||
|
||||
|
||||
@events.observe('CanCreate')
|
||||
def can_create_new_object(user, objtype):
|
||||
check_limit(user, '%s_limit' % objtype)
|
||||
def can_create_new_object(user, objtype, count=1):
|
||||
check_limit(user, '%s_limit' % objtype, count)
|
||||
|
||||
|
||||
@events.observe("AccountCreated")
|
||||
def create_pool(user):
|
||||
owner = get_object_owner(user)
|
||||
if not owner.is_superuser and \
|
||||
not owner.belongs_to_group("Resellers"):
|
||||
if not owner.group in ['SuperAdmins', 'Resellers']:
|
||||
return
|
||||
|
||||
if user.belongs_to_group("DomainAdmins"):
|
||||
if user.group == 'DomainAdmins':
|
||||
check_limit(owner, 'domain_admins_limit')
|
||||
inc_limit(owner, 'domain_admins_limit')
|
||||
inc_limit_usage(owner, 'domain_admins_limit')
|
||||
|
||||
if user.group in ["DomainAdmins", "Resellers"]:
|
||||
p = LimitsPool(user=user)
|
||||
p.save()
|
||||
p.create_limits()
|
||||
if user.group in ['DomainAdmins', 'Resellers']:
|
||||
p, created = LimitsPool.objects.get_or_create(user=user)
|
||||
p.create_limits(owner)
|
||||
|
||||
|
||||
@events.observe("UserCanSetRole")
|
||||
def user_can_set_role(user, role, account=None):
|
||||
"""Check if the user can still set this role.
|
||||
|
||||
The only interesting case concerns resellers defining new domain
|
||||
administrators. We want to check if they are allowed to do this
|
||||
operation before any modification is made to :keyword:`account`.
|
||||
|
||||
:param ``User`` user: connected user
|
||||
:param ``User`` account: account modified (None on creation)
|
||||
:param str newrole: role to check
|
||||
"""
|
||||
if role == 'DomainAdmins':
|
||||
lname = 'domain_admins_limit'
|
||||
else:
|
||||
return [True]
|
||||
if user.is_superuser or not user.limitspool.will_be_reached(lname):
|
||||
return [True]
|
||||
if account is not None and account.group == role:
|
||||
return [True]
|
||||
return [False]
|
||||
|
||||
|
||||
@events.observe("AccountModified")
|
||||
@@ -164,19 +175,19 @@ def on_account_modified(old, new):
|
||||
except LimitsPool.DoesNotExist:
|
||||
p = LimitsPool(user=new)
|
||||
p.save()
|
||||
p.create_limits()
|
||||
p.create_limits(owner)
|
||||
|
||||
if not new.group in ["DomainAdmins", "Resellers"]:
|
||||
move_pool_resource(owner, new)
|
||||
|
||||
if old.oldgroup == "DomainAdmins":
|
||||
if new.group != "DomainAdmins":
|
||||
dec_limit(owner, 'domain_admins_limit')
|
||||
dec_limit_usage(owner, 'domain_admins_limit')
|
||||
return
|
||||
|
||||
if new.group == "DomainAdmins":
|
||||
check_limit(owner, 'domain_admins_limit')
|
||||
inc_limit(owner, 'domain_admins_limit')
|
||||
inc_limit_usage(owner, 'domain_admins_limit')
|
||||
|
||||
|
||||
@events.observe("AccountDeleted")
|
||||
@@ -188,4 +199,21 @@ def on_account_deleted(account, byuser, **kwargs):
|
||||
move_pool_resource(owner, account)
|
||||
|
||||
if account.group == "DomainAdmins":
|
||||
dec_limit(owner, 'domain_admins_limit')
|
||||
dec_limit_usage(owner, 'domain_admins_limit')
|
||||
|
||||
|
||||
@events.observe('DomainOwnershipRemoved')
|
||||
def domain_ownership_removed(reseller, domain):
|
||||
"""DomainOwnershipRemoved listener.
|
||||
|
||||
The access :keyword:`reseller` had to :keyword:`domain` has been
|
||||
removed by a super adminstrator. We must decrement all limit
|
||||
usages.
|
||||
|
||||
:param ``User`` reseller: reseller that created :keyword:`domain`
|
||||
:param ``Domain`` domain: domain
|
||||
"""
|
||||
dec_nb_domains(domain, reseller)
|
||||
for dadmin in domain.admins:
|
||||
if reseller.is_owner(dadmin):
|
||||
dec_limit_usage(reseller, 'domain_admins_limit')
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
# coding: utf-8
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import parameters
|
||||
from .models import limits_tpl
|
||||
from .models import LimitTemplates
|
||||
from .lib import BadLimitValue, UnsufficientResource
|
||||
|
||||
|
||||
class ResourcePoolForm(forms.Form):
|
||||
domain_admins_limit = forms.IntegerField(
|
||||
label=ugettext_lazy("Domain admins"),
|
||||
help_text=ugettext_lazy("Maximum number of domain administrators this user can create")
|
||||
)
|
||||
domains_limit = forms.IntegerField(
|
||||
label=ugettext_lazy("Domains"),
|
||||
help_text=ugettext_lazy("Maximum number of domains this user can create")
|
||||
)
|
||||
domain_aliases_limit = forms.IntegerField(
|
||||
label=ugettext_lazy("Domain aliases"),
|
||||
help_text=ugettext_lazy("Maximum number of domain aliases this user can create")
|
||||
)
|
||||
mailboxes_limit = forms.IntegerField(
|
||||
label=ugettext_lazy("Mailboxes"),
|
||||
help_text=ugettext_lazy("Maximum number of mailboxes this user can create")
|
||||
)
|
||||
mailbox_aliases_limit = forms.IntegerField(
|
||||
label=ugettext_lazy("Mailbox aliases"),
|
||||
help_text=ugettext_lazy("Maximum number of mailbox aliases this user can create")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.account = None
|
||||
if "instance" in kwargs:
|
||||
self.account = kwargs["instance"]
|
||||
del kwargs["instance"]
|
||||
super(ResourcePoolForm, self).__init__(*args, **kwargs)
|
||||
for tpl in LimitTemplates().templates:
|
||||
if len(tpl) > 3 and self.account is not None and self.account.group != tpl[3]:
|
||||
continue
|
||||
self.fields[tpl[0]] = forms.IntegerField(
|
||||
label=tpl[1], help_text=tpl[2]
|
||||
)
|
||||
if hasattr(self, "account"):
|
||||
if self.account.group != "Resellers":
|
||||
del self.fields["domain_admins_limit"]
|
||||
del self.fields["domains_limit"]
|
||||
del self.fields["domain_aliases_limit"]
|
||||
self.load_from_user(self.account)
|
||||
|
||||
def check_limit_value(self, lname):
|
||||
@@ -46,29 +28,30 @@ class ResourcePoolForm(forms.Form):
|
||||
raise forms.ValidationError(_("Invalid limit"))
|
||||
return self.cleaned_data[lname]
|
||||
|
||||
def clean_domains_limit(self):
|
||||
return self.check_limit_value("domains_limit")
|
||||
|
||||
def clean_domain_aliases_limit(self):
|
||||
return self.check_limit_value("domain_aliases_limit")
|
||||
|
||||
def clean_mailboxes_limit(self):
|
||||
return self.check_limit_value("mailboxes_limit")
|
||||
|
||||
def clean_mailbox_aliases_limit(self):
|
||||
return self.check_limit_value("mailbox_aliases_limit")
|
||||
def clean(self):
|
||||
cleaned_data = super(ResourcePoolForm, self).clean()
|
||||
for lname in self.fields.keys():
|
||||
if not lname in self._errors and cleaned_data[lname] < -1:
|
||||
self._errors[lname] = self.error_class([_('Invalid limit')])
|
||||
del cleaned_data[lname]
|
||||
return cleaned_data
|
||||
|
||||
def load_from_user(self, user):
|
||||
for l in limits_tpl:
|
||||
if not l[0] in self.fields:
|
||||
continue
|
||||
self.fields[l[0]].initial = user.limitspool.getmaxvalue(l[0])
|
||||
for lname in self.fields.keys():
|
||||
self.fields[lname].initial = user.limitspool.getmaxvalue(lname)
|
||||
# The following lines will become useless in a near
|
||||
# future.
|
||||
if self.fields[l[0]].initial == -2:
|
||||
self.fields[l[0]].initial = parameters.get_admin("DEFLT_%s" % l[0].upper())
|
||||
if self.fields[lname].initial == -2:
|
||||
self.fields[lname].initial = parameters.get_admin("DEFLT_%s" % lname.upper())
|
||||
|
||||
def allocate_from_pool(self, limit, pool):
|
||||
"""Allocate resource using an existing pool.
|
||||
|
||||
When a reseller creates a domain administrator, he generally
|
||||
assigns him resource to create new objetcs. As a reseller may
|
||||
also be limited, the resource he gives is taken from its own
|
||||
pool.
|
||||
"""
|
||||
ol = pool.get_limit(limit.name)
|
||||
if ol.maxvalue == -2:
|
||||
raise BadLimitValue(_("Your resources are not initialized yet"))
|
||||
@@ -92,7 +75,7 @@ class ResourcePoolForm(forms.Form):
|
||||
from modoboa.lib.permissions import get_object_owner
|
||||
|
||||
owner = get_object_owner(self.account)
|
||||
for ltpl in limits_tpl:
|
||||
for ltpl in LimitTemplates().templates:
|
||||
if not ltpl[0] in self.cleaned_data:
|
||||
continue
|
||||
l = self.account.limitspool.limit_set.get(name=ltpl[0])
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib import events
|
||||
from modoboa.extensions.limits.models import LimitTemplates
|
||||
from modoboa.extensions.limits.forms import ResourcePoolForm
|
||||
|
||||
|
||||
@events.observe("GetExtraRoles")
|
||||
def get_extra_roles(user):
|
||||
if user.is_superuser:
|
||||
return [("Resellers", _("Reseller")), ]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe("ExtraAdminContent")
|
||||
def display_pool_usage(user, target, currentpage):
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
if target != "leftcol" or user.is_superuser:
|
||||
return []
|
||||
if currentpage == "identities":
|
||||
names = ["mailboxes_limit", "mailbox_aliases_limit"]
|
||||
if user.has_perm("admin.add_domain"):
|
||||
names += ["domain_admins_limit"]
|
||||
else:
|
||||
names = [
|
||||
tpl[0] for tpl in LimitTemplates().templates
|
||||
if tpl[0] not in ["domain_admins_limit", "mailboxes_limit",
|
||||
"mailbox_aliases_limit"]
|
||||
and (len(tpl) == 3 or tpl[3] == user.group)
|
||||
]
|
||||
|
||||
limits = user.limitspool.limit_set.filter(name__in=names, maxvalue__gt=0)
|
||||
if len(limits) == 0:
|
||||
return []
|
||||
return [render_to_string("limits/poolusage.html", dict(limits=limits))]
|
||||
|
||||
|
||||
@events.observe("ExtraAccountForm")
|
||||
def extra_account_form(user, account=None):
|
||||
if not user.group in ["SuperAdmins", "Resellers"]:
|
||||
return []
|
||||
if account is not None and \
|
||||
not account.group in ["Resellers", "DomainAdmins"]:
|
||||
return []
|
||||
|
||||
return [
|
||||
dict(
|
||||
id="resources", title=_("Resources"), cls=ResourcePoolForm
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@events.observe("CheckExtraAccountForm")
|
||||
def check_form_access(account, form):
|
||||
if form["id"] != "resources":
|
||||
return [True]
|
||||
if not account.belongs_to_group("Resellers") and \
|
||||
not account.belongs_to_group("DomainAdmins"):
|
||||
return [False]
|
||||
return [True]
|
||||
|
||||
|
||||
@events.observe("FillAccountInstances")
|
||||
def fill_account_instances(user, account, instances):
|
||||
if not user.is_superuser and not user.belongs_to_group("Resellers"):
|
||||
return
|
||||
if not account.belongs_to_group("Resellers") and \
|
||||
not account.belongs_to_group("DomainAdmins"):
|
||||
return
|
||||
instances["resources"] = account
|
||||
|
||||
|
||||
@events.observe("GetStaticContent")
|
||||
def get_static_content(caller, user):
|
||||
if caller not in ['domains', 'identities']:
|
||||
return []
|
||||
if user.group == "SimpleUsers":
|
||||
return []
|
||||
return ["""<style>
|
||||
.resource {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.resource .progress {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.resource .progress .bar {
|
||||
color: #000000;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(".progress").tooltip();
|
||||
});
|
||||
</script>
|
||||
"""]
|
||||
@@ -6,9 +6,12 @@
|
||||
"""
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.extensions.limits.models import LimitsPool
|
||||
|
||||
|
||||
class LimitReached(ModoboaException):
|
||||
http_code = 403
|
||||
|
||||
def __init__(self, limit):
|
||||
self.limit = limit
|
||||
|
||||
@@ -17,6 +20,8 @@ class LimitReached(ModoboaException):
|
||||
|
||||
|
||||
class UnsufficientResource(ModoboaException):
|
||||
http_code = 424
|
||||
|
||||
def __init__(self, limit):
|
||||
self.limit = limit
|
||||
|
||||
@@ -25,4 +30,23 @@ class UnsufficientResource(ModoboaException):
|
||||
|
||||
|
||||
class BadLimitValue(ModoboaException):
|
||||
pass
|
||||
http_code = 400
|
||||
|
||||
|
||||
def inc_limit_usage(user, lname):
|
||||
"""Increase a given limit usage.
|
||||
|
||||
"""
|
||||
try:
|
||||
user.limitspool.inc_curvalue(lname)
|
||||
except LimitsPool.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def dec_limit_usage(user, lname):
|
||||
if user is None:
|
||||
return
|
||||
try:
|
||||
user.limitspool.dec_curvalue(lname)
|
||||
except LimitsPool.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -2,27 +2,56 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.singleton import Singleton
|
||||
from django.conf import settings
|
||||
from modoboa.lib import events
|
||||
|
||||
limits_tpl = [
|
||||
("domain_admins_limit", ugettext_lazy("Domain admins")),
|
||||
("domains_limit", ugettext_lazy("Domains")),
|
||||
("domain_aliases_limit", ugettext_lazy("Domain aliases")),
|
||||
("mailboxes_limit", ugettext_lazy("Mailboxes")),
|
||||
("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"))
|
||||
]
|
||||
|
||||
class LimitTemplates(Singleton):
|
||||
|
||||
def __init__(self):
|
||||
self.__templates = [
|
||||
("domain_admins_limit", ugettext_lazy("Domain admins"),
|
||||
ugettext_lazy("Maximum number of domain administrators this user can create"),
|
||||
'Resellers'),
|
||||
("domains_limit", ugettext_lazy("Domains"),
|
||||
ugettext_lazy("Maximum number of domains this user can create"),
|
||||
'Resellers'),
|
||||
("domain_aliases_limit", ugettext_lazy("Domain aliases"),
|
||||
ugettext_lazy("Maximum number of domain aliases this user can create"),
|
||||
'Resellers'),
|
||||
("mailboxes_limit", ugettext_lazy("Mailboxes"),
|
||||
ugettext_lazy("Maximum number of mailboxes this user can create")),
|
||||
("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"),
|
||||
ugettext_lazy("Maximum number of mailbox aliases this user can create"))
|
||||
]
|
||||
|
||||
@property
|
||||
def templates(self):
|
||||
return self.__templates + events.raiseQueryEvent('GetExtraLimitTemplates')
|
||||
|
||||
|
||||
class LimitsPool(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
||||
|
||||
def create_limits(self):
|
||||
for ltpl in limits_tpl:
|
||||
l = Limit()
|
||||
l.name = ltpl[0]
|
||||
l.pool = self
|
||||
l.maxvalue = int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper()))
|
||||
l.save()
|
||||
def create_limits(self, creator):
|
||||
"""Create limits for this pool.
|
||||
|
||||
All limits defined into ``LimitTemplates`` will be created. If
|
||||
the creator is a super administrator, the default maximum values
|
||||
will be used.
|
||||
|
||||
:param ``User`` creator: user creating this pool
|
||||
"""
|
||||
for ltpl in LimitTemplates().templates:
|
||||
try:
|
||||
Limit.objects.get(name=ltpl[0], pool=self)
|
||||
except Limit.DoesNotExist:
|
||||
maxvalue = int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper())) \
|
||||
if creator.is_superuser else 0
|
||||
Limit.objects.create(
|
||||
name=ltpl[0], pool=self, maxvalue=maxvalue
|
||||
)
|
||||
|
||||
def getcurvalue(self, lname):
|
||||
l = self.limit_set.get(name=lname)
|
||||
@@ -32,6 +61,11 @@ class LimitsPool(models.Model):
|
||||
l = self.limit_set.get(name=lname)
|
||||
return l.maxvalue
|
||||
|
||||
def set_maxvalue(self, lname, value):
|
||||
l = self.limit_set.get(name=lname)
|
||||
l.maxvalue = value
|
||||
l.save()
|
||||
|
||||
def inc_curvalue(self, lname, nb=1):
|
||||
l = self.limit_set.get(name=lname)
|
||||
l.curvalue += nb
|
||||
@@ -92,7 +126,7 @@ class Limit(models.Model):
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
for l in limits_tpl:
|
||||
for l in LimitTemplates().templates:
|
||||
if l[0] == self.name:
|
||||
return l[1]
|
||||
return ""
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib.tests import ExtTestCase
|
||||
from modoboa.core.factories import UserFactory
|
||||
from modoboa.extensions.admin.factories import DomainFactory, MailboxFactory
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.core.models import User
|
||||
from modoboa.core.factories import UserFactory
|
||||
from modoboa.extensions.admin.factories import (
|
||||
DomainFactory, MailboxFactory, populate_database
|
||||
)
|
||||
from modoboa.extensions.admin.models import Alias, Domain, DomainAlias
|
||||
from modoboa.extensions.limits.models import LimitTemplates
|
||||
|
||||
|
||||
class PermissionsTestCase(ExtTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
name = "limits"
|
||||
|
||||
def setUp(self):
|
||||
super(PermissionsTestCase, self).setUp()
|
||||
dom = DomainFactory(name='test.com')
|
||||
self.admin = UserFactory(
|
||||
username='admin@test.com', groups=('DomainAdmins',)
|
||||
)
|
||||
MailboxFactory(address='admin', domain=dom, user=self.admin)
|
||||
dom.add_admin(self.admin)
|
||||
self.activate_extensions('limits')
|
||||
populate_database()
|
||||
|
||||
def test_domainadmin_deletes_reseller(self):
|
||||
"""Check if a domain admin can delete a reseller.
|
||||
@@ -26,13 +26,378 @@ class PermissionsTestCase(ExtTestCase):
|
||||
values = dict(
|
||||
username="reseller@test.com", first_name="Reseller", last_name="",
|
||||
password1="toto", password2="toto", role="Resellers",
|
||||
is_active=True, email="reseller@test.com", stepid=2
|
||||
is_active=True, email="reseller@test.com", stepid='step2'
|
||||
)
|
||||
self.check_ajax_post(reverse("modoboa.extensions.admin.views.identity.newaccount"), values)
|
||||
self.ajax_post(reverse("modoboa.extensions.admin.views.identity.newaccount"), values)
|
||||
account = User.objects.get(username="reseller@test.com")
|
||||
self.clt.logout()
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
self.check_ajax_get(
|
||||
resp = self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount", args=[account.id]),
|
||||
{}, status="ko", respmsg="Permission denied"
|
||||
{}, status=403
|
||||
)
|
||||
self.assertEqual(resp, "Permission denied")
|
||||
|
||||
|
||||
class ResourceTestCase(ExtTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
"""Custom setUp method.
|
||||
|
||||
The 'limits' is manually loaded to ensure extra parameters
|
||||
provided by 'postfix_relay_domains' are properly received.
|
||||
"""
|
||||
super(ResourceTestCase, self).setUp()
|
||||
self.activate_extensions('limits')
|
||||
for tpl in LimitTemplates().templates:
|
||||
parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits')
|
||||
populate_database()
|
||||
|
||||
def _create_account(self, username, role='SimpleUsers', status=200):
|
||||
values = dict(
|
||||
username=username, first_name="Tester", last_name="Toto",
|
||||
password1="toto", password2="toto", role=role,
|
||||
quota_act=True,
|
||||
is_active=True, email=username, stepid='step2',
|
||||
)
|
||||
return self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.newaccount"),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _create_alias(self, email, rcpt='user@test.com', status=200):
|
||||
values = dict(
|
||||
email=email, recipients=rcpt, enabled=True
|
||||
)
|
||||
return self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.alias.newalias"),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _create_domain(self, name, status=200, withtpl=False):
|
||||
values = {
|
||||
"name": name, "quota": 100, "create_dom_admin": "no",
|
||||
"create_aliases": "no", "stepid": 'step2'
|
||||
}
|
||||
if withtpl:
|
||||
values['create_dom_admin'] = 'yes'
|
||||
values['dom_admin_username'] = 'admin'
|
||||
values['create_aliases'] = 'yes'
|
||||
return self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.newdomain"),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _domain_alias_operation(self, optype, domain, name, status=200):
|
||||
dom = Domain.objects.get(name=domain)
|
||||
values = {
|
||||
'name': dom.name, 'quota': dom.quota, 'enabled': dom.enabled,
|
||||
}
|
||||
aliases = [alias.name for alias in dom.domainalias_set.all()]
|
||||
if optype == 'add':
|
||||
aliases.append(name)
|
||||
else:
|
||||
aliases.remove(name)
|
||||
for cpt, alias in enumerate(aliases):
|
||||
fname = 'aliases' if not cpt else 'aliases_%d' % cpt
|
||||
values[fname] = alias
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.editdomain",
|
||||
args=[dom.id]),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _check_limit(self, name, curvalue, maxvalue):
|
||||
l = self.user.limitspool.get_limit('%s_limit' % name)
|
||||
self.assertEqual(l.curvalue, curvalue)
|
||||
self.assertEqual(l.maxvalue, maxvalue)
|
||||
|
||||
|
||||
class DomainAdminTestCase(ResourceTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DomainAdminTestCase, self).setUp()
|
||||
self.user = User.objects.get(username='admin@test.com')
|
||||
self.user.limitspool.set_maxvalue('mailboxes_limit', 2)
|
||||
self.user.limitspool.set_maxvalue('mailbox_aliases_limit', 2)
|
||||
self.clt.logout()
|
||||
self.clt.login(username='admin@test.com', password='toto')
|
||||
|
||||
def test_mailboxes_limit(self):
|
||||
self._create_account('tester1@test.com')
|
||||
self._check_limit('mailboxes', 1, 2)
|
||||
self._create_account('tester2@test.com')
|
||||
self._check_limit('mailboxes', 2, 2)
|
||||
resp = self._create_account('tester3@test.com', status=403)
|
||||
self._check_limit('mailboxes', 2, 2)
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.admin.views.identity.delaccount',
|
||||
args=[User.objects.get(username='tester2@test.com').id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('mailboxes', 1, 2)
|
||||
|
||||
def test_aliases_limit(self):
|
||||
self._create_alias('alias1@test.com')
|
||||
self._check_limit('mailbox_aliases', 1, 2)
|
||||
self._create_alias('alias2@test.com')
|
||||
self._check_limit('mailbox_aliases', 2, 2)
|
||||
resp = self._create_alias('alias3@test.com', status=403)
|
||||
self._check_limit('mailbox_aliases', 2, 2)
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.admin.views.alias.delalias') + '?selection=%d' \
|
||||
% Alias.objects.get(address='alias2', domain__name='test.com').id,
|
||||
{}
|
||||
)
|
||||
self._check_limit('mailbox_aliases', 1, 2)
|
||||
|
||||
def test_aliases_limit_through_account_form(self):
|
||||
user = User.objects.get(username='user@test.com')
|
||||
values = dict(
|
||||
username=user.username, role=user.group,
|
||||
is_active=user.is_active, email=user.email, quota_act=True,
|
||||
aliases="alias1@test.com", aliases_1="alias2@test.com"
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
)
|
||||
Alias.objects.get(address='alias1', domain__name='test.com')
|
||||
self._check_limit('mailbox_aliases', 2, 2)
|
||||
|
||||
|
||||
class ResellerTestCase(ResourceTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ResellerTestCase, self).setUp()
|
||||
self.user = UserFactory.create(
|
||||
username='reseller', groups=('Resellers',)
|
||||
)
|
||||
self.clt.logout()
|
||||
self.clt.login(username='reseller', password='toto')
|
||||
|
||||
def test_domains_limit(self):
|
||||
self._create_domain('domain1.tld')
|
||||
self._check_limit('domains', 1, 2)
|
||||
self._create_domain('domain2.tld')
|
||||
self._check_limit('domains', 2, 2)
|
||||
resp = self._create_domain('domain3.tld', 403)
|
||||
self._check_limit('domains', 2, 2)
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.admin.views.domain.deldomain',
|
||||
args=[Domain.objects.get(name='domain2.tld').id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('domains', 1, 2)
|
||||
|
||||
def test_domain_aliases_limit(self):
|
||||
self._create_domain('pouet.com')
|
||||
self._domain_alias_operation('add', 'pouet.com', 'domain_alias1.tld')
|
||||
self._check_limit('domain_aliases', 1, 2)
|
||||
self._domain_alias_operation('add', 'pouet.com', 'domain_alias2.tld')
|
||||
self._check_limit('domain_aliases', 2, 2)
|
||||
resp = self._domain_alias_operation(
|
||||
'add', 'pouet.com', 'domain_alias3.tld', 403
|
||||
)
|
||||
self._check_limit('domain_aliases', 2, 2)
|
||||
self._domain_alias_operation('delete', 'pouet.com', 'domain_alias2.tld')
|
||||
self._check_limit('domain_aliases', 1, 2)
|
||||
|
||||
def test_domain_admins_limit(self):
|
||||
self._create_domain('domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
self._check_limit('domain_admins', 1, 2)
|
||||
self._create_account('admin2@domain.tld', role='DomainAdmins')
|
||||
self._check_limit('domain_admins', 2, 2)
|
||||
resp = self._create_account('admin3@domain.tld', role='DomainAdmins', status=400)
|
||||
self.assertEqual(
|
||||
resp['form_errors']['role'][0],
|
||||
'Select a valid choice. DomainAdmins is not one of the available choices.'
|
||||
)
|
||||
self._check_limit('domain_admins', 2, 2)
|
||||
|
||||
self.user.limitspool.set_maxvalue('mailboxes_limit', 3)
|
||||
self._create_account('user1@domain.tld')
|
||||
user = User.objects.get(username='user1@domain.tld')
|
||||
values = {
|
||||
'username': user.username, 'role': 'DomainAdmins', 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email
|
||||
}
|
||||
resp = self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values, status=400
|
||||
)
|
||||
self.assertEqual(
|
||||
resp['form_errors']['role'][0],
|
||||
'Select a valid choice. DomainAdmins is not one of the available choices.'
|
||||
)
|
||||
self._check_limit('domain_admins', 2, 2)
|
||||
|
||||
def test_domain_admin_resource_are_empty(self):
|
||||
self._create_domain('domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
domadmin = User.objects.get(username='admin1@domain.tld')
|
||||
for l in ['mailboxes', 'mailbox_aliases']:
|
||||
self.assertEqual(
|
||||
domadmin.limitspool.get_limit('%s_limit' % l).maxvalue, 0
|
||||
)
|
||||
|
||||
def test_domain_admins_limit_from_domain_tpl(self):
|
||||
self.user.limitspool.set_maxvalue('domains_limit', 3)
|
||||
self._create_domain('domain1.tld', withtpl=True)
|
||||
self._create_domain('domain2.tld', withtpl=True)
|
||||
self._check_limit('domain_admins', 2, 2)
|
||||
self._check_limit('domains', 2, 3)
|
||||
resp = self._create_domain('domain3.tld', status=200, withtpl=True)
|
||||
self._check_limit('domain_admins', 2, 2)
|
||||
self._check_limit('domains', 3, 3)
|
||||
|
||||
def test_reseller_deletes_domain(self):
|
||||
"""Check if all resources are restored after the deletion.
|
||||
"""
|
||||
self._create_domain('domain.tld', withtpl=True)
|
||||
dom = Domain.objects.get(name="domain.tld")
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.domain.deldomain",
|
||||
args=[dom.id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('domains', 0, 2)
|
||||
self._check_limit('domain_admins', 1, 2)
|
||||
self._check_limit('mailboxes', 0, 2)
|
||||
self._check_limit('mailbox_aliases', 0, 2)
|
||||
|
||||
def test_sadmin_removes_ownership(self):
|
||||
self._create_domain('domain.tld', withtpl=True)
|
||||
dom = Domain.objects.get(name="domain.tld")
|
||||
self.clt.logout()
|
||||
self.clt.login(username='admin', password='password')
|
||||
self.ajax_get(
|
||||
reverse(
|
||||
'modoboa.extensions.admin.views.identity.remove_permission'
|
||||
) + '?domid=%d&daid=%d' % (dom.id, self.user.id)
|
||||
, {}
|
||||
)
|
||||
self._check_limit('domains', 0, 2)
|
||||
self._check_limit('domain_admins', 0, 2)
|
||||
self._check_limit('mailboxes', 0, 2)
|
||||
self._check_limit('mailbox_aliases', 0, 2)
|
||||
|
||||
def test_allocate_from_pool(self):
|
||||
self._create_domain('domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
user = User.objects.get(username='admin1@domain.tld')
|
||||
|
||||
# Give 1 mailbox and 2 aliases to the admin -> should work
|
||||
values = {
|
||||
'username': user.username, 'role': user.group, 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email,
|
||||
'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
)
|
||||
self._check_limit('mailboxes', 1, 1)
|
||||
self._check_limit('mailbox_aliases', 0, 0)
|
||||
|
||||
# Delete the admin -> resources should go back to the
|
||||
# reseller's pool
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount",
|
||||
args=[user.id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('mailboxes', 0, 2)
|
||||
self._check_limit('mailbox_aliases', 0, 2)
|
||||
|
||||
def test_restore_resources(self):
|
||||
self._create_domain('domain.tld')
|
||||
dom = Domain.objects.get(name='domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
user = User.objects.get(username='admin1@domain.tld')
|
||||
values = {
|
||||
'username': user.username, 'role': user.group, 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email,
|
||||
'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
)
|
||||
dom.add_admin(user)
|
||||
self.clt.logout()
|
||||
self.clt.login(username='admin1@domain.tld', password='toto')
|
||||
self._create_account('user1@domain.tld')
|
||||
self._create_alias('alias1@domain.tld', 'user1@domain.tld')
|
||||
self._create_alias('alias2@domain.tld', 'user1@domain.tld')
|
||||
self.clt.logout()
|
||||
self.clt.login(username='reseller', password='toto')
|
||||
# Delete the admin -> resources should go back to the
|
||||
# reseller's pool
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.delaccount",
|
||||
args=[user.id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('mailboxes', 1, 2)
|
||||
self._check_limit('mailbox_aliases', 2, 2)
|
||||
|
||||
def test_change_role(self):
|
||||
self._create_domain('domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
user = User.objects.get(username='admin1@domain.tld')
|
||||
|
||||
# Give 1 mailbox and 2 aliases to the admin -> should work
|
||||
values = {
|
||||
'username': user.username, 'role': user.group, 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email,
|
||||
'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
)
|
||||
self._check_limit('mailboxes', 1, 1)
|
||||
self._check_limit('mailbox_aliases', 0, 0)
|
||||
|
||||
# Change admin role to SimpleUser -> resources should go back
|
||||
# to the reseller.
|
||||
values = {
|
||||
'username': user.username, 'role': 'SimpleUsers', 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email,
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values
|
||||
)
|
||||
self._check_limit('mailboxes', 1, 2)
|
||||
self._check_limit('mailbox_aliases', 0, 2)
|
||||
|
||||
|
||||
def test_allocate_too_much(self):
|
||||
self._create_domain('domain.tld')
|
||||
self._create_account('admin1@domain.tld', role='DomainAdmins')
|
||||
user = User.objects.get(username='admin1@domain.tld')
|
||||
|
||||
# Give 2 mailboxes and 3 aliases to the admin -> should fail.
|
||||
values = {
|
||||
'username': user.username, 'role': user.group, 'quota_act': True,
|
||||
'is_active': user.is_active, 'email': user.email,
|
||||
'mailboxes_limit': 2, 'mailbox_aliases_limit': 3
|
||||
}
|
||||
resp = self.ajax_post(
|
||||
reverse("modoboa.extensions.admin.views.identity.editaccount",
|
||||
args=[user.id]),
|
||||
values, 424
|
||||
)
|
||||
self.assertEqual(resp, 'Not enough resources')
|
||||
self._check_limit('mailboxes', 1, 2)
|
||||
self._check_limit('mailbox_aliases', 0, 2)
|
||||
|
||||
@@ -6,13 +6,20 @@ This module provides a way to integrate Modoboa auto-reply
|
||||
functionality into Postfix.
|
||||
|
||||
"""
|
||||
import sys
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
from .models import Transport, Alias
|
||||
from modoboa.extensions.postfix_autoreply.models import (
|
||||
Transport, Alias
|
||||
)
|
||||
|
||||
|
||||
class PostfixAutoreply(ModoExtension):
|
||||
"""
|
||||
Auto-reply (vacation) functionality using Postfix.
|
||||
|
||||
"""
|
||||
name = "postfix_autoreply"
|
||||
label = "Postfix autoreply"
|
||||
version = "1.0"
|
||||
@@ -25,7 +32,7 @@ class PostfixAutoreply(ModoExtension):
|
||||
try:
|
||||
Transport.objects.get(domain="autoreply.%s" % dom.name)
|
||||
except Transport.DoesNotExist:
|
||||
onCreateDomain(None, dom)
|
||||
onDomainCreated(None, dom)
|
||||
else:
|
||||
continue
|
||||
|
||||
@@ -33,19 +40,17 @@ class PostfixAutoreply(ModoExtension):
|
||||
try:
|
||||
Alias.objects.get(full_address=mb.full_address)
|
||||
except Alias.DoesNotExist:
|
||||
onCreateMailbox(None, mb)
|
||||
onMailboxCreated(None, mb)
|
||||
|
||||
def load(self):
|
||||
from modoboa.extensions.postfix_autoreply.app_settings import ParametersForm
|
||||
parameters.register(ParametersForm, ugettext_lazy("Automatic replies"))
|
||||
from modoboa.extensions.postfix_autoreply import general_callbacks
|
||||
if 'modoboa.extensions.postfix_autoreply.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("CreateDomain", onCreateDomain)
|
||||
events.unregister("DeleteDomain", onDeleteDomain)
|
||||
events.unregister("CreateMailbox", onCreateMailbox)
|
||||
events.unregister("DeleteMailbox", onDeleteMailbox)
|
||||
events.unregister("ModifyMailbox", onModifyMailbox)
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(PostfixAutoreply)
|
||||
@@ -55,87 +60,3 @@ exts_pool.register_extension(PostfixAutoreply)
|
||||
def extra_routes():
|
||||
return [(r'^user/autoreply/$',
|
||||
'modoboa.extensions.postfix_autoreply.views.autoreply'), ]
|
||||
|
||||
|
||||
@events.observe("ExtraUprefsJS")
|
||||
def extra_js(user):
|
||||
return ["""function autoreply_cb() {
|
||||
$('#id_untildate').datepicker({format: 'yyyy-mm-dd', language: '%s'});
|
||||
}
|
||||
""" % parameters.get_user(user, "LANG", app="core")
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "uprefs_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "autoreply",
|
||||
"class": "ajaxlink",
|
||||
"url": "autoreply/",
|
||||
"label": ugettext_lazy("Auto-reply message")}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("CreateDomain")
|
||||
def onCreateDomain(user, domain):
|
||||
transport = Transport()
|
||||
transport.domain = "autoreply.%s" % domain.name
|
||||
transport.method = "autoreply:"
|
||||
transport.save()
|
||||
|
||||
|
||||
@events.observe("DomainModified")
|
||||
def onDomainModified(domain):
|
||||
if domain.oldname == domain.name:
|
||||
return
|
||||
Transport.objects.filter(domain="autoreply.%s" % domain.oldname) \
|
||||
.update(domain="autoreply.%s" % domain.name)
|
||||
for al in Alias.objects.filter(full_address__contains="@%s" % domain.oldname):
|
||||
new_address = al.full_address.replace("@%s" % domain.oldname, "@%s" % domain.name)
|
||||
al.full_address = new_address
|
||||
al.autoreply_address = "%s@autoreply.%s" % (new_address, domain.name)
|
||||
al.save()
|
||||
|
||||
|
||||
@events.observe("DeleteDomain")
|
||||
def onDeleteDomain(domain):
|
||||
Transport.objects.filter(domain="autoreply.%s" % domain.name).delete()
|
||||
|
||||
|
||||
@events.observe("CreateMailbox")
|
||||
def onCreateMailbox(user, mailbox):
|
||||
alias = Alias()
|
||||
alias.full_address = mailbox.full_address
|
||||
alias.autoreply_address = \
|
||||
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
|
||||
alias.save()
|
||||
|
||||
|
||||
@events.observe("DeleteMailbox")
|
||||
def onDeleteMailbox(mailboxes):
|
||||
from modoboa.extensions.admin.models import Mailbox
|
||||
|
||||
if isinstance(mailboxes, Mailbox):
|
||||
mailboxes = [mailboxes]
|
||||
for mailbox in mailboxes:
|
||||
try:
|
||||
alias = Alias.objects.get(full_address=mailbox.full_address)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
alias.delete()
|
||||
|
||||
|
||||
@events.observe("ModifyMailbox")
|
||||
def onModifyMailbox(mailbox, oldmailbox):
|
||||
if oldmailbox.full_address == mailbox.full_address:
|
||||
return
|
||||
alias = Alias.objects.get(full_address=oldmailbox.full_address)
|
||||
alias.full_address = mailbox.full_address
|
||||
alias.autoreply_address = \
|
||||
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
|
||||
alias.save()
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions.postfix_autoreply.models import Transport, Alias
|
||||
|
||||
|
||||
@events.observe("ExtraUprefsJS")
|
||||
def extra_js(user):
|
||||
return ["""function autoreply_cb() {
|
||||
$('#id_untildate').datepicker({format: 'yyyy-mm-dd', language: '%s'});
|
||||
}
|
||||
""" % parameters.get_user(user, "LANG", app="core")
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "uprefs_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "autoreply",
|
||||
"class": "ajaxlink",
|
||||
"url": "autoreply/",
|
||||
"label": ugettext_lazy("Auto-reply message")}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("DomainCreated")
|
||||
def onDomainCreated(user, domain):
|
||||
transport = Transport()
|
||||
transport.domain = "autoreply.%s" % domain.name
|
||||
transport.method = "autoreply:"
|
||||
transport.save()
|
||||
|
||||
|
||||
@events.observe("DomainModified")
|
||||
def onDomainModified(domain):
|
||||
if domain.oldname == domain.name:
|
||||
return
|
||||
Transport.objects.filter(domain="autoreply.%s" % domain.oldname) \
|
||||
.update(domain="autoreply.%s" % domain.name)
|
||||
for al in Alias.objects.filter(full_address__contains="@%s" % domain.oldname):
|
||||
new_address = al.full_address.replace("@%s" % domain.oldname, "@%s" % domain.name)
|
||||
al.full_address = new_address
|
||||
al.autoreply_address = "%s@autoreply.%s" % (new_address, domain.name)
|
||||
al.save()
|
||||
|
||||
|
||||
@events.observe("DomainDeleted")
|
||||
def onDomainDeleted(domain):
|
||||
Transport.objects.filter(domain="autoreply.%s" % domain.name).delete()
|
||||
|
||||
|
||||
@events.observe("MailboxCreated")
|
||||
def onMailboxCreated(user, mailbox):
|
||||
alias = Alias()
|
||||
alias.full_address = mailbox.full_address
|
||||
alias.autoreply_address = \
|
||||
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
|
||||
alias.save()
|
||||
|
||||
|
||||
@events.observe("MailboxDeleted")
|
||||
def onMailboxDeleted(mailboxes):
|
||||
from modoboa.extensions.admin.models import Mailbox
|
||||
|
||||
if isinstance(mailboxes, Mailbox):
|
||||
mailboxes = [mailboxes]
|
||||
for mailbox in mailboxes:
|
||||
try:
|
||||
alias = Alias.objects.get(full_address=mailbox.full_address)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
alias.delete()
|
||||
|
||||
|
||||
@events.observe("MailboxModified")
|
||||
def onModifyMailbox(mailbox, oldmailbox):
|
||||
if oldmailbox.full_address == mailbox.full_address:
|
||||
return
|
||||
alias = Alias.objects.get(full_address=oldmailbox.full_address)
|
||||
alias.full_address = mailbox.full_address
|
||||
alias.autoreply_address = \
|
||||
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
|
||||
alias.save()
|
||||
@@ -3,7 +3,7 @@ from datetime import date
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.webutils import (
|
||||
ajax_simple_response, _render_to_string
|
||||
render_to_json_response, _render_to_string
|
||||
)
|
||||
from modoboa.extensions.admin.lib import needs_mailbox
|
||||
from modoboa.extensions.admin.models import Mailbox
|
||||
@@ -29,24 +29,20 @@ def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
|
||||
arm.untildate = form.cleaned_data["untildate"]
|
||||
arm.mbox = mb
|
||||
arm.save()
|
||||
return ajax_simple_response(dict(
|
||||
status="ok",
|
||||
respmsg=_("Auto reply message updated successfully.")
|
||||
))
|
||||
return render_to_json_response(
|
||||
_("Auto reply message updated successfully.")
|
||||
)
|
||||
|
||||
return ajax_simple_response({
|
||||
"status": "ko",
|
||||
"errors": form.errors,
|
||||
"onload_cb": "autoreply_cb"
|
||||
})
|
||||
return render_to_json_response(
|
||||
{"form_errors": form.errors}, status=400
|
||||
)
|
||||
|
||||
form = ARmessageForm(instance=arm)
|
||||
if arm is not None:
|
||||
form.fields['untildate'].initial = arm.untildate
|
||||
else:
|
||||
form.fields['untildate'].initial = date.today()
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, tplname, {"form": form}),
|
||||
"onload_cb": "autoreply_cb"
|
||||
})
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# coding: utf-8
|
||||
import sys
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
from .models import RelayDomain, RelayDomainAlias, Service
|
||||
|
||||
extension_events = [
|
||||
"RelayDomainCreated",
|
||||
"RelayDomainDeleted",
|
||||
"RelayDomainModified",
|
||||
"RelayDomainAliasCreated",
|
||||
"RelayDomainAliasDeleted",
|
||||
"ExtraRelayDomainForm",
|
||||
"FillRelayDomainInstances"
|
||||
]
|
||||
|
||||
|
||||
def init_limits_dependant_features():
|
||||
"""Populate limits tables.
|
||||
|
||||
Update the pool of every defined user (to add new limits) and
|
||||
update the *Resellers* group with new permissions.
|
||||
"""
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.limits import controls
|
||||
|
||||
for u in User.objects.all():
|
||||
controls.create_pool(u)
|
||||
grp = Group.objects.get(name='Resellers')
|
||||
for model in [RelayDomain, RelayDomainAlias, Service]:
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
name = model.__name__.lower()
|
||||
for action in ['add', 'change', 'delete']:
|
||||
grp.permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type=ct, codename='%s_%s' % (action, name)
|
||||
)
|
||||
)
|
||||
grp.save()
|
||||
|
||||
|
||||
def init_amavis_dependant_features():
|
||||
"""Populate amavis database.
|
||||
|
||||
We create records for *users* and *policy* tables for each defined
|
||||
relay domain or relay domain alias.
|
||||
"""
|
||||
from modoboa.extensions.amavis.models import Users
|
||||
from modoboa.extensions.amavis.lib import (
|
||||
create_user_and_policy, create_user_and_use_policy
|
||||
)
|
||||
|
||||
for rdom in RelayDomain.objects.all():
|
||||
try:
|
||||
Users.objects.get(email="@%s" % rdom.name)
|
||||
except Users.DoesNotExist:
|
||||
create_user_and_policy(rdom.name)
|
||||
for rdomalias in rdom.relaydomainalias_set.all():
|
||||
try:
|
||||
Users.objects.get(email='@%s' % rdomalias.name)
|
||||
except Users.DoesNotExist:
|
||||
create_user_and_use_policy(rdomalias.name, rdom.name)
|
||||
|
||||
|
||||
class PostfixRelayDomains(ModoExtension):
|
||||
name = "postfix_relay_domains"
|
||||
label = "Postfix relay domains"
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Relay domains support for Postfix")
|
||||
|
||||
def init(self):
|
||||
"""Initialisation method.
|
||||
|
||||
Only run once, when the extension is enabled. Populates the
|
||||
service table with default entries.
|
||||
"""
|
||||
for service_name in ['relay', 'smtp']:
|
||||
Service.objects.get_or_create(name=service_name)
|
||||
if exts_pool.is_extension_enabled('limits'):
|
||||
init_limits_dependant_features()
|
||||
|
||||
def load(self):
|
||||
from .app_settings import AdminParametersForm
|
||||
|
||||
parameters.register(
|
||||
AdminParametersForm, ugettext_lazy("Relay domains")
|
||||
)
|
||||
events.declare(extension_events)
|
||||
from modoboa.extensions.postfix_relay_domains import general_callbacks
|
||||
if 'modoboa.extensions.postfix_relay_domains.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
if exts_pool.is_extension_enabled('limits'):
|
||||
import limits_callbacks
|
||||
if 'modoboa.extensions.postfix_relay_domains.limits_callbacks' in sys.modules:
|
||||
reload(limits_callbacks)
|
||||
if exts_pool.is_extension_enabled('amavis'):
|
||||
import amavis_callbacks
|
||||
if 'modoboa.extensions.postfix_relay_domains.amavis_callbacks' in sys.modules:
|
||||
reload(amavis_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(PostfixRelayDomains)
|
||||
@@ -0,0 +1,30 @@
|
||||
from modoboa.lib import events
|
||||
from modoboa.extensions.amavis.lib import (
|
||||
create_user_and_policy, update_user_and_policy, delete_user_and_policy,
|
||||
create_user_and_use_policy, delete_user
|
||||
)
|
||||
|
||||
|
||||
@events.observe("RelayDomainCreated")
|
||||
def on_relay_domain_created(user, rdomain):
|
||||
create_user_and_policy(rdomain.name)
|
||||
|
||||
|
||||
@events.observe("RelayDomainModified")
|
||||
def on_relay_domain_modified(rdomain):
|
||||
update_user_and_policy(rdomain.oldname, rdomain.name)
|
||||
|
||||
|
||||
@events.observe("RelayDomainDeleted")
|
||||
def on_relay_domain_deleted(rdomain):
|
||||
delete_user_and_policy(rdomain.name)
|
||||
|
||||
|
||||
@events.observe("RelayDomainAliasCreated")
|
||||
def on_relay_domain_alias_created(user, rdomainalias):
|
||||
create_user_and_use_policy(rdomainalias.name, rdomainalias.target.name)
|
||||
|
||||
|
||||
@events.observe("RelayDomainAliasDeleted")
|
||||
def on_relay_domain_alias_deleted(rdomainalias):
|
||||
delete_user(rdomainalias.name)
|
||||
@@ -0,0 +1,13 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import parameters
|
||||
|
||||
|
||||
class AdminParametersForm(parameters.AdminParametersForm):
|
||||
app = "postfix_relay_domains"
|
||||
|
||||
master_cf_path = forms.CharField(
|
||||
label=ugettext_lazy("Postfix's master.cf path"),
|
||||
initial="/etc/postfix/master.cf",
|
||||
help_text=ugettext_lazy('Path to the master.cf configuration file')
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
import factory
|
||||
from modoboa.core.factories import PermissionFactory
|
||||
from . import models
|
||||
|
||||
|
||||
class ServiceFactory(factory.DjangoModelFactory):
|
||||
FACTORY_FOR = models.Service
|
||||
FACTORY_DJANGO_GET_OR_CREATE = ('name', )
|
||||
|
||||
name = 'dummy'
|
||||
|
||||
|
||||
class RelayDomainFactory(PermissionFactory):
|
||||
FACTORY_FOR = models.RelayDomain
|
||||
|
||||
target_host = 'external.host.tld'
|
||||
service = factory.SubFactory(ServiceFactory)
|
||||
|
||||
|
||||
class RelayDomainAliasFactory(PermissionFactory):
|
||||
FACTORY_FOR = models.RelayDomainAlias
|
||||
|
||||
enabled = True
|
||||
@@ -0,0 +1,157 @@
|
||||
from django import forms
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.formutils import DynamicForm, DomainNameField, TabForms
|
||||
from modoboa.extensions.admin.models import Domain, DomainAlias
|
||||
from .models import RelayDomain, RelayDomainAlias
|
||||
|
||||
|
||||
class RelayDomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
aliases = DomainNameField(
|
||||
label=ugettext_lazy("Alias(es)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Alias(es) of this relay domain. Indicate only one name per input"
|
||||
", press ENTER to add a new input."
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RelayDomain
|
||||
exclude = ['dates']
|
||||
widgets = {
|
||||
'service': forms.Select(attrs={'class': 'span2'})
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RelayDomainFormGeneral, self).__init__(*args, **kwargs)
|
||||
if args and isinstance(args[0], QueryDict):
|
||||
self._load_from_qdict(args[0], "aliases", DomainNameField)
|
||||
elif 'instance' in kwargs:
|
||||
rd = kwargs['instance']
|
||||
for pos, rdalias in enumerate(rd.relaydomainalias_set.all()):
|
||||
name = "aliases_%d" % (pos + 1)
|
||||
self._create_field(forms.CharField, name, rdalias.name, 3)
|
||||
|
||||
def clean(self):
|
||||
"""Custom fields validaton.
|
||||
|
||||
We want to prevent duplicate names between domains, relay
|
||||
domains, domain aliases and relay domain aliases.
|
||||
|
||||
The validation way is not very smart...
|
||||
"""
|
||||
super(RelayDomainFormGeneral, self).clean()
|
||||
if self._errors:
|
||||
raise forms.ValidationError(self._errors)
|
||||
cleaned_data = self.cleaned_data
|
||||
for dtype, label in [(Domain, _('domain')),
|
||||
(DomainAlias, _('domain alias')),
|
||||
(RelayDomainAlias, _('relay domain alias'))]:
|
||||
try:
|
||||
dtype.objects.get(name=cleaned_data['name'])
|
||||
except dtype.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self._errors["name"] = self.error_class(
|
||||
[_("A %s with this name already exists" % label)]
|
||||
)
|
||||
del cleaned_data["name"]
|
||||
break
|
||||
|
||||
for k in cleaned_data.keys():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if not cleaned_data[k]:
|
||||
del cleaned_data[k]
|
||||
continue
|
||||
for dtype, name in [(RelayDomain, _('relay domain')),
|
||||
(DomainAlias, _('domain alias')),
|
||||
(Domain, _('domain'))]:
|
||||
try:
|
||||
dtype.objects.get(name=cleaned_data[k])
|
||||
except dtype.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self._errors[k] = self.error_class(
|
||||
[_("A %s with this name already exists" % name)]
|
||||
)
|
||||
del cleaned_data[k]
|
||||
break
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def save(self, user, commit=True, rdomalias_post_create=False):
|
||||
"""Custom save method.
|
||||
|
||||
As relay domain aliases are defined using the same form as
|
||||
relay domains, we need to save them manually.
|
||||
|
||||
:param ``User`` user: connected user
|
||||
"""
|
||||
rd = super(RelayDomainFormGeneral, self).save(commit=False)
|
||||
if commit:
|
||||
rd.save()
|
||||
aliases = []
|
||||
for k, v in self.cleaned_data.iteritems():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if v in ["", None]:
|
||||
continue
|
||||
aliases.append(v)
|
||||
for rdalias in rd.relaydomainalias_set.all():
|
||||
if not rdalias.name in aliases:
|
||||
rdalias.delete()
|
||||
else:
|
||||
aliases.remove(rdalias.name)
|
||||
if aliases:
|
||||
events.raiseEvent(
|
||||
"CanCreate", user, "relay_domain_aliases", len(aliases)
|
||||
)
|
||||
for alias in aliases:
|
||||
try:
|
||||
rd.relaydomainalias_set.get(name=alias)
|
||||
except RelayDomainAlias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
al = RelayDomainAlias(
|
||||
name=alias, target=rd, enabled=rd.enabled
|
||||
)
|
||||
al.save(creator=user) \
|
||||
if rdomalias_post_create else al.save()
|
||||
return rd
|
||||
|
||||
|
||||
class RelayDomainForm(TabForms):
|
||||
"""Specific edition form for relay domains.
|
||||
|
||||
We use a *tabs* compatible form because extensions can add their
|
||||
own tab. (ex: amavis)
|
||||
"""
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self.user = user
|
||||
self.forms = []
|
||||
if user.has_perm("postfix_relay_domains.change_relaydomain"):
|
||||
self.forms.append({
|
||||
'id': 'general', 'title': _("General"),
|
||||
'formtpl': 'postfix_relay_domains/relaydomain_form.html',
|
||||
'cls': RelayDomainFormGeneral, 'mandatory': True
|
||||
})
|
||||
|
||||
cbargs = [user]
|
||||
if "instances" in kwargs:
|
||||
cbargs += [kwargs["instances"]["general"]]
|
||||
self.forms += events.raiseQueryEvent("ExtraRelayDomainForm", *cbargs)
|
||||
super(RelayDomainForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, user):
|
||||
"""Custom save method
|
||||
|
||||
As forms interact with each other, it is easier to make custom
|
||||
code to save them.
|
||||
"""
|
||||
self.forms[0]['instance'].save(user, rdomalias_post_create=True)
|
||||
for f in self.forms[1:]:
|
||||
f["instance"].save(user)
|
||||
@@ -0,0 +1,143 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import Template, Context
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.db.models import Q
|
||||
from modoboa.lib import events
|
||||
from .models import RelayDomain, RelayDomainAlias
|
||||
|
||||
|
||||
@events.observe('GetStaticContent')
|
||||
def static_content(caller, user):
|
||||
if caller != 'domains':
|
||||
return []
|
||||
|
||||
t = Template("""<script src="{{ STATIC_URL }}postfix_relay_domains/js/relay_domains.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var rdomain;
|
||||
$(document).ready(function() {
|
||||
rdomain = new RelayDomains({});
|
||||
});
|
||||
</script>
|
||||
""")
|
||||
return [t.render(Context({'STATIC_URL': settings.STATIC_URL}))]
|
||||
|
||||
|
||||
@events.observe('ExtraDomainFilters')
|
||||
def extra_domain_filters():
|
||||
return ['srvfilter']
|
||||
|
||||
|
||||
@events.observe('ExtraDomainMenuEntries')
|
||||
def extra_domain_menu_entries(user):
|
||||
return [
|
||||
{"name": "newrelaydomain",
|
||||
"label": ugettext_lazy("Add relay domain"),
|
||||
"img": "icon-plus",
|
||||
"modal": True,
|
||||
"modalcb": "rdomain.domainform_cb",
|
||||
"url": reverse(
|
||||
"modoboa.extensions.postfix_relay_domains.views.create"
|
||||
)},
|
||||
]
|
||||
|
||||
|
||||
@events.observe('ExtraDomainEntries')
|
||||
def extra_domain_entries(user, domfilter, searchquery, **extrafilters):
|
||||
if domfilter is not None and domfilter and domfilter != 'relaydomain':
|
||||
return []
|
||||
relay_domains = RelayDomain.objects.get_for_admin(user)
|
||||
if searchquery is not None:
|
||||
q = Q(name__contains=searchquery)
|
||||
q |= Q(relaydomainalias__name__contains=searchquery)
|
||||
relay_domains = relay_domains.filter(q).distinct()
|
||||
if 'srvfilter' in extrafilters and extrafilters['srvfilter']:
|
||||
relay_domains = relay_domains.filter(
|
||||
Q(service__name=extrafilters['srvfilter'])
|
||||
)
|
||||
return relay_domains
|
||||
|
||||
|
||||
@events.observe('GetDomainModifyLink')
|
||||
def rdomain_modify_link(domain):
|
||||
if not isinstance(domain, RelayDomain):
|
||||
return {}
|
||||
return {
|
||||
'url': reverse(
|
||||
'modoboa.extensions.postfix_relay_domains.views.edit',
|
||||
args=[domain.id]
|
||||
),
|
||||
'modalcb': 'rdomain.domainform_cb'
|
||||
}
|
||||
|
||||
|
||||
@events.observe('GetDomainActions')
|
||||
def rdomain_actions(user, domain):
|
||||
if not isinstance(domain, RelayDomain):
|
||||
return []
|
||||
if not user.has_perm("postfix_relay_domains.delete_relaydomain"):
|
||||
return []
|
||||
return [{
|
||||
"name": "delrelaydomain",
|
||||
"url": reverse("modoboa.extensions.postfix_relay_domains.views.delete",
|
||||
args=[domain.id]),
|
||||
"title": ugettext_lazy("Delete %s?" % domain.name),
|
||||
"img": "icon-trash"
|
||||
}]
|
||||
|
||||
|
||||
@events.observe('CheckDomainName')
|
||||
def check_domain_name():
|
||||
return [
|
||||
(RelayDomain, ugettext_lazy('relay domain')),
|
||||
(RelayDomainAlias, ugettext_lazy('relay domain alias'))
|
||||
]
|
||||
|
||||
|
||||
@events.observe('GetExtraLimitTemplates')
|
||||
def extra_limit_templates():
|
||||
return [
|
||||
('relay_domains_limit', ugettext_lazy('Relay domains'),
|
||||
ugettext_lazy('Maximum number of relay domains this user can create'),
|
||||
'Resellers'),
|
||||
("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"),
|
||||
ugettext_lazy('Maximum number of relay domain aliases this user can create'),
|
||||
'Resellers'),
|
||||
]
|
||||
|
||||
|
||||
@events.observe('ExtraDomainImportHelp')
|
||||
def extra_domain_import_help():
|
||||
return [ugettext_lazy("""
|
||||
<li><em>relaydomain; name; target host; service; enabled; verify recipients</em></li>
|
||||
<li><em>relaydomainalias; name; target; enabled</em></li>
|
||||
""")]
|
||||
|
||||
|
||||
@events.observe('ImportObject')
|
||||
def get_import_func(objtype):
|
||||
from .lib import import_relaydomain, import_relaydomainalias
|
||||
|
||||
if objtype == 'relaydomain':
|
||||
return [import_relaydomain]
|
||||
if objtype == 'relaydomainalias':
|
||||
return [import_relaydomainalias]
|
||||
return []
|
||||
|
||||
|
||||
@events.observe('ExtEnabled')
|
||||
def extension_enabled(extension):
|
||||
"""ExtEnabled event listener.
|
||||
|
||||
Usefull when *limits* or *amavis* extensions are activated after
|
||||
*postfix_relay_domains*.
|
||||
|
||||
:param extension: enabled extension
|
||||
"""
|
||||
from modoboa.extensions.postfix_relay_domains import (
|
||||
init_limits_dependant_features, init_amavis_dependant_features
|
||||
)
|
||||
if extension.name == 'limits':
|
||||
init_limits_dependant_features()
|
||||
if extension.name == 'amavis':
|
||||
init_amavis_dependant_features()
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.db import transaction
|
||||
from .models import RelayDomain, RelayDomainAlias
|
||||
|
||||
|
||||
@transaction.commit_on_success
|
||||
def import_relaydomain(user, row, formopts):
|
||||
"""Specific code for relay domains import"""
|
||||
RelayDomain().from_csv(user, row)
|
||||
|
||||
|
||||
@transaction.commit_on_success
|
||||
def import_relaydomainalias(user, row, formopts):
|
||||
"""Specific code for relay domain aliases import"""
|
||||
RelayDomainAlias().from_csv(user, row)
|
||||
@@ -0,0 +1,58 @@
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.permissions import get_object_owner
|
||||
from modoboa.extensions.limits.lib import inc_limit_usage, dec_limit_usage
|
||||
from .models import RelayDomainAlias
|
||||
|
||||
|
||||
@events.observe('GetExtraParameters')
|
||||
def extra_parameters(app, level):
|
||||
from django import forms
|
||||
|
||||
if app != 'limits' or level != 'A':
|
||||
return {}
|
||||
return {
|
||||
'deflt_relay_domains_limit': forms.IntegerField(
|
||||
label=ugettext_lazy("Relay domains"),
|
||||
initial=0,
|
||||
help_text=ugettext_lazy(
|
||||
"Maximum number of allowed relay domains for a new administrator"
|
||||
),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "span1"})
|
||||
),
|
||||
'deflt_relay_domain_aliases_limit': forms.IntegerField(
|
||||
label=ugettext_lazy("Relay domain aliases"),
|
||||
initial=0,
|
||||
help_text=ugettext_lazy(
|
||||
"Maximum number of allowed relay domain aliases for a new administrator"
|
||||
),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "span1"})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@events.observe('RelayDomainCreated')
|
||||
def inc_relaydomains_count(user, rdomain):
|
||||
inc_limit_usage(user, 'relay_domains_limit')
|
||||
|
||||
|
||||
@events.observe('RelayDomainDeleted')
|
||||
def dec_relaydomains_count(rdomain):
|
||||
owner = get_object_owner(rdomain)
|
||||
dec_limit_usage(owner, 'relay_domains_limit')
|
||||
for rdomalias in rdomain.relaydomainalias_set.all():
|
||||
dec_rdomaliases_count(rdomalias)
|
||||
|
||||
|
||||
@events.observe('RelayDomainAliasCreated')
|
||||
def inc_rdomaliases_count(user, rdomalias):
|
||||
inc_limit_usage(user, 'relay_domain_aliases_limit')
|
||||
|
||||
|
||||
@events.observe('RelayDomainAliasDeleted')
|
||||
def dec_rdomaliases_count(rdomainaliases):
|
||||
if isinstance(rdomainaliases, RelayDomainAlias):
|
||||
rdomainaliases = [rdomainaliases]
|
||||
for rdomainalias in rdomainaliases:
|
||||
owner = get_object_owner(rdomainalias)
|
||||
dec_limit_usage(owner, 'relay_domain_aliases_limit')
|
||||
@@ -0,0 +1,199 @@
|
||||
import reversion
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.core.models import ObjectAccess
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import BadRequest, NotFound
|
||||
from modoboa.extensions.admin.models import AdminObject
|
||||
|
||||
|
||||
class RelayDomainManager(Manager):
|
||||
|
||||
def get_for_admin(self, admin):
|
||||
"""Return the relay domains belonging to this admin.
|
||||
|
||||
The result is a ``QuerySet`` object, so this function can be used
|
||||
to fill ``ModelChoiceField`` objects.
|
||||
"""
|
||||
if admin.is_superuser:
|
||||
return self.get_query_set()
|
||||
return self.get_query_set().filter(owners__user=admin)
|
||||
|
||||
|
||||
class ServiceManager(Manager):
|
||||
|
||||
def load_from_master_cf(self):
|
||||
"""Load services from the master.cf file.
|
||||
|
||||
Parse the configuration file to update the service table. New
|
||||
entries are saved and outdated ones (ie. present in the
|
||||
database but not in the file) are removed.
|
||||
"""
|
||||
with open(parameters.get_admin('MASTER_CF_PATH')) as fp:
|
||||
content = fp.read()
|
||||
services = []
|
||||
for line in content.split('\n'):
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
parts = line.strip().split()
|
||||
if len(parts) != 8:
|
||||
continue
|
||||
if parts[7] != 'smtp':
|
||||
continue
|
||||
srv, created = self.get_or_create(name=parts[0])
|
||||
services.append(parts[0])
|
||||
to_delete = []
|
||||
for service in self.all():
|
||||
if not service.name in services:
|
||||
to_delete.append(service.name)
|
||||
Service.objects.filter(name__in=to_delete).delete()
|
||||
|
||||
|
||||
class Service(models.Model):
|
||||
"""Postfix service.
|
||||
"""
|
||||
name = models.CharField(
|
||||
ugettext_lazy('name'), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy('The service name')
|
||||
)
|
||||
|
||||
objects = ServiceManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RelayDomain(AdminObject):
|
||||
"""Relay domain.
|
||||
|
||||
A relay domain differs from a usual domaine because its final
|
||||
destination is not reached yet. It must be accepted by the MTA but
|
||||
it will then be transfered to another one.
|
||||
"""
|
||||
name = models.CharField(
|
||||
ugettext_lazy('name'), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy('The domain name')
|
||||
)
|
||||
target_host = models.CharField(
|
||||
ugettext_lazy('target host'), max_length=255,
|
||||
help_text=ugettext_lazy('Remote destination of this domain')
|
||||
)
|
||||
service = models.ForeignKey(Service, default='relay')
|
||||
enabled = models.BooleanField(
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy('Check to activate this domain')
|
||||
)
|
||||
verify_recipients = models.BooleanField(
|
||||
ugettext_lazy('verify recipients'),
|
||||
help_text=ugettext_lazy('Check for valid recipients')
|
||||
)
|
||||
|
||||
owners = generic.GenericRelation(ObjectAccess)
|
||||
|
||||
objects = RelayDomainManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return [
|
||||
{"name": "relaydomain", "label": _("Relay Domain"), "type": "dom"},
|
||||
{"name": self.service.name, "label": "%s:" % self.service.name,
|
||||
"type": "srv", "color": "info"}
|
||||
]
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
return self.relaydomainalias_set
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
"""Export this relay domain to CSV.
|
||||
|
||||
:param csvwriter:
|
||||
"""
|
||||
csvwriter.writerow(
|
||||
["relaydomain", self.name, self.target_host,
|
||||
self.service.name, self.enabled, self.verify_recipients]
|
||||
)
|
||||
for rdalias in self.relaydomainalias_set.all():
|
||||
rdalias.to_csv(csvwriter)
|
||||
|
||||
def from_csv(self, user, row):
|
||||
"""Import a relay domain from CSV.
|
||||
|
||||
:param user: user importing the relay domain
|
||||
:param str row: relay domain definition
|
||||
"""
|
||||
if len(row) != 6:
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
self.target_host = row[2].strip()
|
||||
self.service, created = Service.objects.get_or_create(name=row[3].strip())
|
||||
self.enabled = (row[4].strip() == 'True')
|
||||
self.verify_recipients = (row[5].strip() == 'True')
|
||||
self.save(creator=user)
|
||||
|
||||
def post_create(self, creator):
|
||||
"""Post creation actions.
|
||||
|
||||
:param ``User`` creator: user whos created this relay domain
|
||||
"""
|
||||
super(RelayDomain, self).post_create(creator)
|
||||
for rdomalias in self.relaydomainalias_set.all():
|
||||
rdomalias.post_create(creator)
|
||||
|
||||
reversion.register(RelayDomain)
|
||||
|
||||
|
||||
class RelayDomainAlias(AdminObject):
|
||||
"""Relay domain alias.
|
||||
|
||||
"""
|
||||
name = models.CharField(
|
||||
ugettext_lazy("name"), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy("The alias name")
|
||||
)
|
||||
target = models.ForeignKey(
|
||||
RelayDomain, verbose_name=ugettext_lazy('target'),
|
||||
help_text=ugettext_lazy("The relay domain this alias points to")
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy("Check to activate this alias")
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
"""Export this relay domain alias to CSV.
|
||||
|
||||
:param csvwriter:
|
||||
"""
|
||||
csvwriter.writerow(
|
||||
['relaydomainalias', self.name, self.target.name, self.enabled]
|
||||
)
|
||||
|
||||
def from_csv(self, user, row):
|
||||
"""Import a relay domain alias from CSV.
|
||||
|
||||
:param user: user importing the relay domain alias
|
||||
:param str row: relay domain alias definition
|
||||
"""
|
||||
if len(row) != 4:
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
try:
|
||||
self.target = RelayDomain.objects.get(name=row[2].strip())
|
||||
except RelayDomain.DoesNotExist:
|
||||
raise NotFound(_("Relay domain %s does not exist" % row[2].strip()))
|
||||
self.enabled = (row[3].strip() == 'True')
|
||||
self.save(creator=user)
|
||||
|
||||
reversion.register(RelayDomainAlias)
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @module relay_domains
|
||||
* @desc Utility class to manage relay domains.
|
||||
*/
|
||||
|
||||
var RelayDomains = function(options) {
|
||||
this.initialize(options);
|
||||
};
|
||||
|
||||
RelayDomains.prototype = {
|
||||
constructor: RelayDomains,
|
||||
|
||||
defaults: {
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
admin.register_tag_handler("srv", this.srv_tag_handler);
|
||||
$(document).bind('domain_listing_refresh', this.listing_refresh_cb);
|
||||
},
|
||||
|
||||
listing_refresh_cb: function() {
|
||||
$("a[name=delrelaydomain]").confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
method: "DELETE",
|
||||
success_cb: $.proxy(admin.reload_listing, admin)
|
||||
});
|
||||
},
|
||||
|
||||
launch_scan: function(e) {
|
||||
var $link = get_target(e);
|
||||
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: $link.attr("href"),
|
||||
global: false
|
||||
}).done(function(data) {
|
||||
var $widget = $('#id_service');
|
||||
|
||||
$widget.empty();
|
||||
$.each(data, function(label, id) {
|
||||
$widget.append($('<option />', {
|
||||
value: id,
|
||||
html: label
|
||||
}));
|
||||
});
|
||||
$('.modal-body').prepend(
|
||||
build_success_alert(gettext('Services updated'))
|
||||
);
|
||||
}).fail(function(jqxhr) {
|
||||
var result = $.parseJSON(jqxhr.responseText);
|
||||
$('.modal-body').prepend(
|
||||
build_error_alert(result[0])
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
domainform_cb: function() {
|
||||
$('#btnscan').click(this.launch_scan);
|
||||
$('input:text:visible:first').focus();
|
||||
$("#id_aliases").dynamic_input();
|
||||
$(".submit").on('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "rdomform",
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(admin.reload_listing, admin)
|
||||
});
|
||||
}, this));
|
||||
$(document).trigger('domform_init');
|
||||
},
|
||||
|
||||
srv_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") == undefined && $link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj
|
||||
.setparam("domfilter", "relaydomain")
|
||||
.setparam(tag + "filter", text)
|
||||
.update();
|
||||
if ($("a[name=dom]").length == 0) {
|
||||
$("#searchform").parent().after(this.make_tag("relaydomain", "dom"));
|
||||
}
|
||||
$("#searchform").parent().after(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{% extends "common/generic_modal_form.html" %}{% block formbody %}
|
||||
{% include "postfix_relay_domains/relaydomain_form.html" %}
|
||||
{% endblock %}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{% load lib_tags %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.target_host %}
|
||||
{% include "postfix_relay_domains/service_field.html" with field=form.service %}
|
||||
{% render_fields_group form "aliases" %}
|
||||
{% render_field form.enabled %}
|
||||
{% render_field form.verify_recipients %}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
{% extends "common/generic_field.html" %}{% load i18n %}
|
||||
{% block extra_controls %}<a id="btnscan" class="btn btn-success" style="margin-bottom: 8px" href="{% url 'modoboa.extensions.postfix_relay_domains.views.scan_for_services' %}">{% trans "Scan" %}</a>{% endblock %}
|
||||
@@ -0,0 +1,212 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.core.factories import UserFactory
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.tests import ExtTestCase
|
||||
from modoboa.extensions.admin import factories
|
||||
from modoboa.extensions.limits.tests import ResourceTestCase
|
||||
from .models import RelayDomain, RelayDomainAlias, Service
|
||||
from .factories import RelayDomainFactory, RelayDomainAliasFactory
|
||||
|
||||
|
||||
class Operations(object):
|
||||
|
||||
def _create_relay_domain(self, name, status=200):
|
||||
srv, created = Service.objects.get_or_create(name='dummy')
|
||||
values = {
|
||||
'name': name, 'target_host': 'external.host.tld',
|
||||
'service': srv.id, 'enabled': True
|
||||
}
|
||||
return self.ajax_post(
|
||||
reverse("modoboa.extensions.postfix_relay_domains.views.create"),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _relay_domain_alias_operation(self, optype, rdomain, name, status=200):
|
||||
rdom = RelayDomain.objects.get(name=rdomain)
|
||||
values = {
|
||||
'name': rdom.name, 'target_host': rdom.target_host,
|
||||
'service': rdom.service.id
|
||||
}
|
||||
aliases = [alias.name for alias in rdom.relaydomainalias_set.all()]
|
||||
if optype == 'add':
|
||||
aliases.append(name)
|
||||
else:
|
||||
aliases.remove(name)
|
||||
for cpt, alias in enumerate(aliases):
|
||||
fname = 'aliases' if not cpt else 'aliases_%d' % cpt
|
||||
values[fname] = alias
|
||||
return self.ajax_post(
|
||||
reverse("modoboa.extensions.postfix_relay_domains.views.edit",
|
||||
args=[rdom.id]),
|
||||
values, status
|
||||
)
|
||||
|
||||
def _check_limit(self, name, curvalue, maxvalue):
|
||||
l = self.user.limitspool.get_limit('%s_limit' % name)
|
||||
self.assertEqual(l.curvalue, curvalue)
|
||||
self.assertEqual(l.maxvalue, maxvalue)
|
||||
|
||||
|
||||
class RelayDomainsTestCase(ExtTestCase, Operations):
|
||||
fixtures = ['initial_users.json']
|
||||
|
||||
def setUp(self):
|
||||
super(RelayDomainsTestCase, self).setUp()
|
||||
self.activate_extensions('postfix_relay_domains')
|
||||
factories.populate_database()
|
||||
self.rdom = RelayDomainFactory(name='relaydomain.tld')
|
||||
RelayDomainAliasFactory(name='relaydomainalias.tld', target=self.rdom)
|
||||
|
||||
def test_create_relaydomain(self):
|
||||
"""Test the creation of a relay domain.
|
||||
|
||||
We also check that unique constraints are respected: domain,
|
||||
relay domain alias.
|
||||
|
||||
FIXME: add a check for domain alias.
|
||||
"""
|
||||
self._create_relay_domain('relaydomain1.tld')
|
||||
rdom = RelayDomain.objects.get(name='relaydomain1.tld')
|
||||
self.assertEqual(rdom.target_host, 'external.host.tld')
|
||||
self.assertEqual(rdom.service.name, 'dummy')
|
||||
self.assertEqual(rdom.enabled, True)
|
||||
self.assertEqual(rdom.verify_recipients, False)
|
||||
|
||||
resp = self._create_relay_domain('test.com', 400)
|
||||
self.assertEqual(resp['form_errors']['name'][0],
|
||||
'A domain with this name already exists')
|
||||
resp = self._create_relay_domain('relaydomainalias.tld', 400)
|
||||
self.assertEqual(
|
||||
resp['form_errors']['name'][0],
|
||||
'A relay domain alias with this name already exists'
|
||||
)
|
||||
|
||||
def test_create_relaydomainalias(self):
|
||||
"""Test the creation of a relay domain alias.
|
||||
|
||||
We also check that unique constraints are respected: domain,
|
||||
relay domain.
|
||||
|
||||
FIXME: add a check for domain alias.
|
||||
"""
|
||||
self._relay_domain_alias_operation(
|
||||
'add', self.rdom.name, 'relaydomainalias1.tld'
|
||||
)
|
||||
resp = self._relay_domain_alias_operation(
|
||||
'add', self.rdom.name, 'test.com', 400
|
||||
)
|
||||
self.assertEqual(
|
||||
resp['form_errors']['aliases_2'][0],
|
||||
'A domain with this name already exists'
|
||||
)
|
||||
resp = self._relay_domain_alias_operation(
|
||||
'add', self.rdom.name, self.rdom.name, 400
|
||||
)
|
||||
self.assertEqual(
|
||||
resp['form_errors']['aliases_2'][0],
|
||||
'A relay domain with this name already exists'
|
||||
)
|
||||
|
||||
def test_edit_relaydomain(self):
|
||||
"""Test the modification of a relay domain.
|
||||
|
||||
Rename 'relaydomain.tld' domain to 'relaydomain.org'
|
||||
"""
|
||||
values = {
|
||||
'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
|
||||
'service': self.rdom.service.id
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.postfix_relay_domains.views.edit',
|
||||
args=[self.rdom.id]),
|
||||
values
|
||||
)
|
||||
RelayDomain.objects.get(name='relaydomain.org')
|
||||
|
||||
def test_edit_relaydomainalias(self):
|
||||
"""Test the modification of a relay domain alias.
|
||||
|
||||
Rename 'relaydomainalias.tld' domain to 'relaydomainalias.net'
|
||||
"""
|
||||
values = {
|
||||
'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
|
||||
'service': self.rdom.service.id, 'aliases': 'relaydomainalias.net'
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.postfix_relay_domains.views.edit',
|
||||
args=[self.rdom.id]),
|
||||
values
|
||||
)
|
||||
RelayDomainAlias.objects.get(name='relaydomainalias.net')
|
||||
with self.assertRaises(RelayDomainAlias.DoesNotExist):
|
||||
RelayDomainAlias.objects.get(name='relaydomainalias.tld')
|
||||
|
||||
def test_delete_relaydomain(self):
|
||||
"""Test the removal of a relay domain
|
||||
"""
|
||||
self.ajax_post(
|
||||
reverse("modoboa.extensions.postfix_relay_domains.views.delete",
|
||||
args=[self.rdom.id]),
|
||||
{}
|
||||
)
|
||||
with self.assertRaises(RelayDomain.DoesNotExist):
|
||||
RelayDomain.objects.get(name='relaydomain.tld')
|
||||
|
||||
def test_delete_relaydomainalias(self):
|
||||
"""Test the remove of a relay domain alias
|
||||
"""
|
||||
self._relay_domain_alias_operation(
|
||||
'del', self.rdom.name, 'relaydomainalias.tld'
|
||||
)
|
||||
with self.assertRaises(RelayDomain.DoesNotExist):
|
||||
RelayDomain.objects.get(name='relaydomainalias.tld')
|
||||
|
||||
|
||||
class LimitsTestCase(ExtTestCase, Operations):
|
||||
fixtures = ['initial_users.json']
|
||||
|
||||
def setUp(self):
|
||||
super(LimitsTestCase, self).setUp()
|
||||
from modoboa.extensions.limits.models import LimitTemplates
|
||||
|
||||
self.activate_extensions('limits', 'postfix_relay_domains')
|
||||
for tpl in LimitTemplates().templates:
|
||||
parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits')
|
||||
self.user = UserFactory.create(
|
||||
username='reseller', groups=('Resellers',)
|
||||
)
|
||||
self.clt.logout()
|
||||
self.clt.login(username='reseller', password='toto')
|
||||
|
||||
def test_relay_domains_limit(self):
|
||||
self._create_relay_domain('relaydomain1.tld')
|
||||
self._check_limit('relay_domains', 1, 2)
|
||||
self._create_relay_domain('relaydomain2.tld')
|
||||
self._check_limit('relay_domains', 2, 2)
|
||||
resp = self._create_relay_domain('relaydomain3.tld', 403)
|
||||
self._check_limit('relay_domains', 2, 2)
|
||||
self.ajax_post(
|
||||
reverse('modoboa.extensions.postfix_relay_domains.views.delete',
|
||||
args=[RelayDomain.objects.get(name='relaydomain2.tld').id]),
|
||||
{}
|
||||
)
|
||||
self._check_limit('relay_domains', 1, 2)
|
||||
|
||||
def test_relay_domain_aliases_limit(self):
|
||||
self._create_relay_domain('relaydomain1.tld')
|
||||
self._relay_domain_alias_operation(
|
||||
'add', 'relaydomain1.tld', 'relay_domain_alias1.tld'
|
||||
)
|
||||
self._check_limit('relay_domain_aliases', 1, 2)
|
||||
self._relay_domain_alias_operation(
|
||||
'add', 'relaydomain1.tld', 'relay_domain_alias2.tld'
|
||||
)
|
||||
self._check_limit('relay_domain_aliases', 2, 2)
|
||||
self._relay_domain_alias_operation(
|
||||
'add', 'relaydomain1.tld', 'relay_domain_alias3.tld', 403
|
||||
)
|
||||
self._check_limit('relay_domain_aliases', 2, 2)
|
||||
self._relay_domain_alias_operation(
|
||||
'delete', 'relaydomain1.tld', 'relay_domain_alias2.tld'
|
||||
)
|
||||
self._check_limit('relay_domain_aliases', 1, 2)
|
||||
@@ -0,0 +1,9 @@
|
||||
from django.conf.urls import patterns
|
||||
|
||||
urlpatterns = patterns(
|
||||
'modoboa.extensions.postfix_relay_domains.views',
|
||||
(r'^relaydomains/new/', 'create'),
|
||||
(r'^relaydomains/(?P<rdom_id>\d+)/edit/$', 'edit'),
|
||||
(r'^relaydomains/(?P<rdom_id>\d+)/delete/$', 'delete'),
|
||||
(r'^relaydomains/scan_services/$', 'scan_for_services'),
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
from django.shortcuts import render
|
||||
from django.db import transaction
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required
|
||||
)
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.webutils import render_to_json_response
|
||||
from .models import RelayDomain, Service
|
||||
from .forms import RelayDomainForm, RelayDomainFormGeneral
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("postfix_relay_domains.add_relaydomain")
|
||||
@transaction.commit_on_success
|
||||
def create(request, tplname="postfix_relay_domains/new_relaydomain_form.html"):
|
||||
events.raiseEvent("CanCreate", request.user, "relay_domains")
|
||||
if request.method == 'POST':
|
||||
form = RelayDomainFormGeneral(request.POST)
|
||||
if form.is_valid():
|
||||
rdom = form.save(request.user)
|
||||
rdom.post_create(request.user)
|
||||
return render_to_json_response(_("Relay domain created"))
|
||||
return render_to_json_response(
|
||||
{'form_errors': form.errors}, status=400
|
||||
)
|
||||
|
||||
ctx = {"title": _("New relay domain"),
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(create),
|
||||
"formid": "rdomform",
|
||||
"form": RelayDomainFormGeneral()}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("postfix_relay_domains.change_relaydomain")
|
||||
def edit(request, rdom_id, tplname='common/tabforms.html'):
|
||||
rdom = RelayDomain.objects.get(pk=rdom_id)
|
||||
if not request.user.can_access(rdom):
|
||||
raise PermDeniedException
|
||||
instances = {'general': rdom}
|
||||
events.raiseEvent("FillRelayDomainInstances", request.user, rdom, instances)
|
||||
if request.method == 'POST':
|
||||
rdom.oldname = rdom.name
|
||||
form = RelayDomainForm(request.user, request.POST, instances=instances)
|
||||
if form.is_valid():
|
||||
form.save(request.user)
|
||||
events.raiseEvent('RelayDomainModified', rdom)
|
||||
return render_to_json_response(_('Relay domain modified'))
|
||||
|
||||
return render_to_json_response(
|
||||
{'form_errors': form.errors}, status=400
|
||||
)
|
||||
ctx = {
|
||||
'action': reverse(edit, args=[rdom.id]),
|
||||
'formid': 'rdomform',
|
||||
'title': rdom.name,
|
||||
'action_label': _("Update"),
|
||||
'action_classes': "submit",
|
||||
'tabs': RelayDomainForm(request.user, instances=instances)
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("postfix_relay_domains.delete_relaydomain")
|
||||
def delete(request, rdom_id):
|
||||
rdom = RelayDomain.objects.get(pk=rdom_id)
|
||||
if not request.user.can_access(rdom):
|
||||
raise PermDeniedException
|
||||
rdom.delete()
|
||||
return render_to_json_response(_('Relay domain deleted'))
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("postfix_relay_domains.add_service")
|
||||
@require_http_methods(["POST"])
|
||||
def scan_for_services(request):
|
||||
try:
|
||||
Service.objects.load_from_master_cf()
|
||||
except IOError as e:
|
||||
return render_to_json_response([str(e)], status=500)
|
||||
|
||||
return render_to_json_response(
|
||||
dict((srv.name, srv.id) for srv in Service.objects.all())
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
import sys
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
|
||||
@@ -11,43 +11,18 @@ class SieveFilters(ModoExtension):
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Plugin to easily create server-side filters")
|
||||
url = "sfilters"
|
||||
available_for_topredirection = True
|
||||
|
||||
def load(self):
|
||||
from .app_settings import ParametersForm, UserSettings
|
||||
parameters.register(ParametersForm, ugettext_lazy("Sieve filters"))
|
||||
parameters.register(UserSettings, ugettext_lazy("Message filters"))
|
||||
from modoboa.extensions.sievefilters import general_callbacks
|
||||
if 'modoboa.extensions.sievefilters.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(SieveFilters)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "options_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "sievefilters",
|
||||
"label": _("Message filters"),
|
||||
"url": reverse("modoboa.extensions.sievefilters.views.index"),
|
||||
"img": "icon-check"}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserLogout")
|
||||
def userlogout(request):
|
||||
from .lib import SieveClient
|
||||
|
||||
if not request.user.mailbox_set.count():
|
||||
return
|
||||
try:
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
sc.logout()
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import events
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "options_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "sievefilters",
|
||||
"label": _("Message filters"),
|
||||
"url": reverse("modoboa.extensions.sievefilters.views.index"),
|
||||
"img": "icon-check"}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserLogout")
|
||||
def userlogout(request):
|
||||
from .lib import SieveClient
|
||||
|
||||
if not request.user.mailbox_set.count():
|
||||
return
|
||||
try:
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
sc.logout()
|
||||
@@ -5,11 +5,12 @@ from sievelib.managesieve import Client, Error
|
||||
from sievelib.parser import Parser
|
||||
from sievelib.factory import FiltersSet
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.connections import ConnectionsManager, ConnectionError
|
||||
|
||||
|
||||
class SieveClientError(Exception):
|
||||
pass
|
||||
class SieveClientError(ModoboaException):
|
||||
http_code = 424
|
||||
|
||||
|
||||
class SieveClient(object):
|
||||
|
||||
@@ -18,15 +18,10 @@ function removefilter(evt) {
|
||||
|
||||
$.ajax({
|
||||
url: $this.attr("href"),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.status == "ok") {
|
||||
history.update(true);
|
||||
$("body").notify('success', response.respmsg, 2000);
|
||||
} else {
|
||||
$("body").notify('error', response.respmsg);
|
||||
}
|
||||
}
|
||||
dataType: 'json'
|
||||
}).done(function(response) {
|
||||
history.update(true);
|
||||
$("body").notify('success', response, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,20 +72,15 @@ function loadfs(response) {
|
||||
function send_command(cmd, extracb) {
|
||||
$.ajax({
|
||||
url: cmd,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.status == "ko") {
|
||||
$("body").notify('error', response.respmsg);
|
||||
} else {
|
||||
if (extracb != undefined) {
|
||||
extracb(response);
|
||||
}
|
||||
if (!response.norefresh) {
|
||||
history.update(true);
|
||||
}
|
||||
$("body").notify('success', response.respmsg, 2000);
|
||||
}
|
||||
dataType: 'json'
|
||||
}).done(function(response) {
|
||||
if (extracb != undefined) {
|
||||
extracb(response);
|
||||
}
|
||||
if (!response.norefresh) {
|
||||
history.update(true);
|
||||
}
|
||||
$("body").notify('success', response.respmsg, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,14 +113,9 @@ function savefs(evt) {
|
||||
url: $editor.attr("action"),
|
||||
type: $editor.attr("method"),
|
||||
dataType: 'json',
|
||||
data: $editor.serialize(),
|
||||
success: function(response) {
|
||||
if (response.status == "ko") {
|
||||
$("body").notify('error', response.respmsg);
|
||||
} else {
|
||||
$("body").notify('success', response.respmsg, 2000);
|
||||
}
|
||||
}
|
||||
data: $editor.serialize()
|
||||
}).done(function(response) {
|
||||
$("body").notify('success', response.respmsg, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,16 +166,11 @@ function toggle_filter_state(event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
url: $this.attr("href"),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.status == "ok") {
|
||||
$this.html(response.label);
|
||||
$this.attr("class", "");
|
||||
$this.addClass(response.color);
|
||||
} else {
|
||||
$("body").notify('error', response.respmsg);
|
||||
}
|
||||
}
|
||||
dataType: 'json'
|
||||
}).done(function(response) {
|
||||
$this.html(response.label);
|
||||
$this.attr("class", "");
|
||||
$this.addClass(response.color);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,11 +180,10 @@ function move_filter(event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
url: $this.attr('href'),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$("#set_content").html(response.content);
|
||||
init_filters_list();
|
||||
}
|
||||
dataType: 'json'
|
||||
}).done(function(response) {
|
||||
$("#set_content").html(response.content);
|
||||
init_filters_list();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,11 +213,10 @@ function filtersetform_cb() {
|
||||
$("#newfiltersset").find("input").keypress(function(e) {
|
||||
if (e.which == 13) e.preventDefault();
|
||||
});
|
||||
$(".submit").one('click', function(e) {
|
||||
$(".submit").on('click', function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "newfiltersset",
|
||||
reload_on_success: false,
|
||||
error_cb: filtersetform_cb,
|
||||
success_cb: filterset_created
|
||||
});
|
||||
});
|
||||
@@ -257,8 +235,9 @@ function fetch_templates(url, container) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
async: false,
|
||||
dataType: 'json',
|
||||
success: function(data) { result = data; }
|
||||
dataType: 'json'
|
||||
}).done(function(data) {
|
||||
result = data;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -14,6 +14,25 @@ var history = new History({
|
||||
var cond_templates = null;
|
||||
var action_templates = null;
|
||||
|
||||
function display_filter_errors(data) {
|
||||
$("ul.errors").remove();
|
||||
$.each(data.form_errors, function(id, value) {
|
||||
if (id.indexOf('cond') && id.indexOf('action')) {
|
||||
return;
|
||||
}
|
||||
var fullid = "id_" + (data.prefix ? data.prefix + "-" : "") + id;
|
||||
var $widget = $("#" + fullid);
|
||||
var ulid = fullid + "-error";
|
||||
var $ul = $("<ul />", {
|
||||
"class": "errors",
|
||||
"id": ulid
|
||||
}).append($("<li />", {
|
||||
html: value[0]
|
||||
}));
|
||||
$widget.after($ul);
|
||||
});
|
||||
}
|
||||
|
||||
function filterform_cb() {
|
||||
cond_templates = fetch_templates("{% url 'modoboa.extensions.sievefilters.views.get_templates' 'condition' %}", cond_templates);
|
||||
action_templates = fetch_templates("{% url 'modoboa.extensions.sievefilters.views.get_templates' 'action' %}", action_templates);
|
||||
@@ -34,13 +53,14 @@ function filterform_cb() {
|
||||
}
|
||||
});
|
||||
|
||||
$(".submit").one('click', function(e) {
|
||||
$(".submit").on('click', function(e) {
|
||||
var counters = "conds=" + $dc.fields_cnt + "&actions=" + $da.fields_cnt;
|
||||
|
||||
simple_ajax_form_post(e, {
|
||||
error_cb: filterform_cb,
|
||||
formid: 'filterform',
|
||||
reload_mode: 'history',
|
||||
extradata: counters
|
||||
extradata: counters,
|
||||
error_cb: display_filter_errors
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.webutils import _render_error, \
|
||||
ajax_response, ajax_simple_response
|
||||
ajax_response, render_to_json_response
|
||||
from modoboa.lib.connections import ConnectionError
|
||||
from modoboa.extensions.admin.lib import needs_mailbox
|
||||
from .lib import SieveClient, SieveClientError
|
||||
@@ -54,8 +54,12 @@ def index(request, tplname="sievefilters/index.html"):
|
||||
@needs_mailbox()
|
||||
def get_templates(request, ftype):
|
||||
if ftype == "condition":
|
||||
return ajax_simple_response(FilterForm([], [], request).cond_templates)
|
||||
return ajax_simple_response(FilterForm([], [], request).action_templates)
|
||||
return render_to_json_response(
|
||||
FilterForm([], [], request).cond_templates
|
||||
)
|
||||
return render_to_json_response(
|
||||
FilterForm([], [], request).action_templates
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -87,8 +91,8 @@ def getfs(request, name):
|
||||
|
||||
menu = '<ul id="fsetmenu" class="nav nav-list"><li class="nav-header">%s</li>%s</ul>' % \
|
||||
(_("Actions"), fset_menu(editormode, name))
|
||||
resp = dict(status="ok", menu=menu, content=htmlcontent)
|
||||
return ajax_simple_response(resp)
|
||||
resp = dict(menu=menu, content=htmlcontent)
|
||||
return render_to_json_response(resp)
|
||||
|
||||
|
||||
def build_filter_ctx(ctx, form):
|
||||
@@ -119,13 +123,12 @@ def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None
|
||||
fset.updatefilter(
|
||||
oldname, fltname, conditions, actions, match_type
|
||||
)
|
||||
try:
|
||||
sc.pushscript(fset.name, str(fset))
|
||||
except SieveClientError as e:
|
||||
return ajax_response(request, "ko", respmsg=str(e))
|
||||
return ajax_response(request, respmsg=okmsg, ajaxnav=True)
|
||||
tplctx = build_filter_ctx(tplctx, form)
|
||||
return ajax_response(request, status="ko", template=tplname, **tplctx)
|
||||
sc.pushscript(fset.name, str(fset))
|
||||
return render_to_json_response(okmsg)
|
||||
#tplctx = build_filter_ctx(tplctx, form)
|
||||
#return ajax_response(request, status="ko", template=tplname, **tplctx)
|
||||
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -186,8 +189,8 @@ def removefilter(request, setname, fname):
|
||||
fset = sc.getscript(setname, format="fset")
|
||||
if fset.removefilter(fname.encode("utf-8")):
|
||||
sc.pushscript(fset.name, str(fset))
|
||||
return ajax_response(request, respmsg=_("Filter removed"))
|
||||
return ajax_response(request, "ko", respmsg=_("Failed to remove filter"))
|
||||
return render_to_json_response(_("Filter removed"))
|
||||
return render_to_json_response(_("Failed to remove filter"), status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -208,37 +211,28 @@ def savefs(request, name):
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def new_filters_set(request, tplname="common/generic_modal_form.html"):
|
||||
if request.method == "POST":
|
||||
form = FiltersSetForm(request.POST)
|
||||
if form.is_valid():
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
sc.pushscript(form.cleaned_data["name"], "# Empty script",
|
||||
form.cleaned_data["active"])
|
||||
return render_to_json_response({
|
||||
"url": form.cleaned_data["name"],
|
||||
"active": form.cleaned_data["active"],
|
||||
"respmsg": _("Filters set created")
|
||||
})
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
ctx = {"title": _("Create a new filters set"),
|
||||
"formid": "newfiltersset",
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"action": reverse(new_filters_set),
|
||||
"withmenu": False,
|
||||
"withunseen": False}
|
||||
if request.method == "POST":
|
||||
form = FiltersSetForm(request.POST)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
try:
|
||||
sc.pushscript(form.cleaned_data["name"], "# Empty script",
|
||||
form.cleaned_data["active"])
|
||||
except SieveClientError, e:
|
||||
error = str(e)
|
||||
else:
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
"url": form.cleaned_data["name"],
|
||||
"active": form.cleaned_data["active"],
|
||||
"respmsg": _("Filters set created")
|
||||
})
|
||||
|
||||
ctx["form"] = form
|
||||
ctx["error"] = error
|
||||
return ajax_response(request, status="ko", template=tplname, **ctx)
|
||||
|
||||
ctx["form"] = FiltersSetForm()
|
||||
"withunseen": False,
|
||||
"form": FiltersSetForm()}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@@ -247,13 +241,10 @@ def new_filters_set(request, tplname="common/generic_modal_form.html"):
|
||||
def remove_filters_set(request, name):
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
try:
|
||||
sc.deletescript(name)
|
||||
except SieveClientError, e:
|
||||
return ajax_simple_response(dict(status="ko", respmsg=str(e)))
|
||||
sc.deletescript(name)
|
||||
acs, scripts = sc.listscripts()
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Filters set deleted"), newfs=acs
|
||||
return render_to_json_response(dict(
|
||||
respmsg=_("Filters set deleted"), newfs=acs
|
||||
))
|
||||
|
||||
|
||||
@@ -293,24 +284,19 @@ def toggle_filter_state(request, setname, fname):
|
||||
password=request.session["password"])
|
||||
if type(fname) is unicode:
|
||||
fname = fname.encode("utf-8")
|
||||
try:
|
||||
fset = sc.getscript(setname, format="fset")
|
||||
if fset.is_filter_disabled(fname):
|
||||
ret = fset.enablefilter(fname)
|
||||
newstate = _("yes")
|
||||
color = "green"
|
||||
else:
|
||||
ret = fset.disablefilter(fname)
|
||||
newstate = _("no")
|
||||
color = "red"
|
||||
if not ret:
|
||||
pass
|
||||
sc.pushscript(setname, str(fset))
|
||||
except SieveClientError, e:
|
||||
return ajax_response(request, "ko", respmsg=str(e))
|
||||
|
||||
return ajax_simple_response({
|
||||
"status": "ok",
|
||||
fset = sc.getscript(setname, format="fset")
|
||||
if fset.is_filter_disabled(fname):
|
||||
ret = fset.enablefilter(fname)
|
||||
newstate = _("yes")
|
||||
color = "green"
|
||||
else:
|
||||
ret = fset.disablefilter(fname)
|
||||
newstate = _("no")
|
||||
color = "red"
|
||||
if not ret:
|
||||
pass
|
||||
sc.pushscript(setname, str(fset))
|
||||
return render_to_json_response({
|
||||
"label": newstate,
|
||||
"color": color
|
||||
})
|
||||
@@ -319,12 +305,9 @@ def toggle_filter_state(request, setname, fname):
|
||||
def move_filter(request, setname, fname, direction):
|
||||
sc = SieveClient(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
try:
|
||||
fset = sc.getscript(setname, format="fset")
|
||||
fset.movefilter(fname.encode("utf-8"), direction)
|
||||
sc.pushscript(setname, str(fset))
|
||||
except (SieveClientError), e:
|
||||
return ajax_response(request, "ko", respmsg=str(e))
|
||||
fset = sc.getscript(setname, format="fset")
|
||||
fset.movefilter(fname.encode("utf-8"), direction)
|
||||
sc.pushscript(setname, str(fset))
|
||||
return ajax_response(
|
||||
request, template="sievefilters/guieditor.html", fs=fset
|
||||
)
|
||||
|
||||
@@ -7,8 +7,8 @@ This module provides support to retrieve statistics from postfix log :
|
||||
sent, received, bounced, rejected
|
||||
|
||||
"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
import sys
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
|
||||
@@ -17,7 +17,9 @@ class Stats(ModoExtension):
|
||||
name = "stats"
|
||||
label = "Statistics"
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Graphical statistics about emails traffic using RRDtool")
|
||||
description = ugettext_lazy(
|
||||
"Graphical statistics about emails traffic using RRDtool"
|
||||
)
|
||||
needs_media = True
|
||||
|
||||
def load(self):
|
||||
@@ -26,27 +28,12 @@ class Stats(ModoExtension):
|
||||
parameters.register(
|
||||
ParametersForm, ugettext_lazy("Graphical statistics")
|
||||
)
|
||||
from modoboa.extensions.stats import general_callbacks
|
||||
if 'modoboa.extensions.stats.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("AdminMenuDisplay", menu)
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(Stats)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "top_menu" or user.group == "SimpleUsers":
|
||||
return []
|
||||
return [
|
||||
{"name": "stats",
|
||||
"label": _("Statistics"),
|
||||
"url": reverse('fullindex')}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("GetGraphSets")
|
||||
def get_default_graph_sets():
|
||||
from modoboa.extensions.stats.graph_templates import MailTraffic
|
||||
|
||||
gset = MailTraffic()
|
||||
return {gset.html_id: gset}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib import events
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "top_menu" or user.group == "SimpleUsers":
|
||||
return []
|
||||
return [
|
||||
{"name": "stats",
|
||||
"label": _("Statistics"),
|
||||
"url": reverse('fullindex')}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("GetGraphSets")
|
||||
def get_default_graph_sets():
|
||||
from modoboa.extensions.stats.graph_templates import MailTraffic
|
||||
|
||||
gset = MailTraffic()
|
||||
return {gset.html_id: gset}
|
||||
@@ -79,10 +79,6 @@ Stats.prototype = {
|
||||
},
|
||||
|
||||
graphs_cb: function(data) {
|
||||
if (data.status == "ko") {
|
||||
$("body").notify("error", data.respmsg);
|
||||
return;
|
||||
}
|
||||
if (data.content) {
|
||||
$(".tab-pane.active").html(data.content);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ from django.contrib.auth.decorators import (
|
||||
login_required, user_passes_test, permission_required
|
||||
)
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import ModoboaException, PermDeniedException
|
||||
from modoboa.lib.exceptions import BadRequest, PermDeniedException, NotFound
|
||||
from modoboa.lib.webutils import (
|
||||
_render_to_string, ajax_simple_response
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain
|
||||
@@ -24,7 +24,7 @@ def index(request):
|
||||
deflocation = "graphs/?gset=mailtraffic"
|
||||
if not request.user.is_superuser:
|
||||
if not Domain.objects.get_for_admin(request.user).count():
|
||||
raise ModoboaException(_("No statistics available"))
|
||||
raise NotFound(_("No statistics available"))
|
||||
|
||||
period = request.GET.get("period", "day")
|
||||
graph_sets = events.raiseDictEvent('GetGraphSets')
|
||||
@@ -43,14 +43,14 @@ def graphs(request):
|
||||
gset = request.GET.get("gset", None)
|
||||
gsets = events.raiseDictEvent("GetGraphSets")
|
||||
if not gset in gsets:
|
||||
raise ModoboaException(_("Unknown graphic set"))
|
||||
raise NotFound(_("Unknown graphic set"))
|
||||
searchq = request.GET.get("searchquery", None)
|
||||
period = request.GET.get("period", "day")
|
||||
tplvars = dict(graphs=[], period=period)
|
||||
if searchq in [None, "global"]:
|
||||
if not request.user.is_superuser:
|
||||
if not Domain.objects.get_for_admin(request.user).count():
|
||||
return ajax_simple_response({"status": "ok"})
|
||||
return render_to_json_response({})
|
||||
tplvars.update(
|
||||
domain=Domain.objects.get_for_admin(request.user)[0].name
|
||||
)
|
||||
@@ -59,14 +59,14 @@ def graphs(request):
|
||||
else:
|
||||
domain = Domain.objects.filter(name__contains=searchq)
|
||||
if domain.count() != 1:
|
||||
return ajax_simple_response({"status": "ok"})
|
||||
return render_to_json_response({})
|
||||
if not request.user.can_access(domain[0]):
|
||||
raise PermDeniedException
|
||||
tplvars.update(domain=domain[0].name)
|
||||
|
||||
if period == "custom":
|
||||
if not "start" in request.GET or not "end" in request.GET:
|
||||
raise ModoboaException(_("Bad custom period"))
|
||||
raise BadRequest(_("Bad custom period"))
|
||||
start = request.GET["start"]
|
||||
end = request.GET["end"]
|
||||
G = Grapher()
|
||||
@@ -83,7 +83,6 @@ def graphs(request):
|
||||
else:
|
||||
tplvars['graphs'] = gsets[gset].get_graph_names()
|
||||
|
||||
return ajax_simple_response(dict(
|
||||
status="ok",
|
||||
content=_render_to_string(request, "stats/graphs.html", tplvars)
|
||||
))
|
||||
return render_to_json_response({
|
||||
'content': _render_to_string(request, "stats/graphs.html", tplvars)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
import sys
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import events, parameters
|
||||
@@ -11,49 +12,19 @@ class Webmail(ModoExtension):
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Simple IMAP webmail")
|
||||
needs_media = True
|
||||
available_for_topredirection = True
|
||||
|
||||
def load(self):
|
||||
from .app_settings import ParametersForm, UserSettings
|
||||
|
||||
parameters.register(ParametersForm, "Webmail")
|
||||
parameters.register(UserSettings, "Webmail")
|
||||
from modoboa.extensions.webmail import general_callbacks
|
||||
if 'modoboa.extensions.webmail.general_callbacks' in sys.modules:
|
||||
reload(general_callbacks)
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
events.unregister_extension()
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(Webmail)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "top_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "webmail",
|
||||
"label": _("Webmail"),
|
||||
"url": reverse("modoboa.extensions.webmail.views.index")},
|
||||
]
|
||||
|
||||
|
||||
@events.observe("UserLogout")
|
||||
def userlogout(request):
|
||||
from .lib import IMAPconnector
|
||||
from .exceptions import ImapError
|
||||
|
||||
if not request.user.mailbox_set.count():
|
||||
return
|
||||
try:
|
||||
m = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
except Exception:
|
||||
# TODO silent exception are bad : we should at least log it
|
||||
return
|
||||
|
||||
# The following statement may fail under Python 2.6...
|
||||
try:
|
||||
m.logout()
|
||||
except ImapError:
|
||||
pass
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
"""
|
||||
import re
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.exceptions import ModoboaException, InternalError
|
||||
|
||||
|
||||
class WebmailError(ModoboaException):
|
||||
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
|
||||
class WebmailInternalError(InternalError):
|
||||
errorexpr = re.compile(r'\[([^\]]+)\]\s*([^\.]+)')
|
||||
|
||||
def __init__(self, reason, ajax=False):
|
||||
match = WebmailError.errorexpr.match(reason)
|
||||
match = WebmailInternalError.errorexpr.match(reason)
|
||||
if not match:
|
||||
self.reason = reason
|
||||
else:
|
||||
@@ -24,6 +24,16 @@ class WebmailError(ModoboaException):
|
||||
return self.reason
|
||||
|
||||
|
||||
class UnknownAction(ModoboaException):
|
||||
"""
|
||||
Use this exception when the webmail encounter an unknown action.
|
||||
"""
|
||||
http_code = 404
|
||||
|
||||
def __init__(self):
|
||||
super(UnknownAction, self).__init__(_("Unknown action"))
|
||||
|
||||
|
||||
class ImapError(ModoboaException):
|
||||
def __init__(self, reason):
|
||||
self.reason = reason
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user