Merge branch 'master' into feature/radicale

This commit is contained in:
Antoine Nguyen
2014-08-05 08:46:01 +02:00
30 changed files with 1131 additions and 196 deletions
+2 -2
View File
@@ -19,7 +19,7 @@ easy. Actually, all current features are extensions.
It is written in Python and uses the `Django
<https://www.djangoproject.com>`_, `jQuery <http://jquery.com>`_ and
`Bootstap, from Twitter <http://twitter.github.com/bootstrap/>`_
`Bootstrap, from Twitter <http://twitter.github.com/bootstrap/>`_
frameworks.
*************
@@ -37,7 +37,7 @@ Main features
Documentation
*************
A detailled `documentation <https://modoboa.readthedocs.org/>`_ will help you
A detailed `documentation <https://modoboa.readthedocs.org/>`_ will help you
to install, use or extend Modoboa.
*************
+1 -1
View File
@@ -50,7 +50,7 @@ copyright = u'2014, Antoine Nguyen'
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.1.4'
release = '1.1.5'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+28
View File
@@ -66,6 +66,14 @@ The *admin* application exposes several parameters, they are presented below:
| |is specified | |
+--------------------+--------------------+--------------------+
.. note::
If you are not familiar with virtual domain hosting, you should
take a look at `postfix's documentation
<http://www.postfix.org/VIRTUAL_README.html>`_. This `How to
<https://help.ubuntu.com/community/PostfixVirtualMailBoxClamSmtpHowto>`_
also contains useful information.
.. note::
A random secret key will be generated each time the *Parameters*
@@ -76,6 +84,20 @@ The *admin* application exposes several parameters, they are presented below:
Specific LDAP parameters are also available, see :ref:`LDAP
authentication <ldap_auth>`.
***********
Media files
***********
Modoboa uses a specific directory to upload files (ie. when the
webmail is in use) or to create ones (ex: graphical statistics). This
directory is named ``media`` and is located inside modoboa's
installation directory (called ``modoboa_site`` in this
documentation).
To work properly, the system user which runs modoboa (``www-data``,
``apache``, whatever) must have write access to this directory.
*************
Customization
*************
@@ -297,6 +319,9 @@ online panel.
To use it, you can setup a cron job to run every night::
0 0 * * * <modoboa_site>/manage.py cleanlogs
#
# Or like this if you use a virtual environment:
# 0 0 * * * <virtualenv path/bin/python> <modoboa_site>/manage.py cleanlogs
Cleaning the session table
==========================
@@ -311,3 +336,6 @@ cleanup``. That script deletes any session in the session table whose
For example, you could setup a cron job to run this script every night::
0 0 * * * <modoboa_site>/manage.py cleanup
#
# Or like this if you use a virtual environment:
# 0 0 * * * <virtualenv path/bin/python> <modoboa_site>/manage.py cleanup
+5 -4
View File
@@ -91,7 +91,7 @@ sure your database is using UTF8 as a default charset.
Deployment
**********
`modoboa-admin.py`, a command line tool, let you deploy a
`modoboa-admin.py`, a command line tool, lets you deploy a
*ready-to-use* Modoboa site using only one instruction::
$ modoboa-admin.py deploy modoboa_example --syncdb --collectstatic [--with-amavis] [--dburl database-url] [--amavis_dburl database-url]
@@ -155,8 +155,9 @@ server::
You should be able to access Modoboa at http://localhost:8000/.
For a fully working interface using the embedded HTTP server, you need to set the ``DEBUG``
parameter in settings.py to ``True``.
For a fully working interface using the embedded HTTP server, you need
to set the ``DEBUG`` parameter in settings.py to ``True``.
For a production environment, we recommend using a stable webserver
like :ref:`apache2` or :ref:`nginx-label`. Don't forget to set ``DEBUG`` back to ``False``.
like :ref:`apache2` or :ref:`nginx-label`. Don't forget to set
``DEBUG`` back to ``False``.
+52 -14
View File
@@ -99,8 +99,46 @@ This plugin provides a simple management frontend for `amavisd-new
you're planning to use the :ref:`selfservice`, you'll need version
**2.8.0**.
Database
========
Quick Amavis setup
==================
By default, amavis doesn't use a database. To configure this
behaviour, you first need to create a dedicated database. This step is a
bit manual since no *ready-to-use* SQL schema is provided by
amavis. The information is located inside README files, one for `MySQL
<http://www.amavis.org/README.sql-mysql.txt>`_ and one for `PostgreSQL
<http://www.amavis.org/README.sql-pg.txt>`_.
Then, you must tell amavis to use this database for lookups and
quarantined messages storing. Here is a working configuration sample::
@lookup_sql_dsn =
(['DBI:<driver>:database=<database>;host=<dbhost>;port=<dbport>', '<dbuser>', '<password>]']);
@storage_sql_dsn =
(['DBI:<driver>:database=<database>;host=<dbhost>;port=<dbport>', '<dbuser>', '<password>]']);
$virus_quarantine_method = 'sql:';
$spam_quarantine_method = 'sql:';
$banned_files_quarantine_method = 'sql:';
$bad_header_quarantine_method = 'sql:';
$virus_quarantine_to = 'virus-quarantine';
$banned_quarantine_to = 'banned-quarantine';
$bad_header_quarantine_to = 'bad-header-quarantine';
$spam_quarantine_to = 'spam-quarantine';
Replace values between ``<>`` by yours. To know how to configure
amavis to allow quarantined messages release, read this :ref:`section
<amavis_release>`.
.. note::
Amavis configuration allows for separate lookup and storage
databases but Modoboa doesn't support it yet.
Connect Modoboa and Amavis
==========================
You must tell to Modoboa where it can find the amavis
database. Inside :file:`settings.py`, add a new connection to the
@@ -120,17 +158,6 @@ database. Inside :file:`settings.py`, add a new connection to the
Replace values between ``<>`` with yours.
.. note::
Modoboa doesn't create amavis tables. You need to install them
following the `official documentation
<http://www.amavis.org/#doc>`_.
.. note::
Amavis configuration allows for separate lookup and storage
databases but Modoboa doesn't support it yet.
Cleanup
-------
@@ -140,6 +167,9 @@ purge the quarantine database. To use it, add the following line
inside root's crontab::
0 0 * * * <modoboa_site>/manage.py qcleanup
#
# Or like this if you use a virtual environment:
# 0 0 * * * <virtualenv path/bin/python> <modoboa_site>/manage.py qcleanup
Replace ``modoboa_site`` with the path of your Modoboa instance.
@@ -147,6 +177,8 @@ By default, messages older than 14 days are automatically purged. You
can modify this value by changing the ``MAX_MESSAGES_AGE`` parameter
in the online panel.
.. _amavis_release:
Release messages
================
@@ -206,6 +238,9 @@ administrators or domain administrators. To use it, add the following
example line to root's crontab::
0 12 * * * <modoboa_site>/manage.py amnotify --baseurl='<modoboa_url>'
#
# Or like this if you use a virtual environment:
# 0 12 * * * <virtualenv path/bin/python> <modoboa_site>/manage.py amnotify --baseurl='<modoboa_url>'
You are free to change the frequency.
@@ -279,7 +314,7 @@ files (see `rrdtool <http://oss.oetiker.ch/rrdtool/>`_) and then
generates graphics in PNG format.
To use it, go to the online parameters panel and adapt the following
ones to your environnement:
ones to your environment:
+--------------------+--------------------+--------------------------+
|Name |Description |Default value |
@@ -307,6 +342,9 @@ To finish, you need to collect information periodically in order to
feed the RRD files. Add the following line into root's crontab::
*/5 * * * * <modoboa_site>/manage.py logparser &> /dev/null
#
# Or like this if you use a virtual environment:
# 0/5 * * * * <virtualenv path/bin/python> <modoboa_site>/manage.py logparser &> /dev/null
Replace ``<modoboa_site>`` with the path of your Modoboa instance.
+1 -1
View File
@@ -560,7 +560,7 @@ requires a migration.
$ python manage.py migrate admin
#. Update SQL queries used in your environnement (see
#. Update SQL queries used in your environment (see
:ref:`postfix` or :ref:`dovecot`).
#. Update Postfix configuration so that it can handle domain aliases
+9 -62
View File
@@ -155,7 +155,7 @@ MySQL users
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 core_user
iterate_query = SELECT email AS username FROM core_user WHERE email<>''
.. _dovecot_pg_queries:
@@ -174,7 +174,7 @@ PostgreSQL users
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 core_user
iterate_query = SELECT email AS username FROM core_user WHERE email<>''
SQLite users
------------
@@ -191,7 +191,7 @@ SQLite users
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 core_user
iterate_query = SELECT email AS username FROM core_user WHERE email<>''
.. note::
@@ -330,69 +330,16 @@ Trigger
As indicated on `Dovecot's wiki
<http://wiki2.dovecot.org/Quota/Dict>`_, you need a trigger to
properly update the quota. Unfortunately, the provided example won't
work for Modoboa. You should use the following one instead:
properly update the quota.
.. sourcecode:: sql
A working copy of this trigger is available on `Modoboa's website
<http://modoboa.org/resources/modoboa_postgres_trigger.sql>`_.
CREATE OR REPLACE FUNCTION merge_quota() RETURNS TRIGGER AS $$
BEGIN
IF NEW.messages < 0 OR NEW.messages IS NULL THEN
-- ugly kludge: we came here from this function, really do try to insert
IF NEW.messages IS NULL THEN
NEW.messages = 0;
ELSE
NEW.messages = -NEW.messages;
END IF;
return NEW;
END IF;
LOOP
UPDATE admin_quota SET bytes = bytes + NEW.bytes,
messages = messages + NEW.messages
WHERE username = NEW.username;
IF found THEN
RETURN NULL;
END IF;
BEGIN
IF NEW.messages = 0 THEN
RETURN NEW;
ELSE
NEW.messages = - NEW.messages;
return NEW;
END IF;
EXCEPTION WHEN unique_violation THEN
-- someone just inserted the record, update it
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION set_mboxid() RETURNS TRIGGER AS $$
DECLARE
mboxid INTEGER;
BEGIN
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;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS mergequota ON admin_quota;
CREATE TRIGGER mergequota BEFORE INSERT ON admin_quota
FOR EACH ROW EXECUTE PROCEDURE merge_quota();
DROP TRIGGER IF EXISTS setmboxid ON admin_quota;
CREATE TRIGGER setmboxid AFTER INSERT ON admin_quota
FOR EACH ROW EXECUTE PROCEDURE set_mboxid();
Copy this example into a file (for example: :file:`quota-trigger.sql`) on
server running postgres and execute the following commands::
Download this file and copy it on the server running postgres. Then,
execute the following commands::
$ su - postgres
$ psql [modoboa database] < /path/to/quota-trigger.sql
$ psql [modoboa database] < /path/to/modoboa_postgres_trigger.sql
$ exit
Replace ``[modoboa database]`` by the appropriate value.
+7 -37
View File
@@ -48,45 +48,15 @@ following content inside::
This is just one possible configuration.
.. note::
Django 1.3 users, please consult this `page <https://docs.djangoproject.com/en/1.3/howto/deployment/modwsgi/>`_,
it contains an example :file:`wsgi.py` file.
To use mod_wsgi daemon mode, add the two following directives just
under ``WSGIScriptAlias``::
.. note::
You will certainly need more configuration in order to launch
Apache.
WSGIDaemonProcess example.com python-path=<path to your site's dir>:<virtualenv path>/lib/python2.7/site-packages
WSGIProcessGroup example.com
mod_python
==========
First, make sure that mod_python is installed on your server.
Create a new virtualhost in your Apache configuration and put the
following content inside::
<VirtualHost *:80>
ServerName <your value>
DocumentRoot <path to your site's dir>
<Location "/">
SetHandler mod_python
PythonHandler django.core.handlers.modpython
PythonPath "['<path to directory that contains your site's dir>'] + sys.path"
SetEnv DJANGO_SETTINGS_MODULE <your site's name>.settings
</Location>
Alias "/sitestatic" "<path to your site's dir>/sitestatic"
<Location "/sitestatic/">
SetHandler None
</Location>
Alias "/media" "<path to your site's dir>/media"
<Location "/media/">
SetHandler None
</Location>
</VirtualHost>
This is just one possible configuration.
Replace values between ``<>`` with yours. If you don't use a
`virtualenv <http://virtualenv.readthedocs.org/en/latest/>`_, just
remove the last part of the ``WSGIDaemonProcess`` directive.
.. note::
You will certainly need more configuration in order to launch
+44 -13
View File
@@ -10,19 +10,29 @@ from . import Command
class MapFilesGenerator(object):
def __init__(self):
def __init__(self, dbinfo=None):
"""Constructor.
"""
self.template = Template("""user = {{ dbuser }}
password = {{ dbpass }}
dbname = {{ dbname }}
hosts = {{ dbhost }}
query = {{ query|safe }}
""")
self.tpl_context = {
'dbhost': raw_input('Database host: '),
'dbname': raw_input('Database name: '),
'dbuser': raw_input('Username: '),
'dbpass': getpass.getpass('Password: ')
}
if dbinfo is None:
self.tpl_context = {
'dbhost': raw_input('Database host: '),
'dbname': raw_input('Database name: '),
'dbuser': raw_input('Username: '),
'dbpass': getpass.getpass('Password: ')
}
else:
self.tpl_context = {
'dbhost': dbinfo['HOST'],
'dbname': dbinfo['NAME'],
'dbuser': dbinfo['USER'],
'dbpass': dbinfo['PASSWORD']
}
if not self.tpl_context['dbhost']:
self.tpl_context['dbhost'] = '127.0.0.1'
@@ -77,13 +87,18 @@ query = {{ query|safe }}
class SQLiteMapFilesGenerator(MapFilesGenerator):
def __init__(self):
def __init__(self, dbinfo=None):
self.template = Template("""dbpath = {{ dbpath }}
query = {{ query|safe }}
""")
self.tpl_context = {
'dbpath': raw_input('Database path: ')
}
if dbinfo is None:
self.tpl_context = {
'dbpath': raw_input('Database path: ')
}
else:
self.tpl_context = {
'dbpath': dbinfo['NAME']
}
class MapFile(object):
@@ -194,14 +209,30 @@ class PostfixMapsCommand(Command):
default='mysql',
help='Used database type'
)
self._parser.add_argument(
'--dburl', type=str, nargs=1, default=None,
help="url of Modoboa's database"
)
self._parser.add_argument(
'--categories', type=str, nargs='*', default=['std'],
help='Map file categories to generate (choices: std, autoreply)'
)
def handle(self, parsed_args):
"""Command entry point.
"""
dbinfo = None
if parsed_args.dburl:
import dj_database_url
dbinfo = dj_database_url.config(default=parsed_args.dburl[0])
if 'sqlite' in dbinfo['ENGINE']:
parsed_args.dbtype = 'sqlite'
elif 'psycopg2' in dbinfo['ENGINE']:
parsed_args.dbtype = 'postgres'
else:
parsed_args.dbtype = 'mysql'
if parsed_args.dbtype == 'sqlite':
g = SQLiteMapFilesGenerator()
g = SQLiteMapFilesGenerator(dbinfo)
else:
g = MapFilesGenerator()
g = MapFilesGenerator(dbinfo)
g.render(parsed_args)
+61
View File
@@ -1,4 +1,10 @@
import os
import tempfile
from django.core.urlresolvers import reverse
from django.test import TestCase
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib.tests import ModoTestCase
from . import factories
@@ -40,3 +46,58 @@ class ProfileTestCase(ModoTestCase):
self.assertTrue(
self.clt.login(username="user@test.com", password="tutu")
)
class ModoboaAdminCommandTestCase(TestCase):
MAP_FILES = {
"std": [
"sql-domains.cf", "sql-domain-aliases.cf", "sql-aliases.cf",
"sql-domain-aliases-mailboxes.cf", "sql-catchall-aliases.cf",
"sql-maintain.cf"
],
"autoreply": ["sql-autoreplies-transport.cf", "sql-autoreplies.cf"],
"relaydomains": [
"sql-relaydomains.cf", "sql-relaydomains-transport.cf",
"sql-relaydomain-aliases-transport.cf",
"sql-relay-recipient-verification.cf"
]
}
def setUp(self):
self.workdir = tempfile.mkdtemp()
def tearDown(self):
exec_cmd("rm -rf {}".format(self.workdir))
def _test_maps_generation(self, engine, categories=None):
if categories is None:
categories = ["std", "autoreply", "relaydomains"]
dburl = "{}://user:password@localhost/testdb".format(engine)
code, output = exec_cmd(
"modoboa-admin.py postfix_maps --categories {} --dburl {} {}".format(
" ".join(categories), dburl, self.workdir
)
)
self.assertEqual(code, 0)
for category in categories:
for mapfile in self.MAP_FILES[category]:
path = "{}/{}".format(self.workdir, mapfile)
self.assertTrue(os.path.exists(path))
with open(path) as fpo:
content = fpo.read()
if engine != "sqlite":
self.assertIn("user = user", content)
self.assertIn("password = password", content)
self.assertIn("dbname = testdb", content)
self.assertIn("hosts = localhost", content)
else:
self.assertIn("dbpath = testdb", content)
def test_postgres_maps(self):
self._test_maps_generation("postgres")
def test_mysql_maps(self):
self._test_maps_generation("mysql")
def test_sqlite_maps(self):
self._test_maps_generation("sqlite")
+304 -1
View File
@@ -1 +1,304 @@
[{"pk": 1, "model": "core.user", "fields": {"username": "admin", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": false, "last_login": "2012-05-03T16:58:51+01:00", "groups": [], "user_permissions": [], "password": "{CRYPT}dTTsGDkA5ZHKg", "email": "", "date_joined": "2012-01-26T15:19:25+01:00"}}, {"pk": 2, "model": "core.user", "fields": {"username": "admin@demo.local", "first_name": "Demo", "last_name": "Administrator", "is_active": true, "is_superuser": false, "is_staff": false, "last_login": "2012-05-24T05:00:12+01:00", "groups": [1], "user_permissions": [], "password": "{CRYPT}SJ2bQWwabfH/6", "email": "admin@demo.local", "date_joined": "2012-05-03T16:50:15+01:00"}}, {"pk": 4, "model": "core.user", "fields": {"username": "user@demo.local", "first_name": "Demo", "last_name": "User", "is_active": true, "is_superuser": false, "is_staff": false, "last_login": "2012-05-03T16:56:50+01:00", "groups": [2], "user_permissions": [], "password": "{CRYPT}2iAFXtxUA6.7Y", "email": "user@demo.local", "date_joined": "2012-05-03T16:56:50+01:00"}}, {"pk": 2, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["admin", "domain"], "object_id": 1}}, {"pk": 3, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["core", "user"], "object_id": 2}}, {"pk": 4, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["admin", "mailbox"], "object_id": 1}}, {"pk": 5, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["admin", "alias"], "object_id": 1}}, {"pk": 6, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["admin", "domain"], "object_id": 1}}, {"pk": 7, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["admin", "mailbox"], "object_id": 1}}, {"pk": 8, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["core", "user"], "object_id": 2}}, {"pk": 9, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["admin", "alias"], "object_id": 1}}, {"pk": 13, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["core", "user"], "object_id": 4}}, {"pk": 14, "model": "core.objectaccess", "fields": {"is_owner": true, "user": 1, "content_type": ["admin", "mailbox"], "object_id": 3}}, {"pk": 15, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["admin", "mailbox"], "object_id": 3}}, {"pk": 16, "model": "core.objectaccess", "fields": {"is_owner": false, "user": 2, "content_type": ["core", "user"], "object_id": 4}}, {"pk": 1, "model": "admin.objectdates", "fields": {"creation": "2012-05-03T16:49:40+01:00", "last_modification": "2012-05-03T16:49:40+01:00"}}, {"pk": 2, "model": "admin.objectdates", "fields": {"creation": "2012-05-03T16:50:15+01:00", "last_modification": "2012-05-03T16:50:36+01:00"}}, {"pk": 3, "model": "admin.objectdates", "fields": {"creation": "2012-05-03T16:50:15+01:00", "last_modification": "2012-05-03T16:50:15+01:00"}}, {"pk": 4, "model": "admin.objectdates", "fields": {"creation": "2012-05-03T16:51:04+01:00", "last_modification": "2012-05-03T16:51:04+01:00"}}, {"pk": 5, "model": "admin.objectdates", "fields": {"creation": "2012-05-03T16:56:50+01:00", "last_modification": "2012-05-03T16:56:50+01:00"}}, {"pk": 1, "model": "admin.domain", "fields": {"dates": 1, "enabled": true, "name": "demo.local", "quota": 10}}, {"pk": 1, "model": "admin.mailbox", "fields": {"dates": 2, "quota": 10, "user": 2, "domain": 1, "address": "admin"}}, {"pk": 3, "model": "admin.mailbox", "fields": {"dates": 5, "quota": 10, "user": 4, "domain": 1, "address": "user"}}, {"pk": 1, "model": "admin.alias", "fields": {"dates": 3, "enabled": true, "domain": 1, "mboxes": [1], "address": "postmaster", "extmboxes": ""}}, {"pk": 1, "model": "core.extension", "fields": {"enabled": true, "name": "limits"}}, {"pk": 2, "model": "core.extension", "fields": {"enabled": true, "name": "postfix_autoreply"}}, {"pk": 3, "model": "core.extension", "fields": {"enabled": true, "name": "stats"}}, {"pk": 4, "model": "core.extension", "fields": {"enabled": true, "name": "amavis"}}, {"pk": 5, "model": "core.extension", "fields": {"enabled": true, "name": "webmail"}}, {"pk": 6, "model": "core.extension", "fields": {"enabled": true, "name": "sievefilters"}}, {"pk": 7, "model": "core.extension", "fields": {"enabled": true, "name": "postfix_relay_domains"}}, {"pk": "admin@demo.local", "model": "admin.quota", "fields": {"mbox": 1, "username": "admin@demo.local", "bytes": 0, "messages": 0}}, {"pk": "user@demo.local", "model": "admin.quota", "fields": {"mbox": 3, "username": "user@demo.local", "bytes": 0, "messages": 0}}]
[
{
"pk": 1,
"model": "core.user",
"fields": {
"username": "admin",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": false,
"last_login": "2012-05-03T16:58:51+01:00",
"groups": [],
"user_permissions": [],
"password": "{CRYPT}dTTsGDkA5ZHKg",
"email": "",
"date_joined": "2012-01-26T15:19:25+01:00"
}
},
{
"pk": 2,
"model": "core.user",
"fields": {
"username": "admin@demo.local",
"first_name": "Demo",
"last_name": "Administrator",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2012-05-24T05:00:12+01:00",
"groups": [1],
"user_permissions": [],
"password": "{CRYPT}SJ2bQWwabfH/6",
"email": "admin@demo.local",
"date_joined": "2012-05-03T16:50:15+01:00"
}
},
{
"pk": 4,
"model": "core.user",
"fields": {
"username": "user@demo.local",
"first_name": "Demo", "last_name": "User",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2012-05-03T16:56:50+01:00",
"groups": [2],
"user_permissions": [],
"password": "{CRYPT}2iAFXtxUA6.7Y",
"email": "user@demo.local",
"date_joined": "2012-05-03T16:56:50+01:00"
}
},
{
"pk": 2,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "domain"],
"object_id": 1
}
},
{
"pk": 3,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["core", "user"],
"object_id": 2
}
},
{
"pk": 4,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "mailbox"],
"object_id": 1
}
},
{
"pk": 5,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "alias"],
"object_id": 1
}
},
{
"pk": 6,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin", "domain"],
"object_id": 1
}
},
{
"pk": 7,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin", "mailbox"],
"object_id": 1
}
},
{
"pk": 8,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["core", "user"],
"object_id": 2
}
},
{
"pk": 9,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin",
"alias"],
"object_id": 1}},
{
"pk": 13,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["core",
"user"],
"object_id": 4}},
{
"pk": 14,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin",
"mailbox"],
"object_id": 3}},
{
"pk": 15,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin",
"mailbox"],
"object_id": 3}},
{
"pk": 16,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["core",
"user"],
"object_id": 4}},
{
"pk": 1,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:49:40+01:00",
"last_modification": "2012-05-03T16:49:40+01:00"}},
{
"pk": 2,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:50:15+01:00",
"last_modification": "2012-05-03T16:50:36+01:00"}},
{
"pk": 3,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:50:15+01:00",
"last_modification": "2012-05-03T16:50:15+01:00"}},
{
"pk": 4,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:51:04+01:00",
"last_modification": "2012-05-03T16:51:04+01:00"}},
{
"pk": 5,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:56:50+01:00",
"last_modification": "2012-05-03T16:56:50+01:00"}},
{
"pk": 1,
"model": "admin.domain",
"fields": {
"dates": 1,
"enabled": true,
"name": "demo.local",
"quota": 10}},
{
"pk": 1,
"model": "admin.mailbox",
"fields": {
"dates": 2,
"quota": 10,
"user": 2,
"domain": 1,
"address": "admin",
"quota_value": "admin@demo.local"
}
},
{
"pk": 3,
"model": "admin.mailbox",
"fields": {
"dates": 5,
"quota": 10,
"user": 4,
"domain": 1,
"address": "user",
"quota_value": "user@demo.local"
}
},
{
"pk": 1,
"model": "admin.alias",
"fields": {
"dates": 3,
"enabled": true,
"domain": 1,
"mboxes": [1],
"address": "postmaster",
"extmboxes": ""}},
{
"pk": 1,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "limits"}},
{
"pk": 2,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "postfix_autoreply"}},
{
"pk": 3,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "stats"}},
{
"pk": 4,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "amavis"}},
{
"pk": 5,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "webmail"}},
{
"pk": 6,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "sievefilters"}},
{
"pk": 7,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "postfix_relay_domains"
}
},
{
"pk": "admin@demo.local",
"model": "admin.quota",
"fields": {
"username": "admin@demo.local",
"bytes": 0,
"messages": 0
}
},
{
"pk": "user@demo.local",
"model": "admin.quota",
"fields": {
"username": "user@demo.local",
"bytes": 0,
"messages": 0
}
}
]
+9 -4
View File
@@ -105,7 +105,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
return cleaned_data
def update_mailbox_quotas(self, domain):
"""Update all quota records associated to this domain
"""Update all quota records associated to this domain.
This method must be called only when a domain gets renamed. As
the primary key used for a quota is an email address, rename a
@@ -119,9 +119,14 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
perfomant at all as it will generate one query per quota
record to update.
"""
for q in Quota.objects.filter(username__contains="@%s" % self.oldname).values('username'):
username = q['username'].replace('@%s' % self.oldname, '@%s' % domain.name)
Quota.objects.filter(username=q['username']).update(username=username)
for q in Quota.objects.filter(username__contains="@%s" % self.oldname):
username = q.username.replace(
'@%s' % self.oldname, '@%s' % domain.name)
newq = Quota.objects.create(
username=username, bytes=q.bytes, messages=q.messages)
q.mailbox.quota_value = newq
q.mailbox.save()
q.delete()
def save(self, user, commit=True, domalias_post_create=False):
"""Custom save method
@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Mailbox.quota_value'
db.add_column(u'admin_mailbox', 'quota_value',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['admin.Quota'], null=True),
keep_default=False)
# Deleting field 'Quota.mbox'
db.delete_column(u'admin_quota', 'mbox_id')
def backwards(self, orm):
# Deleting field 'Mailbox.quota_value'
db.delete_column(u'admin_mailbox', 'quota_value_id')
# Adding field 'Quota.mbox'
db.add_column(u'admin_quota', 'mbox',
self.gf('django.db.models.fields.related.OneToOneField')(related_name='quota_value', unique=True, null=True, to=orm['admin.Mailbox']),
keep_default=False)
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', [], {}),
'quota_value': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Quota']", 'null': 'True'}),
'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'}),
'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'username': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'primary_key': 'True'})
},
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.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': '256'}),
'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 = ['admin']
@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
for mailbox in orm.Mailbox.objects.all():
quota, created = orm.Quota.objects.get_or_create(
pk="%s@%s" % (mailbox.address, mailbox.domain.name)
)
mailbox.quota_value = quota
mailbox.save()
def backwards(self, orm):
"Write your backwards methods here."
for mailbox in orm.Mailbox.objects.all():
mailbox.quota_value = None
mailbox.save()
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', [], {}),
'quota_value': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Quota']", 'null': 'True'}),
'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'}),
'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'username': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'primary_key': 'True'})
},
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.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': '256'}),
'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 = ['admin']
symmetrical = True
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Mailbox.quota_value'
db.alter_column(u'admin_mailbox', 'quota_value_id', self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['admin.Quota']))
def backwards(self, orm):
# Changing field 'Mailbox.quota_value'
db.alter_column(u'admin_mailbox', 'quota_value_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['admin.Quota'], null=True))
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', [], {}),
'quota_value': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Quota']"}),
'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'}),
'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'username': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'primary_key': 'True'})
},
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.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': '256'}),
'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 = ['admin']
@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Mailbox.quota_value'
db.alter_column(u'admin_mailbox', 'quota_value_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['admin.Quota'], unique=True))
# Adding unique constraint on 'Mailbox', fields ['quota_value']
db.create_unique(u'admin_mailbox', ['quota_value_id'])
def backwards(self, orm):
# Removing unique constraint on 'Mailbox', fields ['quota_value']
db.delete_unique(u'admin_mailbox', ['quota_value_id'])
# Changing field 'Mailbox.quota_value'
db.alter_column(u'admin_mailbox', 'quota_value_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['admin.Quota']))
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', [], {}),
'quota_value': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['admin.Quota']", 'unique': 'True'}),
'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'}),
'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'username': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'primary_key': 'True'})
},
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.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': '256'}),
'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 = ['admin']
+38 -23
View File
@@ -16,6 +16,17 @@ from .base import AdminObject
from .domain import Domain
class Quota(models.Model):
"""Mailbox current quota usage.
"""
username = models.EmailField(primary_key=True, max_length=254)
bytes = models.BigIntegerField(default=0)
messages = models.IntegerField(default=0)
class Meta:
app_label = 'admin'
class MailboxManager(Manager):
def get_for_admin(self, admin, squery=None):
@@ -43,7 +54,7 @@ class MailboxManager(Manager):
qf = Q(pk__in=ids) & qf
else:
qf = Q(pk__in=ids)
return self.get_query_set().filter(qf)
return self.get_query_set().select_related().filter(qf)
class Mailbox(AdminObject):
@@ -53,6 +64,7 @@ class Mailbox(AdminObject):
)
quota = models.PositiveIntegerField()
use_domain_quota = models.BooleanField(default=False)
quota_value = models.OneToOneField(Quota, to_field="username")
domain = models.ForeignKey(Domain)
user = models.ForeignKey(User)
@@ -95,7 +107,7 @@ class Mailbox(AdminObject):
We ask dovecot to give us this information because there are
several patterns to understand and we don't want to implement
them.
them.
"""
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
@@ -134,16 +146,25 @@ class Mailbox(AdminObject):
)
def rename(self, address, domain):
"""Rename the mailbox
"""Rename the mailbox.
To update the associated Quota record, we must create a new
one first, update the foreign key and then we can delete the
original record!
:param string address: the new mailbox's address (local part)
:param Domain domain: the new mailbox's domain
"""
old_mail_home = self.mail_home
qs = Quota.objects.filter(username=self.full_address)
self.address = address
self.domain = domain
qs.update(username=self.full_address)
old_qvalue = self.quota_value
self.quota_value = Quota.objects.create(
username=self.full_address, bytes=old_qvalue.bytes,
messages=old_qvalue.messages
)
old_qvalue.delete()
self.rename_dir(old_mail_home)
def delete_dir(self):
@@ -179,11 +200,10 @@ class Mailbox(AdminObject):
def get_quota(self):
"""Get quota limit.
:rtype: int
"""
q = Quota.objects.get(username=self.full_address)
return int(q.bytes / 1048576)
return int(self.quota_value.bytes / 1048576)
def get_quota_in_percent(self):
"""Get current quota usage.
@@ -192,8 +212,9 @@ class Mailbox(AdminObject):
"""
if not self.quota:
return 0
q = Quota.objects.get(username=self.full_address)
return int(q.bytes / float(self.quota * 1048576) * 100)
return int(
self.quota_value.bytes / float(self.quota * 1048576) * 100
)
def post_create(self, creator):
from modoboa.lib.permissions import grant_access_to_object
@@ -209,11 +230,16 @@ class Mailbox(AdminObject):
grant_access_to_object(admin, self.user)
def save(self, *args, **kwargs):
super(Mailbox, self).save(*args, **kwargs)
"""Custom save.
We just make sure a quota record is defined for this mailbox.
"""
try:
q = self.quota_value
except Quota.DoesNotExist:
Quota.objects.create(mbox=self, username=self.full_address)
self.quota_value, created = Quota.objects.get_or_create(
username=self.full_address)
super(Mailbox, self).save(*args, **kwargs)
def delete(self, keepdir=False):
"""Custom delete method
@@ -257,17 +283,6 @@ def mailbox_deleted_handler(sender, **kwargs):
alias.delete()
class Quota(models.Model):
username = models.EmailField(primary_key=True, max_length=254)
bytes = models.BigIntegerField(default=0)
messages = models.IntegerField(default=0)
mbox = models.OneToOneField(Mailbox, related_name="quota_value", null=True)
class Meta:
app_label = 'admin'
class MailboxOperation(models.Model):
mailbox = models.ForeignKey(Mailbox, blank=True, null=True)
type = models.CharField(
@@ -46,6 +46,7 @@ class AccountTestCase(ModoTestCase):
)
mb = Mailbox.objects.get(pk=mb.id)
self.assertEqual(mb.full_address, "pouet@test.com")
self.assertEqual(mb.quota_value.username, "pouet@test.com")
self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.delaccount",
args=[account.id]),
@@ -1,5 +1,4 @@
# coding: utf-8
import datetime
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils import timezone
@@ -42,20 +41,25 @@ class ARmessageForm(forms.ModelForm):
"""Custom fields validaton.
We want to be sure that fromdate < untildate and that they are
both in the future.
both in the future ONLY IF the autoreply is beeing activated.
"""
super(ARmessageForm, self).clean()
if self._errors:
raise forms.ValidationError(self._errors)
if not self.cleaned_data["enabled"]:
return self.cleaned_data
if self.cleaned_data["fromdate"] is not None:
if self.cleaned_data["fromdate"] < timezone.now():
self._errors["fromdate"] = self.error_class([_("This date is over")])
self._errors["fromdate"] = self.error_class(
[_("This date is over")])
del self.cleaned_data['fromdate']
else:
self.cleaned_data['fromdate'] = timezone.now()
if self.cleaned_data["untildate"] is not None:
if self.cleaned_data["untildate"] < timezone.now():
self._errors["untildate"] = self.error_class([_("This date is over")])
self._errors["untildate"] = self.error_class(
[_("This date is over")])
del self.cleaned_data['untildate']
elif 'fromdate' in self.cleaned_data and \
self.cleaned_data['untildate'] < self.cleaned_data['fromdate']:
@@ -20,7 +20,7 @@ function removefilter(evt) {
url: $this.attr("href"),
dataType: 'json'
}).done(function(response) {
history.update(true);
histomanager.update(true);
$("body").notify('success', response, 2000);
});
}
@@ -30,7 +30,7 @@ function removefilter(evt) {
*/
function changefs(evt) {
evt.preventDefault();
history.baseurl($(this).val()).update();
histomanager.baseurl($(this).val()).update();
}
/*
@@ -48,7 +48,7 @@ function init_filters_list() {
* It updates the middle DIV and initializes the user interface.
*/
function loadfs(response) {
curscript = history.getbaseurl();
curscript = histomanager.getbaseurl();
$("#curfset").val(curscript);
$("#set_content").html(response.content);
@@ -78,7 +78,7 @@ function send_command(cmd, extracb) {
extracb(response);
}
if (!response.norefresh) {
history.update(true);
histomanager.update(true);
}
$("body").notify('success', response.respmsg, 2000);
});
@@ -133,7 +133,7 @@ function removefs(evt) {
var $curfset = $("#curfset");
$curfset.find("option[value='" + curscript + "']").remove();
history.baseurl(response.newfs, 1);
histomanager.baseurl(response.newfs, 1);
});
}
@@ -206,7 +206,7 @@ function filterset_created(data) {
}
});
}
history.baseurl(data.url, 1).update(1);
histomanager.baseurl(data.url, 1).update(1);
}
function filtersetform_cb() {
@@ -240,4 +240,4 @@ function fetch_templates(url, container) {
result = data;
});
return result;
}
}
@@ -7,7 +7,7 @@
<script type="text/javascript" src="{{ STATIC_URL }}js/dynamic_input.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}sievefilters/js/jquery.dynfields.js"></script>
<script type="text/javascript">
var history = new History({
var histomanager = new History({
deflocation: "{{ default_script }}",
defcallback: loadfs
});
+2 -4
View File
@@ -95,8 +95,7 @@ class Graphic(object):
class AverageTraffic(Graphic):
"""Average traffic.
"""
title = ugettext_lazy('Average traffic')
vertlabel = ugettext_lazy('msgs/min')
title = ugettext_lazy('Average traffic (msgs/min)')
# Curve definitions
sent = Curve("sent", "lawngreen", ugettext_lazy("sent messages"))
@@ -110,8 +109,7 @@ class AverageTraffic(Graphic):
class AverageTrafficSize(Graphic):
"""Average traffic size.
"""
title = ugettext_lazy('Average normal traffic size')
vertlabel = ugettext_lazy('bytes/min')
title = ugettext_lazy('Average normal traffic size (bytes/min)')
# Curve definitions
size_recv = Curve("size_recv", "orange", ugettext_lazy("received size"))
+15 -5
View File
@@ -218,6 +218,11 @@ class Modifier(ImapEmail):
def _modify_html(self):
pass
@property
def subject(self):
"""Just a shortcut to return a subject in any case."""
return self.Subject if hasattr(self, "Subject") else ""
class ReplyModifier(Modifier):
headernames = ImapEmail.headernames + \
@@ -246,11 +251,11 @@ class ReplyModifier(Modifier):
if self.form.fields["cc"].initial != "":
self.form.fields["cc"].initial += ", "
self.form.fields["cc"].initial += tmp.fulladdress
m = re.match(r"re\s*:\s*.+", self.Subject.lower())
m = re.match(r"re\s*:\s*.+", self.subject.lower())
if m:
self.form.fields["subject"].initial = self.Subject
self.form.fields["subject"].initial = self.subject
else:
self.form.fields["subject"].initial = "Re: %s" % self.Subject
self.form.fields["subject"].initial = "Re: %s" % self.subject
def _modify_plain(self):
super(ReplyModifier, self)._modify_plain()
@@ -264,10 +269,15 @@ class ReplyModifier(Modifier):
class ForwardModifier(Modifier):
"""
Modify a message so it can be forwarded.
"""
def __init__(self, *args, **kwargs):
super(ForwardModifier, self).__init__(*args, **kwargs)
self._header()
self.form.fields["subject"].initial = "Fwd: %s" % self.Subject
self.form.fields["subject"].initial = "Fwd: %s" % self.subject
def __getfunc(self, name):
return getattr(self, "%s_%s" % (name, self.dformat))
@@ -275,7 +285,7 @@ class ForwardModifier(Modifier):
def _header(self):
self.textheader = self.__getfunc("_header_begin")() + "\n"
self.textheader += \
self.__getfunc("_header_line")(_("Subject"), self.Subject)
self.__getfunc("_header_line")(_("Subject"), self.subject)
self.textheader += \
self.__getfunc("_header_line")(_("Date"), self.Date)
for hdr in ["From", "To", "Reply-To"]:
+2 -2
View File
@@ -85,9 +85,9 @@ def parse_date(value, **kwargs):
tmp = email.utils.parsedate_tz(value)
if not tmp:
return value
ndate = datetime.datetime(*(tmp)[:7])
now = datetime.datetime.now()
try:
ndate = datetime.datetime(*(tmp)[:7])
now = datetime.datetime.now()
if now - ndate > datetime.timedelta(7):
return ndate.strftime("%d.%m.%Y %H:%M")
return ndate.strftime("%a %H:%M")
+4 -4
View File
@@ -8,22 +8,22 @@ class EmailSignature(object):
:param user: User object
"""
def __init__(self, user):
self._sig = ""
self._sig = u""
dformat = parameters.get_user(user, "EDITOR")
content = parameters.get_user(user, "SIGNATURE")
if len(content):
getattr(self, "_format_sig_%s" % dformat)(content)
def _format_sig_plain(self, content):
self._sig = """
self._sig = u"""
---
%s""" % content
def _format_sig_html(self, content):
content = re.sub("\n", "<br/>", content)
self._sig = """<br/>
self._sig = u"""<br/>
---<br/>
%s""" % content
def __repr__(self):
def __unicode__(self):
return self._sig
@@ -37,6 +37,7 @@ $(document).ready(function() {
<div id="listing"></div>
{% endblock %}
{% block footerleft %}
{% if quota != -1 %}
<div id="quotabox">
<label class="pull-left navbar-text">{% trans "Mailbox quota" %}</label>
<div id="quotabar" class="pull-left progress {% progress_color quota %}">
@@ -44,5 +45,6 @@ $(document).ready(function() {
</div>
<label id="quotaraw" class="pull-left navbar-text">{{ quota }}%</label>
</div>
{% endif %}
{% endblock %}
+3 -3
View File
@@ -344,14 +344,14 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
def render_compose(request, form, posturl, email=None, insert_signature=False):
editor = parameters.get_user(request.user, "EDITOR")
if email is None:
body = ""
textheader = ""
body = u""
textheader = u""
else:
body = email.body
textheader = email.textheader
if insert_signature:
signature = EmailSignature(request.user)
body += str(signature)
body += unicode(signature)
randid = None
if not "id" in request.GET:
if "compose_mail" in request.session:
+1 -1
View File
@@ -177,7 +177,7 @@ function simple_ajax_form_post(e, options) {
if (opts.reload_mode == 'full') {
window.location.reload();
} else {
history.update(true);
histomanager.update(true);
}
}
if (data) {
+2 -2
View File
@@ -15,7 +15,7 @@
id: bid,
click: $.proxy(this.destroy_box, this)
}).css({
position: "absolute"
position: "fixed"
});
return div;
@@ -101,4 +101,4 @@
top_position: 50
};
})(jQuery);
})(jQuery);
+1 -1
View File
@@ -54,7 +54,7 @@ def read(fname):
setup(
name="modoboa",
version='1.1.4',
version='1.1.5',
url='http://modoboa.org/',
license='MIT',
description="Mail hosting made simple",