diff --git a/doc/extending/events.rst b/doc/extending/events.rst
index 6f4a533e8..af9c1a5b6 100644
--- a/doc/extending/events.rst
+++ b/doc/extending/events.rst
@@ -266,28 +266,6 @@ creator.
* ``owner`` is the original creator
* ``domain`` is the ``Domain`` instance being modified
-ExtDisabled
-===========
-
-Raised just after an extension has been disabled.
-
-*Callback prototype*::
-
- def callback(extension): pass
-
-* ``extension`` is an ``Extension`` instance
-
-ExtEnabled
-==========
-
-Raised just after an extension has been activated.
-
-*Callback prototype*::
-
- def callback(extension): pass
-
-* ``extension`` is an ``Extension`` instance
-
ExtraAccountActions
===================
diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py
index ca94c9604..8a03f0e5d 100644
--- a/modoboa/core/__init__.py
+++ b/modoboa/core/__init__.py
@@ -25,9 +25,6 @@ BASE_EVENTS = [
"AdminMenuDisplay",
"GetStaticContent",
- "ExtEnabled",
- "ExtDisabled",
-
"UserLogin",
"UserLogout",
@@ -58,17 +55,6 @@ def load_core_settings():
events.declare(BASE_EVENTS)
-@events.observe("ExtDisabled")
-def unset_default_topredirection(extension):
- """
- Simple callback to change the default redirection if the
- corresponding extension is being disabled.
- """
- topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION")
- if topredirection == extension.name:
- parameters.save_admin("DEFAULT_TOP_REDIRECTION", "core")
-
-
@events.observe("TopNotifications")
def check_for_new_version(request, include_all):
"""
diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py
index 63c009777..773d5b315 100644
--- a/modoboa/core/app_settings.py
+++ b/modoboa/core/app_settings.py
@@ -9,7 +9,7 @@ from modoboa.lib.form_utils import YesNoField, SeparatorField, InlineRadioSelect
def enabled_applications():
- """Return the list of currently enabled extensions
+ """Return the list of installed extensions.
We check if the table exists before trying to fetch activated
extensions because the admin module is always imported by Django,
@@ -17,19 +17,11 @@ def enabled_applications():
:return: a list
"""
- from modoboa.core.models import Extension
from modoboa.core.extensions import exts_pool
- from modoboa.lib.db_utils import db_table_exists
result = [("user", "user")]
- if db_table_exists("core_extension"):
- exts = Extension.objects.filter(enabled=True)
- for ext in exts:
- extclass = exts_pool.get_extension(ext.name)
- if extclass is None:
- continue
- if extclass.available_for_topredirection:
- result.append((ext.name, ext.name))
+ for extension in exts_pool.list_all():
+ result.append((extension["name"], extension["name"]))
return sorted(result, key=lambda e: e[0])
diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py
index 20e799d94..4e77afc5d 100644
--- a/modoboa/core/commands/deploy.py
+++ b/modoboa/core/commands/deploy.py
@@ -63,10 +63,6 @@ class DeployCommand(Command):
self._parser.add_argument(
'--domain', type=str, default=None,
help='The domain under which you want to deploy modoboa')
- self._parser.add_argument(
- '--extensions', type=str, nargs='*',
- help='Deploy with those extensions already enabled'
- )
self._parser.add_argument(
'--lang', type=str, default="en-us",
help="Set the default language"
@@ -242,11 +238,6 @@ class DeployCommand(Command):
"collectstatic", parsed_args.name, '--noinput'
)
- if parsed_args.extensions:
- self._exec_django_command(
- "manage_extensions", parsed_args.name, *parsed_args.extensions
- )
-
self._exec_django_command(
"set_default_site", parsed_args.name, allowed_host
)
diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl
index cf0518017..aaf648741 100644
--- a/modoboa/core/commands/templates/settings.py.tpl
+++ b/modoboa/core/commands/templates/settings.py.tpl
@@ -86,7 +86,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'modoboa.lib.middleware.AjaxLoginRedirect',
'modoboa.lib.middleware.CommonExceptionCatcher',
- 'modoboa.lib.middleware.ExtControlMiddleware',
'modoboa.lib.middleware.RequestCatcherMiddleware',
)
diff --git a/modoboa/core/extensions.py b/modoboa/core/extensions.py
index 96d3096cf..a67e3c04d 100644
--- a/modoboa/core/extensions.py
+++ b/modoboa/core/extensions.py
@@ -6,11 +6,13 @@ from django.conf.urls import include
class ModoExtension(object):
- """Base extension class.
+ """
+ Base extension class.
Each Modoboa extension must inherit from this class to be
considered as valid.
"""
+
name = None
label = None
version = "NA"
@@ -27,12 +29,6 @@ class ModoExtension(object):
always_active=self.always_active
)
- def init(self):
- pass
-
- def destroy(self):
- pass
-
def load(self):
pass
@@ -45,26 +41,24 @@ class ExtensionsPool(object):
self.extensions = {}
def register_extension(self, ext, show=True):
+ """Register an extension.
+
+ :param ext: a class inheriting from ``Extension``
+ :param show: list the extension or not
+ """
self.extensions[ext.name] = dict(cls=ext, show=show)
def get_extension(self, name):
+ """Retrieve the current instance of an extension."""
if name not in self.extensions:
return None
if "instance" not in self.extensions[name]:
self.extensions[name]["instance"] = self.extensions[name]["cls"]()
return self.extensions[name]["instance"]
- def is_extension_enabled(self, name):
- """Check if an extension is enabled or not."""
- from modoboa.core.models import Extension
-
- if name not in self.extensions:
- return False
- try:
- state = Extension.objects.get(name=name).enabled
- except Extension.DoesNotExist:
- state = False
- return state
+ def is_extension_installed(self, name):
+ """Check if an extension is installed ir not."""
+ return name in settings.MODOBOA_APPS
def get_extension_infos(self, name):
instance = self.get_extension(name)
@@ -73,7 +67,7 @@ class ExtensionsPool(object):
return instance.infos()
def load_all(self):
- """Load all enabled extensions.
+ """Load all defined extensions.
Each extension must be loaded in order to integrate with
Modoboa. Only enabled and special extensions are loaded but
@@ -83,11 +77,9 @@ class ExtensionsPool(object):
:return: a list of url maps
"""
- from modoboa.core.models import Extension
-
result = []
for ext in settings.MODOBOA_APPS:
- __import__(ext)
+ __import__(ext, locals(), globals(), ["modo_extension"])
extname = ext.split('.')[-1]
extinstance = self.get_extension(extname)
if extinstance is None:
@@ -97,25 +89,16 @@ class ExtensionsPool(object):
if extinstance.url is not None else extname
result += [
(r'^%s/' % (baseurl),
- include("%s.urls" % extinstance.__module__,
- namespace=extname))
+ include("{0}.urls".format(ext), namespace=extname))
]
except ImportError:
# No urls for this extension
pass
- if not extinstance.always_active:
- try:
- ext = Extension.objects.get(name=extname)
- if not ext.enabled:
- continue
- except Extension.DoesNotExist:
- continue
extinstance.load()
return result
def list_all(self):
- """List all defined extensions.
- """
+ """List all defined extensions."""
result = []
for extname, extdef in self.extensions.iteritems():
if not extdef["show"]:
diff --git a/modoboa/core/management/commands/clean_unknown_extensions.py b/modoboa/core/management/commands/clean_unknown_extensions.py
deleted file mode 100644
index d0a297fea..000000000
--- a/modoboa/core/management/commands/clean_unknown_extensions.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# coding: utf-8
-"""
-A management command to clean unknown extensions from the
-database.
-
-"""
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.db import connection
-
-from modoboa.core.models import Extension
-
-
-class Command(BaseCommand):
-
- """Command definition."""
-
- help = "Clean unknowns extensions from the database"
-
- def handle(self, *args, **options):
- """Entry point."""
- for extension in Extension.objects.all():
- fullname = "modoboa.extensions.{0}".format(extension.name)
- if fullname not in settings.MODOBOA_APPS:
- extension.delete()
- connection.close()
diff --git a/modoboa/core/management/commands/manage_extensions.py b/modoboa/core/management/commands/manage_extensions.py
deleted file mode 100644
index af90b1302..000000000
--- a/modoboa/core/management/commands/manage_extensions.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# coding: utf-8
-"""
-A management command to enable an extension.
-"""
-from optparse import make_option
-
-from django.conf import settings
-from django.core.management.base import BaseCommand
-
-from modoboa.core.models import Extension
-from . import CloseConnectionMixin
-
-
-class Command(BaseCommand, CloseConnectionMixin):
-
- """Command definition."""
-
- help = "Enable or disable extensions"
-
- option_list = BaseCommand.option_list + (
- make_option(
- "--action", type=str, default="enable",
- help="The action to take on extensions ('enable' or 'disable')"
- ),
- )
-
- def _change_extension_state(self, name, options):
- """Change the state of an extension."""
- newstate = options["action"] == "enable"
- update_db = True
- try:
- extension = Extension.objects.get(name=name)
- except Extension.DoesNotExist:
- extension = Extension.objects.create(name=name, enabled=newstate)
- update_db = False
- else:
- if extension.enabled == newstate:
- return
- if newstate:
- extension.on(update_db)
- else:
- extension.off(update_db)
-
- def handle(self, *args, **options):
- """Entry point."""
- if "all" in args:
- for fullname in settings.MODOBOA_APPS:
- if not fullname.startswith("modoboa.extensions"):
- continue
- extname = fullname.replace("modoboa.extensions.", "")
- self._change_extension_state(extname, options)
- return
-
- for extname in args:
- fullname = "modoboa.extensions.{0}".format(extname)
- if fullname not in settings.MODOBOA_APPS:
- continue
- self._change_extension_state(extname, options)
diff --git a/modoboa/core/migrations/0001_initial.py b/modoboa/core/migrations/0001_initial.py
new file mode 100644
index 000000000..3824558f1
--- /dev/null
+++ b/modoboa/core/migrations/0001_initial.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.utils.timezone
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0001_initial'),
+ ('contenttypes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(unique=True, max_length=254)),
+ ('first_name', models.CharField(max_length=30, blank=True)),
+ ('last_name', models.CharField(max_length=30, blank=True)),
+ ('email', models.EmailField(max_length=254, blank=True)),
+ ('is_staff', models.BooleanField(default=False)),
+ ('is_active', models.BooleanField(default=True)),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
+ ('is_local', models.BooleanField(default=True)),
+ ('password', models.CharField(max_length=256, verbose_name='password')),
+ ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
+ ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
+ ],
+ options={
+ 'ordering': ['username'],
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Extension',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=150)),
+ ('enabled', models.BooleanField(default=False, help_text='Check to enable this extension', verbose_name='enabled')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Log',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('message', models.CharField(max_length=255)),
+ ('level', models.CharField(max_length=15)),
+ ('logger', models.CharField(max_length=30)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ObjectAccess',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('object_id', models.PositiveIntegerField()),
+ ('is_owner', models.BooleanField(default=False)),
+ ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AlterUniqueTogether(
+ name='objectaccess',
+ unique_together=set([('user', 'content_type', 'object_id')]),
+ ),
+ ]
diff --git a/modoboa/core/migrations/0002_delete_extension.py b/modoboa/core/migrations/0002_delete_extension.py
new file mode 100644
index 000000000..073ef44bb
--- /dev/null
+++ b/modoboa/core/migrations/0002_delete_extension.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Extension',
+ ),
+ ]
diff --git a/modoboa/core/migrations/__init__.py b/modoboa/core/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/core/models.py b/modoboa/core/models.py
index 8d4aed543..93df9a416 100644
--- a/modoboa/core/models.py
+++ b/modoboa/core/models.py
@@ -1,11 +1,8 @@
"""Core models."""
import logging
-import os
import re
-import sys
-from django.conf import settings
from django.contrib.auth.hashers import make_password, is_password_usable
from django.contrib.auth.models import (
UserManager, Group, PermissionsMixin
@@ -20,13 +17,11 @@ from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
-from modoboa.core.extensions import exts_pool
from modoboa.core.password_hashers import get_password_hasher
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import (
PermDeniedException, InternalError, BadRequest, Conflict
)
-from modoboa.lib.sysutils import exec_cmd
try:
@@ -438,72 +433,6 @@ class ObjectAccess(models.Model):
)
-class Extension(models.Model):
-
- """A modoboa extension."""
-
- name = models.CharField(max_length=150)
- enabled = models.BooleanField(
- ugettext_lazy('enabled'),
- help_text=ugettext_lazy("Check to enable this extension")
- )
-
- def __init__(self, *args, **kwargs):
- super(Extension, self).__init__(*args, **kwargs)
-
- def __unicode__(self):
- return self.name
-
- def __get_ext_instance(self):
- if not self.name:
- return None
- if hasattr(self, "instance") and self.instance:
- return
- self.instance = exts_pool.get_extension(self.name)
- if self.instance:
- self.__get_ext_dir()
-
- def __get_ext_dir(self):
- modname = self.instance.__module__
- path = os.path.realpath(sys.modules[modname].__file__)
- self.extdir = os.path.dirname(path)
-
- def on(self, update_db=True):
- """Activate this extension."""
- if update_db:
- self.enabled = True
- self.save()
-
- self.__get_ext_instance()
- self.instance.load()
- self.instance.init()
-
- if self.instance.needs_media:
- path = os.path.join(settings.MEDIA_ROOT, self.name)
- exec_cmd("mkdir %s" % path)
-
- events.raiseEvent("ExtEnabled", self)
-
- def off(self, update_db=True):
- """Disable this extension."""
- self.__get_ext_instance()
- if self.instance is None:
- return
- self.instance.destroy()
-
- if update_db:
- self.enabled = False
- self.save()
-
- if self.instance.needs_media:
- path = os.path.join(settings.MEDIA_ROOT, self.name)
- exec_cmd("rm -r %s" % path)
-
- events.raiseEvent("ExtDisabled", self)
-
-reversion.register(Extension)
-
-
class Log(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
message = models.CharField(max_length=255)
diff --git a/modoboa/core/templates/core/extensions.html b/modoboa/core/templates/core/extensions.html
index cc51b3ecb..6415135e3 100644
--- a/modoboa/core/templates/core/extensions.html
+++ b/modoboa/core/templates/core/extensions.html
@@ -1,26 +1,22 @@
-{% load i18n %}{% load url from future %}
-
{% trans "Extensions" %} {% trans "Choose the extension(s) to use" %}
-
-{% trans 'Save' %}
+{% load i18n %}
+{% load url from future %}
+
+{% trans "Extensions" %} {% trans "The extension(s) you are using" %}
+
+
+
+ | {% trans "Name" %} |
+ {% trans "Version" %} |
+ {% trans "Description" %} |
+
+
+
+ {% for extension in extensions %}
+
+ | {{ extension.label }} |
+ {{ extension.version }} |
+ {{ extension.description }} |
+
+ {% endfor %}
+
+
diff --git a/modoboa/core/urls.py b/modoboa/core/urls.py
index 4402f37d2..cf03287fa 100644
--- a/modoboa/core/urls.py
+++ b/modoboa/core/urls.py
@@ -17,8 +17,6 @@ urlpatterns = patterns(
name="parameter_save"),
url(r'^core/extensions/$', 'modoboa.core.views.admin.viewextensions',
name="extension_list"),
- url(r'^core/extensions/save/$', 'modoboa.core.views.admin.saveextensions',
- name="extension_save"),
url(r'^core/info/$', 'modoboa.core.views.admin.information',
name="information"),
url(r'^core/logs/$', 'modoboa.core.views.admin.logs', name="log_list"),
diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py
index b4b36e056..f1844ff76 100644
--- a/modoboa/core/views/admin.py
+++ b/modoboa/core/views/admin.py
@@ -8,7 +8,7 @@ from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
-from modoboa.core.models import Extension, Log
+from modoboa.core.models import Log
from modoboa.core.utils import new_version_available
from modoboa.lib import events, parameters
from modoboa.lib.listing import get_sort_order, get_listing_page
@@ -58,44 +58,12 @@ def viewextensions(request, tplname='core/extensions.html'):
from modoboa.core.extensions import exts_pool
exts = exts_pool.list_all()
- for ext in exts:
- try:
- dbext = Extension.objects.get(name=ext["id"])
- ext["selection"] = dbext.enabled
- except Extension.DoesNotExist:
- dbext = Extension()
- dbext.name = ext["id"]
- dbext.enabled = False
- dbext.save()
- ext["selection"] = False
-
return render_to_json_response({
"callback": "extensions",
"content": _render_to_string(request, tplname, {"extensions": exts})
})
-@login_required
-@user_passes_test(lambda u: u.is_superuser)
-def saveextensions(request):
- """Enable of disable extension(s)."""
- actived_exts = Extension.objects.filter(enabled=True)
- found = []
- for k in request.POST.keys():
- if k.startswith("select_"):
- parts = k.split("_", 1)
- dbext = Extension.objects.get(name=parts[1])
- if dbext not in actived_exts:
- dbext.on()
- else:
- found += [dbext]
- for ext in actived_exts:
- if ext not in found:
- ext.off()
-
- return render_to_json_response(_("Modifications applied."))
-
-
@login_required
@user_passes_test(lambda u: u.is_superuser)
def information(request, tplname="core/information.html"):
diff --git a/modoboa/extensions/admin/__init__.py b/modoboa/extensions/admin/__init__.py
index 1c23ae3d7..e69de29bb 100644
--- a/modoboa/extensions/admin/__init__.py
+++ b/modoboa/extensions/admin/__init__.py
@@ -1,263 +0,0 @@
-# coding: utf-8
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _, ugettext_lazy
-
-from modoboa.core.extensions import ModoExtension, exts_pool
-from modoboa.extensions.admin.models import (
- Domain, DomainAlias, Mailbox, Alias
-)
-from modoboa.lib import parameters, events
-from modoboa.lib.email_utils import split_mailbox
-from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
-
-
-admin_events = [
- "DomainCreated",
- "DomainModified",
- "DomainDeleted",
- "DomainOwnershipRemoved",
- "ExtraDomainEntries",
- "ExtraDomainMenuEntries",
- "ExtraDomainFilters",
- "GetDomainActions",
- "GetDomainModifyLink",
- "CheckDomainName",
-
- "DomainAliasCreated",
- "DomainAliasDeleted",
-
- "MailboxCreated",
- "MailboxDeleted",
- "MailboxModified",
-
- "MailboxAliasCreated",
- "MailboxAliasDeleted",
-
- "ExtraDomainForm",
- "FillDomainInstances",
-
- "ExtraAccountForm",
- "CheckExtraAccountForm",
- "FillAccountInstances",
-
- "ExtraDomainImportHelp",
- "ImportObject"
-]
-
-
-class AdminConsole(ModoExtension):
- name = "admin"
- label = ugettext_lazy("Administration console")
- version = "1.0"
- description = ugettext_lazy(
- "Web based console to manage domains, accounts and aliases"
- )
- always_active = True
-
- def load(self):
- from modoboa.extensions.admin.app_settings import AdminParametersForm
- parameters.register(
- AdminParametersForm, ugettext_lazy("Administration"))
- events.declare(admin_events)
-
- def destroy(self):
- parameters.unregister()
-
-exts_pool.register_extension(AdminConsole, show=False)
-
-
-@events.observe("ExtraUprefsRoutes")
-def extra_routes():
- from django.conf.urls import url
- return [
- url(r'^user/forward/',
- 'modoboa.extensions.admin.views.user.forward',
- name="user_forward"),
- ]
-
-
-@events.observe("AdminMenuDisplay")
-def admin_menu(target, user):
- if target != "top_menu":
- return []
- entries = []
- if user.has_perm("admin.view_domains"):
- entries += [
- {"name": "domains",
- "url": reverse("admin:domain_list"),
- "label": _("Domains")}
- ]
- if user.has_perm("core.add_user") or user.has_perm("admin.add_alias"):
- entries += [
- {"name": "identities",
- "url": reverse("admin:identity_list"),
- "label": _("Identities")},
- ]
- return entries
-
-
-@events.observe("UserMenuDisplay")
-def user_menu(target, user):
- if target != "uprefs_menu":
- return []
- if not user.mailbox_set.count():
- return []
- return [
- {"name": "forward",
- "class": "ajaxnav",
- "url": "forward/",
- "label": ugettext_lazy("Forward")}
- ]
-
-
-@events.observe("RoleChanged")
-def grant_access_to_all_objects(user, role):
- from django.contrib.contenttypes.models import ContentType
- from modoboa.lib.permissions import grant_access_to_objects
- from modoboa.core.models import User
-
- if role != "SuperAdmins":
- return
- grant_access_to_objects(
- user, User.objects.all(),
- ContentType.objects.get_for_model(User)
- )
- grant_access_to_objects(
- user, Domain.objects.all(),
- ContentType.objects.get_for_model(Domain)
- )
- grant_access_to_objects(
- user, DomainAlias.objects.all(),
- ContentType.objects.get_for_model(DomainAlias)
- )
- grant_access_to_objects(
- user, Mailbox.objects.all(),
- ContentType.objects.get_for_model(Mailbox)
- )
- grant_access_to_objects(
- user, Alias.objects.all(),
- ContentType.objects.get_for_model(Alias)
- )
-
-
-@events.observe("AccountExported")
-def export_admin_domains(admin):
- result = [admin.mailbox_set.all()[0].quota] \
- if admin.mailbox_set.count() else ['']
- if admin.group != "DomainAdmins":
- return result
- return result + [dom.name for dom in Domain.objects.get_for_admin(admin)]
-
-
-@events.observe("AccountImported")
-def import_account_mailbox(user, account, row):
- """Handle extra fields when an account is imported.
-
- Expected fields:
-
- email address; quota; [domain; ...]
-
- :param User user: user importing the account
- :param User account: account being imported
- :param list rom: list of fields (strings)
- """
- account.email = row[0].strip()
- if account.email:
- account.save()
- mailbox, domname = split_mailbox(account.email)
- try:
- domain = Domain.objects.get(name=domname)
- except Domain.DoesNotExist:
- raise BadRequest(
- _("Account import failed (%s): domain does not exist"
- % account.username)
- )
- if not user.can_access(domain):
- raise PermDeniedException
- try:
- mb = Mailbox.objects.get(address=mailbox, domain=domain)
- except Mailbox.DoesNotExist:
- pass
- else:
- raise Conflict(_("Mailbox %s already exists" % account.email))
- if len(row) == 1:
- quota = None
- else:
- try:
- quota = int(row[1].strip())
- except ValueError:
- raise BadRequest(
- _("Account import failed (%s): wrong quota value"
- % account.username)
- )
- use_domain_quota = True if not quota else False
- mb = Mailbox(address=mailbox, domain=domain,
- user=account, use_domain_quota=use_domain_quota)
- mb.set_quota(quota, override_rules=user.has_perm("admin.change_domain"))
- mb.save(creator=user)
- if account.group == "DomainAdmins":
- for domname in row[2:]:
- try:
- dom = Domain.objects.get(name=domname.strip())
- except Domain.DoesNotExist:
- continue
- dom.add_admin(account)
-
-
-@events.observe("AccountAutoCreated")
-def account_auto_created(user):
- from modoboa.core.models import User
- from modoboa.extensions.admin.lib import check_if_domain_exists
- from modoboa.lib.permissions import grant_access_to_object
-
- if parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX") == "no":
- return
- localpart, domname = split_mailbox(user.username)
- if user.group != 'SimpleUsers' and domname is None:
- return
- sadmins = User.objects.filter(is_superuser=True)
- try:
- domain = Domain.objects.get(name=domname)
- except Domain.DoesNotExist:
- label = check_if_domain_exists(
- domname, [(DomainAlias, _('domain alias'))])
- if label is not None:
- return
- domain = Domain(name=domname, enabled=True, quota=0)
- domain.save(creator=sadmins[0])
- for su in sadmins[1:]:
- grant_access_to_object(su, domain)
- try:
- mb = Mailbox.objects.get(domain=domain, address=localpart)
- except Mailbox.DoesNotExist:
- mb = Mailbox(
- address=localpart, domain=domain, user=user, use_domain_quota=True
- )
- mb.set_quota(override_rules=True)
- mb.save(creator=sadmins[0])
- for su in sadmins[1:]:
- grant_access_to_object(su, mb)
-
-
-@events.observe("UserLogin")
-def user_logged_in(request, username, password):
- from modoboa.lib.cryptutils import encrypt
-
- if request.user.mailbox_set.count():
- request.session["password"] = encrypt(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]
- if not byuser.can_access(mb):
- raise PermDeniedException
- keep_mb_dir = kwargs.get("keep_mb_dir", True)
- mb.delete(keepdir=keep_mb_dir)
diff --git a/modoboa/extensions/admin/migrations/0001_initial.py b/modoboa/extensions/admin/migrations/0001_initial.py
new file mode 100644
index 000000000..ed6048142
--- /dev/null
+++ b/modoboa/extensions/admin/migrations/0001_initial.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Alias',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('address', models.CharField(help_text="The alias address (without the domain part). For a 'catch-all' address, just enter an * character.", max_length=254, verbose_name='address')),
+ ('extmboxes', models.TextField(blank=True)),
+ ('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
+ ('aliases', models.ManyToManyField(help_text='The aliases this alias points to', to='admin.Alias')),
+ ],
+ options={
+ 'ordering': ['domain__name', 'address'],
+ 'permissions': (('view_aliases', 'View aliases'),),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Domain',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(help_text='The domain name', unique=True, max_length=100, verbose_name='name')),
+ ('quota', models.IntegerField()),
+ ('enabled', models.BooleanField(default=True, help_text='Check to activate this domain', verbose_name='enabled')),
+ ],
+ options={
+ 'ordering': ['name'],
+ 'permissions': (('view_domain', 'View domain'), ('view_domains', 'View domains')),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='DomainAlias',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
+ ('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
+ ],
+ options={
+ 'permissions': (('view_domaliases', 'View domain aliases'),),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Mailbox',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('address', models.CharField(help_text='Mailbox address (without the @domain.tld part)', max_length=252, verbose_name='address')),
+ ('quota', models.PositiveIntegerField()),
+ ('use_domain_quota', models.BooleanField(default=False)),
+ ],
+ options={
+ 'permissions': (('view_mailboxes', 'View mailboxes'),),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='MailboxOperation',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('type', models.CharField(max_length=20, choices=[(b'rename', b'rename'), (b'delete', b'delete')])),
+ ('argument', models.TextField()),
+ ('mailbox', models.ForeignKey(blank=True, to='admin.Mailbox', null=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ObjectDates',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('creation', models.DateTimeField(auto_now_add=True)),
+ ('last_modification', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Quota',
+ fields=[
+ ('username', models.EmailField(max_length=254, serialize=False, primary_key=True)),
+ ('bytes', models.BigIntegerField(default=0)),
+ ('messages', models.IntegerField(default=0)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='mailbox',
+ name='dates',
+ field=models.ForeignKey(to='admin.ObjectDates'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='mailbox',
+ name='domain',
+ field=models.ForeignKey(to='admin.Domain'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='mailbox',
+ name='user',
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='domainalias',
+ name='dates',
+ field=models.ForeignKey(to='admin.ObjectDates'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='domainalias',
+ name='target',
+ field=models.ForeignKey(verbose_name='target', to='admin.Domain', help_text='The domain this alias points to'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='domain',
+ name='dates',
+ field=models.ForeignKey(to='admin.ObjectDates'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='alias',
+ name='dates',
+ field=models.ForeignKey(to='admin.ObjectDates'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='alias',
+ name='domain',
+ field=models.ForeignKey(to='admin.Domain'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='alias',
+ name='mboxes',
+ field=models.ManyToManyField(help_text='The mailboxes this alias points to', to='admin.Mailbox', verbose_name='mailboxes'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='alias',
+ unique_together=set([('address', 'domain')]),
+ ),
+ ]
diff --git a/modoboa/extensions/admin/migrations/__init__.py b/modoboa/extensions/admin/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/admin/models/alias.py b/modoboa/extensions/admin/models/alias.py
index 04c154603..646dab326 100644
--- a/modoboa/extensions/admin/models/alias.py
+++ b/modoboa/extensions/admin/models/alias.py
@@ -38,7 +38,8 @@ class Alias(AdminObject):
extmboxes = models.TextField(blank=True)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
- help_text=ugettext_lazy("Check to activate this alias")
+ help_text=ugettext_lazy("Check to activate this alias"),
+ default=True
)
_objectname = 'MailboxAlias'
diff --git a/modoboa/extensions/admin/models/domain.py b/modoboa/extensions/admin/models/domain.py
index 20b5d0c1e..8d996def4 100644
--- a/modoboa/extensions/admin/models/domain.py
+++ b/modoboa/extensions/admin/models/domain.py
@@ -28,14 +28,15 @@ class DomainManager(Manager):
class Domain(AdminObject):
- """Mail domain.
- """
+ """Mail domain."""
+
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.IntegerField()
enabled = models.BooleanField(
ugettext_lazy('enabled'),
- help_text=ugettext_lazy("Check to activate this domain")
+ help_text=ugettext_lazy("Check to activate this domain"),
+ default=True
)
owners = generic.GenericRelation(ObjectAccess)
diff --git a/modoboa/extensions/admin/models/domain_alias.py b/modoboa/extensions/admin/models/domain_alias.py
index a0ab23bfc..ba784a3cb 100644
--- a/modoboa/extensions/admin/models/domain_alias.py
+++ b/modoboa/extensions/admin/models/domain_alias.py
@@ -11,6 +11,9 @@ from modoboa.lib.exceptions import BadRequest, Conflict
class DomainAlias(AdminObject):
+
+ """Domain aliases."""
+
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The alias name"))
target = models.ForeignKey(
@@ -19,7 +22,8 @@ class DomainAlias(AdminObject):
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
- help_text=ugettext_lazy("Check to activate this alias")
+ help_text=ugettext_lazy("Check to activate this alias"),
+ default=True
)
class Meta:
diff --git a/modoboa/extensions/admin/modo_extension.py b/modoboa/extensions/admin/modo_extension.py
new file mode 100644
index 000000000..fdbd41c80
--- /dev/null
+++ b/modoboa/extensions/admin/modo_extension.py
@@ -0,0 +1,261 @@
+"""Registration with Modoboa takes place here."""
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _, ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.extensions.admin.models import (
+ Domain, DomainAlias, Mailbox, Alias
+)
+from modoboa.lib import parameters, events
+from modoboa.lib.email_utils import split_mailbox
+from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
+
+
+ADMIN_EVENTS = [
+ "DomainCreated",
+ "DomainModified",
+ "DomainDeleted",
+ "DomainOwnershipRemoved",
+ "ExtraDomainEntries",
+ "ExtraDomainMenuEntries",
+ "ExtraDomainFilters",
+ "GetDomainActions",
+ "GetDomainModifyLink",
+ "CheckDomainName",
+
+ "DomainAliasCreated",
+ "DomainAliasDeleted",
+
+ "MailboxCreated",
+ "MailboxDeleted",
+ "MailboxModified",
+
+ "MailboxAliasCreated",
+ "MailboxAliasDeleted",
+
+ "ExtraDomainForm",
+ "FillDomainInstances",
+
+ "ExtraAccountForm",
+ "CheckExtraAccountForm",
+ "FillAccountInstances",
+
+ "ExtraDomainImportHelp",
+ "ImportObject"
+]
+
+
+class AdminConsole(ModoExtension):
+ name = "admin"
+ label = ugettext_lazy("Administration console")
+ version = "1.0"
+ description = ugettext_lazy(
+ "Web based console to manage domains, accounts and aliases"
+ )
+ always_active = True
+
+ def load(self):
+ from modoboa.extensions.admin.app_settings import AdminParametersForm
+ parameters.register(
+ AdminParametersForm, ugettext_lazy("Administration"))
+ events.declare(ADMIN_EVENTS)
+
+exts_pool.register_extension(AdminConsole)
+
+
+@events.observe("ExtraUprefsRoutes")
+def extra_routes():
+ from django.conf.urls import url
+ return [
+ url(r'^user/forward/',
+ 'modoboa.extensions.admin.views.user.forward',
+ name="user_forward"),
+ ]
+
+
+@events.observe("AdminMenuDisplay")
+def admin_menu(target, user):
+ if target != "top_menu":
+ return []
+ entries = []
+ if user.has_perm("admin.view_domains"):
+ entries += [
+ {"name": "domains",
+ "url": reverse("admin:domain_list"),
+ "label": _("Domains")}
+ ]
+ if user.has_perm("core.add_user") or user.has_perm("admin.add_alias"):
+ entries += [
+ {"name": "identities",
+ "url": reverse("admin:identity_list"),
+ "label": _("Identities")},
+ ]
+ return entries
+
+
+@events.observe("UserMenuDisplay")
+def user_menu(target, user):
+ if target != "uprefs_menu":
+ return []
+ if not user.mailbox_set.count():
+ return []
+ return [
+ {"name": "forward",
+ "class": "ajaxnav",
+ "url": "forward/",
+ "label": ugettext_lazy("Forward")}
+ ]
+
+
+@events.observe("RoleChanged")
+def grant_access_to_all_objects(user, role):
+ from django.contrib.contenttypes.models import ContentType
+ from modoboa.lib.permissions import grant_access_to_objects
+ from modoboa.core.models import User
+
+ if role != "SuperAdmins":
+ return
+ grant_access_to_objects(
+ user, User.objects.all(),
+ ContentType.objects.get_for_model(User)
+ )
+ grant_access_to_objects(
+ user, Domain.objects.all(),
+ ContentType.objects.get_for_model(Domain)
+ )
+ grant_access_to_objects(
+ user, DomainAlias.objects.all(),
+ ContentType.objects.get_for_model(DomainAlias)
+ )
+ grant_access_to_objects(
+ user, Mailbox.objects.all(),
+ ContentType.objects.get_for_model(Mailbox)
+ )
+ grant_access_to_objects(
+ user, Alias.objects.all(),
+ ContentType.objects.get_for_model(Alias)
+ )
+
+
+@events.observe("AccountExported")
+def export_admin_domains(admin):
+ result = [admin.mailbox_set.all()[0].quota] \
+ if admin.mailbox_set.count() else ['']
+ if admin.group != "DomainAdmins":
+ return result
+ return result + [dom.name for dom in Domain.objects.get_for_admin(admin)]
+
+
+@events.observe("AccountImported")
+def import_account_mailbox(user, account, row):
+ """Handle extra fields when an account is imported.
+
+ Expected fields:
+
+ email address; quota; [domain; ...]
+
+ :param User user: user importing the account
+ :param User account: account being imported
+ :param list rom: list of fields (strings)
+ """
+ account.email = row[0].strip()
+ if account.email:
+ account.save()
+ mailbox, domname = split_mailbox(account.email)
+ try:
+ domain = Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ raise BadRequest(
+ _("Account import failed (%s): domain does not exist"
+ % account.username)
+ )
+ if not user.can_access(domain):
+ raise PermDeniedException
+ try:
+ mb = Mailbox.objects.get(address=mailbox, domain=domain)
+ except Mailbox.DoesNotExist:
+ pass
+ else:
+ raise Conflict(_("Mailbox %s already exists" % account.email))
+ if len(row) == 1:
+ quota = None
+ else:
+ try:
+ quota = int(row[1].strip())
+ except ValueError:
+ raise BadRequest(
+ _("Account import failed (%s): wrong quota value"
+ % account.username)
+ )
+ use_domain_quota = True if not quota else False
+ mb = Mailbox(address=mailbox, domain=domain,
+ user=account, use_domain_quota=use_domain_quota)
+ mb.set_quota(quota, override_rules=user.has_perm("admin.change_domain"))
+ mb.save(creator=user)
+ if account.group == "DomainAdmins":
+ for domname in row[2:]:
+ try:
+ dom = Domain.objects.get(name=domname.strip())
+ except Domain.DoesNotExist:
+ continue
+ dom.add_admin(account)
+
+
+@events.observe("AccountAutoCreated")
+def account_auto_created(user):
+ from modoboa.core.models import User
+ from modoboa.extensions.admin.lib import check_if_domain_exists
+ from modoboa.lib.permissions import grant_access_to_object
+
+ if parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX") == "no":
+ return
+ localpart, domname = split_mailbox(user.username)
+ if user.group != 'SimpleUsers' and domname is None:
+ return
+ sadmins = User.objects.filter(is_superuser=True)
+ try:
+ domain = Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ label = check_if_domain_exists(
+ domname, [(DomainAlias, _('domain alias'))])
+ if label is not None:
+ return
+ domain = Domain(name=domname, enabled=True, quota=0)
+ domain.save(creator=sadmins[0])
+ for su in sadmins[1:]:
+ grant_access_to_object(su, domain)
+ try:
+ mb = Mailbox.objects.get(domain=domain, address=localpart)
+ except Mailbox.DoesNotExist:
+ mb = Mailbox(
+ address=localpart, domain=domain, user=user, use_domain_quota=True
+ )
+ mb.set_quota(override_rules=True)
+ mb.save(creator=sadmins[0])
+ for su in sadmins[1:]:
+ grant_access_to_object(su, mb)
+
+
+@events.observe("UserLogin")
+def user_logged_in(request, username, password):
+ from modoboa.lib.cryptutils import encrypt
+
+ if request.user.mailbox_set.count():
+ request.session["password"] = encrypt(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]
+ if not byuser.can_access(mb):
+ raise PermDeniedException
+ keep_mb_dir = kwargs.get("keep_mb_dir", True)
+ mb.delete(keepdir=keep_mb_dir)
diff --git a/modoboa/extensions/amavis/__init__.py b/modoboa/extensions/amavis/__init__.py
index 552e006dd..e69de29bb 100644
--- a/modoboa/extensions/amavis/__init__.py
+++ b/modoboa/extensions/amavis/__init__.py
@@ -1,61 +0,0 @@
-# coding: utf-8
-"""
-Amavis management frontend.
-
-Provides:
-
-* SQL quarantine management
-* Per-domain settings
-
-"""
-import sys
-from django.utils.translation import ugettext_lazy
-from modoboa.lib import events, parameters
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-
-class Amavis(ModoExtension):
- name = "amavis"
- label = "Amavis frontend"
- version = "1.0"
- description = ugettext_lazy("Simple amavis management frontend")
- url = "quarantine"
- available_for_topredirection = True
-
- def init(self):
- """Init function
-
- Only run once, when the extension is enabled. We create records
- for existing domains to let Amavis consider them local.
- """
- from modoboa.extensions.admin.models import Domain
- 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:
- 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_extension()
- parameters.unregister()
-
-exts_pool.register_extension(Amavis)
diff --git a/modoboa/extensions/amavis/migrations/0001_initial.py b/modoboa/extensions/amavis/migrations/0001_initial.py
new file mode 100644
index 000000000..dea55f387
--- /dev/null
+++ b/modoboa/extensions/amavis/migrations/0001_initial.py
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Maddr',
+ fields=[
+ ('partition_tag', models.IntegerField(unique=True, null=True, blank=True)),
+ ('id', models.BigIntegerField(serialize=False, primary_key=True)),
+ ('email', models.CharField(unique=True, max_length=255)),
+ ('domain', models.CharField(max_length=765)),
+ ],
+ options={
+ 'db_table': 'maddr',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Mailaddr',
+ fields=[
+ ('id', models.IntegerField(serialize=False, primary_key=True)),
+ ('priority', models.IntegerField()),
+ ('email', models.CharField(unique=True, max_length=255)),
+ ],
+ options={
+ 'db_table': 'mailaddr',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Msgs',
+ fields=[
+ ('partition_tag', models.IntegerField(null=True, blank=True)),
+ ('mail_id', models.CharField(max_length=12, serialize=False, primary_key=True)),
+ ('secret_id', models.CharField(max_length=12, blank=True)),
+ ('am_id', models.CharField(max_length=60)),
+ ('time_num', models.IntegerField()),
+ ('time_iso', models.CharField(max_length=48)),
+ ('policy', models.CharField(max_length=765, blank=True)),
+ ('client_addr', models.CharField(max_length=765, blank=True)),
+ ('size', models.IntegerField()),
+ ('originating', models.CharField(max_length=3)),
+ ('content', models.CharField(max_length=1, blank=True)),
+ ('quar_type', models.CharField(max_length=1, blank=True)),
+ ('quar_loc', models.CharField(max_length=255, blank=True)),
+ ('dsn_sent', models.CharField(max_length=3, blank=True)),
+ ('spam_level', models.FloatField(null=True, blank=True)),
+ ('message_id', models.CharField(max_length=765, blank=True)),
+ ('from_addr', models.CharField(max_length=765, blank=True)),
+ ('subject', models.CharField(max_length=765, blank=True)),
+ ('host', models.CharField(max_length=765)),
+ ],
+ options={
+ 'db_table': 'msgs',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Msgrcpt',
+ fields=[
+ ('partition_tag', models.IntegerField(null=True, blank=True)),
+ ('mail', models.ForeignKey(primary_key=True, serialize=False, to='amavis.Msgs')),
+ ('rseqnum', models.IntegerField(default=0)),
+ ('is_local', models.CharField(max_length=3)),
+ ('content', models.CharField(max_length=3)),
+ ('ds', models.CharField(max_length=3)),
+ ('rs', models.CharField(max_length=3)),
+ ('bl', models.CharField(max_length=3, blank=True)),
+ ('wl', models.CharField(max_length=3, blank=True)),
+ ('bspam_level', models.FloatField(null=True, blank=True)),
+ ('smtp_resp', models.CharField(max_length=765, blank=True)),
+ ],
+ options={
+ 'db_table': 'msgrcpt',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Policy',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('policy_name', models.CharField(max_length=32, blank=True)),
+ ('virus_lover', models.CharField(max_length=3, null=True, blank=True)),
+ ('spam_lover', models.CharField(max_length=3, null=True, blank=True)),
+ ('unchecked_lover', models.CharField(max_length=3, null=True, blank=True)),
+ ('banned_files_lover', models.CharField(max_length=3, null=True, blank=True)),
+ ('bad_header_lover', models.CharField(max_length=3, null=True, blank=True)),
+ ('bypass_virus_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass virus checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Virus filter')),
+ ('bypass_spam_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass spam checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Spam filter')),
+ ('bypass_banned_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass banned checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Banned filter')),
+ ('bypass_header_checks', models.CharField(max_length=3, null=True, blank=True)),
+ ('virus_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('spam_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('banned_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('unchecked_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('bad_header_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('clean_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('archive_quarantine_to', models.CharField(max_length=192, null=True, blank=True)),
+ ('spam_tag_level', models.FloatField(null=True, blank=True)),
+ ('spam_tag2_level', models.FloatField(null=True, blank=True)),
+ ('spam_tag3_level', models.FloatField(null=True, blank=True)),
+ ('spam_kill_level', models.FloatField(null=True, blank=True)),
+ ('spam_dsn_cutoff_level', models.FloatField(null=True, blank=True)),
+ ('spam_quarantine_cutoff_level', models.FloatField(null=True, blank=True)),
+ ('addr_extension_virus', models.CharField(max_length=192, null=True, blank=True)),
+ ('addr_extension_spam', models.CharField(max_length=192, null=True, blank=True)),
+ ('addr_extension_banned', models.CharField(max_length=192, null=True, blank=True)),
+ ('addr_extension_bad_header', models.CharField(max_length=192, null=True, blank=True)),
+ ('warnvirusrecip', models.CharField(max_length=3, null=True, blank=True)),
+ ('warnbannedrecip', models.CharField(max_length=3, null=True, blank=True)),
+ ('warnbadhrecip', models.CharField(max_length=3, null=True, blank=True)),
+ ('newvirus_admin', models.CharField(max_length=192, null=True, blank=True)),
+ ('virus_admin', models.CharField(max_length=192, null=True, blank=True)),
+ ('banned_admin', models.CharField(max_length=192, null=True, blank=True)),
+ ('bad_header_admin', models.CharField(max_length=192, null=True, blank=True)),
+ ('spam_admin', models.CharField(max_length=192, null=True, blank=True)),
+ ('spam_subject_tag', models.CharField(max_length=192, null=True, blank=True)),
+ ('spam_subject_tag2', models.CharField(default=None, max_length=192, blank=True, help_text="Modify spam subject using the specified text. Choose 'default' to use global settings.", null=True, verbose_name='Spam marker')),
+ ('spam_subject_tag3', models.CharField(max_length=192, null=True, blank=True)),
+ ('message_size_limit', models.IntegerField(null=True, blank=True)),
+ ('banned_rulenames', models.CharField(max_length=192, null=True, blank=True)),
+ ('disclaimer_options', models.CharField(max_length=192, null=True, blank=True)),
+ ('forward_method', models.CharField(max_length=192, null=True, blank=True)),
+ ('sa_userconf', models.CharField(max_length=192, null=True, blank=True)),
+ ('sa_username', models.CharField(max_length=192, null=True, blank=True)),
+ ],
+ options={
+ 'db_table': 'policy',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Quarantine',
+ fields=[
+ ('partition_tag', models.IntegerField(null=True, blank=True)),
+ ('mail', models.ForeignKey(primary_key=True, serialize=False, to='amavis.Msgs')),
+ ('chunk_ind', models.IntegerField()),
+ ('mail_text', models.TextField()),
+ ],
+ options={
+ 'ordering': ['-mail__time_num'],
+ 'db_table': 'quarantine',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Users',
+ fields=[
+ ('id', models.AutoField(serialize=False, primary_key=True)),
+ ('priority', models.IntegerField()),
+ ('email', models.CharField(unique=True, max_length=255)),
+ ('fullname', models.CharField(max_length=765, blank=True)),
+ ],
+ options={
+ 'db_table': 'users',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Wblist',
+ fields=[
+ ('rid', models.IntegerField(serialize=False, primary_key=True)),
+ ('sid', models.IntegerField(primary_key=True)),
+ ('wb', models.CharField(max_length=30)),
+ ],
+ options={
+ 'db_table': 'wblist',
+ 'managed': False,
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/modoboa/extensions/amavis/migrations/0002_load_initial_data.py b/modoboa/extensions/amavis/migrations/0002_load_initial_data.py
new file mode 100644
index 000000000..398696173
--- /dev/null
+++ b/modoboa/extensions/amavis/migrations/0002_load_initial_data.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+from modoboa.core.extensions import exts_pool
+
+
+def load_initial_data(apps, schema_editor):
+ """Load initial data."""
+ Domain = apps.get_model("admin", "Domain")
+ Policy = apps.get_model("amavis", "Policy")
+ Users = apps.get_model("amavis", "Users")
+
+ for dom in Domain.objects.all():
+ name = "@{0}".format(dom.name)
+ policy, created = Policy.objects.get_or_create(policy_name=name[:32])
+ Users.objects.get_or_create(
+ email=name, fullname=name, priority=7, policy=policy)
+ for dalias in dom.domainalias_set.all():
+ name = "@{0}".format(dalias.name)
+ Users.objects.get_or_create(
+ email=name, fullname=name, priority=7, policy=policy)
+
+ if not exts_pool.is_extension_installed(
+ "modoboa.extensions.postfix_relay_domains"):
+ return
+
+ RelayDomain = apps.get_model("postfix_relay_domains", "RelayDomain")
+
+ for dom in RelayDomain.objects.all():
+ name = "@{0}".format(dom.name)
+ policy, created = Policy.objects.get_or_create(policy_name=name[:32])
+ Users.objects.get_or_create(
+ email=name, fullname=name, priority=7, policy=policy)
+ for rdalias in dom.relaydomainalias_set.all():
+ name = "@{0}".format(rdalias.name)
+ Users.objects.get_or_create(
+ email=name, fullname=name, priority=7, policy=policy)
+
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('amavis', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(load_initial_data)
+ ]
diff --git a/modoboa/extensions/amavis/migrations/__init__.py b/modoboa/extensions/amavis/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/amavis/modo_extension.py b/modoboa/extensions/amavis/modo_extension.py
new file mode 100644
index 000000000..3919be8e4
--- /dev/null
+++ b/modoboa/extensions/amavis/modo_extension.py
@@ -0,0 +1,34 @@
+# coding: utf-8
+"""
+Amavis management frontend.
+
+Provides:
+
+* SQL quarantine management
+* Per-domain settings
+
+"""
+import sys
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import parameters
+
+
+class Amavis(ModoExtension):
+ name = "amavis"
+ label = "Amavis frontend"
+ version = "1.0"
+ description = ugettext_lazy("Simple amavis management frontend")
+ url = "quarantine"
+ available_for_topredirection = True
+
+ 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
+
+exts_pool.register_extension(Amavis)
diff --git a/modoboa/extensions/limits/__init__.py b/modoboa/extensions/limits/__init__.py
index 7119b7077..e69de29bb 100644
--- a/modoboa/extensions/limits/__init__.py
+++ b/modoboa/extensions/limits/__init__.py
@@ -1,70 +0,0 @@
-# coding: utf-8
-
-"""
-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_lazy
-from modoboa.lib import events, parameters
-from modoboa.lib.permissions import add_permissions_to_group
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-levents = [
- 'GetExtraLimitTemplates'
-]
-
-
-class Limits(ModoExtension):
- name = "limits"
- label = "Limits"
- version = "1.0"
- description = ugettext_lazy(
- "Per administrator resources to limit the number of objects "
- "they can create"
- )
-
- def init(self):
- from modoboa.core.models import User
- from modoboa.extensions.admin.models import Domain
-
- ct = ContentType.objects.get(app_label="admin", model="domain")
- dagrp = Group.objects.get(name="DomainAdmins")
-
- grp = Group(name="Resellers")
- grp.save()
- grp.permissions.add(*dagrp.permissions.all())
-
- ct = ContentType.objects.get_for_model(Domain)
- add_permissions_to_group(
- "Resellers", [("admin", "domain", "view_domains"),
- ("admin", "domain", "add_domain"),
- ("admin", "domain", "change_domain"),
- ("admin", "domain", "delete_domain")]
- )
-
- for user in User.objects.filter(groups__name='DomainAdmins'):
- try:
- controls.create_pool(user)
- except IntegrityError:
- pass
-
- 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)
diff --git a/modoboa/extensions/limits/migrations/0001_initial.py b/modoboa/extensions/limits/migrations/0001_initial.py
new file mode 100644
index 000000000..82ed516e3
--- /dev/null
+++ b/modoboa/extensions/limits/migrations/0001_initial.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Limit',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('curvalue', models.IntegerField(default=0)),
+ ('maxvalue', models.IntegerField(default=-2)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='LimitsPool',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='limit',
+ name='pool',
+ field=models.ForeignKey(to='limits.LimitsPool'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='limit',
+ unique_together=set([('name', 'pool')]),
+ ),
+ ]
diff --git a/modoboa/extensions/limits/migrations/0002_load_initial_data.py b/modoboa/extensions/limits/migrations/0002_load_initial_data.py
new file mode 100644
index 000000000..acedaf0d2
--- /dev/null
+++ b/modoboa/extensions/limits/migrations/0002_load_initial_data.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import IntegrityError, models, migrations
+
+from modoboa.lib.migration_tools import add_permissions_to_group
+from modoboa.extensions.limits import controls
+
+
+def load_initial_data(apps, schema_editor):
+ """Load initial data."""
+ ContentType = apps.get_model("contenttypes", "ContentType")
+ Group = apps.get_model("auth", "Group")
+ User = apps.get_model("core", "User")
+ Domain = apps.get_model("admin", "Domain")
+
+ ct = ContentType.objects.get(app_label="admin", model="domain")
+ dagrp = Group.objects.get(name="DomainAdmins")
+
+ grp = Group(name="Resellers")
+ grp.save()
+ grp.permissions.add(*dagrp.permissions.all())
+
+ ct = ContentType.objects.get_for_model(Domain)
+ add_permissions_to_group(
+ apps, "Resellers", [("admin", "domain", "view_domains"),
+ ("admin", "domain", "add_domain"),
+ ("admin", "domain", "change_domain"),
+ ("admin", "domain", "delete_domain")]
+ )
+
+ for user in User.objects.filter(groups__name='DomainAdmins'):
+ try:
+ controls.create_pool(user)
+ except IntegrityError:
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('limits', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(load_initial_data)
+ ]
diff --git a/modoboa/extensions/limits/migrations/__init__.py b/modoboa/extensions/limits/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/limits/modo_extension.py b/modoboa/extensions/limits/modo_extension.py
new file mode 100644
index 000000000..f98433e30
--- /dev/null
+++ b/modoboa/extensions/limits/modo_extension.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+"""
+The *limits* extension
+----------------------
+"""
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import events, parameters
+
+
+EVENTS = [
+ 'GetExtraLimitTemplates'
+]
+
+
+class Limits(ModoExtension):
+ name = "limits"
+ label = "Limits"
+ version = "1.0"
+ description = ugettext_lazy(
+ "Per administrator resources to limit the number of objects "
+ "they can create"
+ )
+
+ 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(EVENTS)
+ from modoboa.extensions.limits import general_callbacks
+
+exts_pool.register_extension(Limits)
diff --git a/modoboa/extensions/postfix_autoreply/__init__.py b/modoboa/extensions/postfix_autoreply/__init__.py
index 10578ca40..e69de29bb 100644
--- a/modoboa/extensions/postfix_autoreply/__init__.py
+++ b/modoboa/extensions/postfix_autoreply/__init__.py
@@ -1,77 +0,0 @@
-# coding: utf-8
-"""
-Postfix auto-replies plugin.
-
-This module provides a way to integrate Modoboa auto-reply
-functionality into Postfix.
-
-"""
-import sys
-
-from django.utils.translation import ugettext_lazy
-
-from modoboa.core.extensions import ModoExtension, exts_pool
-from modoboa.extensions.postfix_autoreply.models import (
- Transport, Alias
-)
-from modoboa.lib import events, parameters
-
-
-class PostfixAutoreply(ModoExtension):
-
- """
- Auto-reply (vacation) functionality using Postfix.
-
- """
- name = "postfix_autoreply"
- label = "Postfix autoreply"
- version = "1.0"
- description = ugettext_lazy(
- "Auto-reply (vacation) functionality using Postfix")
-
- def init(self):
- from modoboa.extensions.admin.models import Domain
- from modoboa.extensions.postfix_autoreply.general_callbacks \
- import onDomainCreated, onMailboxCreated
-
- for dom in Domain.objects.all():
- try:
- Transport.objects.get(domain="autoreply.%s" % dom.name)
- except Transport.DoesNotExist:
- onDomainCreated(None, dom)
- else:
- continue
-
- for mb in dom.mailbox_set.all():
- try:
- Alias.objects.get(full_address=mb.full_address)
- except Alias.DoesNotExist:
- 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
- modname = "modoboa.extensions.postfix_autoreply.general_callbacks"
- if modname in sys.modules:
- reload(general_callbacks)
-
- def destroy(self):
- events.unregister_extension()
- parameters.unregister()
-
-exts_pool.register_extension(PostfixAutoreply)
-
-
-@events.observe("ExtraUprefsRoutes")
-def extra_routes():
- from django.conf.urls import url
-
- return [
- url(r'^user/autoreply/$',
- 'modoboa.extensions.postfix_autoreply.views.autoreply',
- name="autoreply")
- ]
diff --git a/modoboa/extensions/postfix_autoreply/migrations/0001_initial.py b/modoboa/extensions/postfix_autoreply/migrations/0001_initial.py
new file mode 100644
index 000000000..4bb61e930
--- /dev/null
+++ b/modoboa/extensions/postfix_autoreply/migrations/0001_initial.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '__first__'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Alias',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('full_address', models.CharField(max_length=255)),
+ ('autoreply_address', models.CharField(max_length=255)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ARhistoric',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('last_sent', models.DateTimeField(auto_now=True)),
+ ('sender', models.CharField(max_length=254)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ARmessage',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('subject', models.CharField(help_text='The subject that will appear in sent emails', max_length=255, verbose_name='subject')),
+ ('content', models.TextField(help_text='The content that will appear in sent emails', verbose_name='content')),
+ ('enabled', models.BooleanField(default=False, help_text='Activate/Deactivate your auto reply', verbose_name='enabled')),
+ ('fromdate', models.DateTimeField(default=django.utils.timezone.now)),
+ ('untildate', models.DateTimeField(null=True, blank=True)),
+ ('mbox', models.ForeignKey(to='admin.Mailbox')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Transport',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('domain', models.CharField(max_length=300)),
+ ('method', models.CharField(max_length=255)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='arhistoric',
+ name='armessage',
+ field=models.ForeignKey(to='postfix_autoreply.ARmessage'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='arhistoric',
+ unique_together=set([('armessage', 'sender')]),
+ ),
+ ]
diff --git a/modoboa/extensions/postfix_autoreply/migrations/0002_load_initial_data.py b/modoboa/extensions/postfix_autoreply/migrations/0002_load_initial_data.py
new file mode 100644
index 000000000..d98c2b720
--- /dev/null
+++ b/modoboa/extensions/postfix_autoreply/migrations/0002_load_initial_data.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+from modoboa.extensions.postfix_autoreply.general_callbacks import (
+ onDomainCreated, onMailboxCreated
+)
+
+
+def load_initial_data(apps, schema_editor):
+ """Load initial data."""
+ Domain = apps.get_model("admin", "Domain")
+ Transport = apps.get_model("postfix_autoreply", "Transport")
+ Alias = apps.get_model("postfix_autoreply", "Alias")
+
+ for dom in Domain.objects.all():
+ try:
+ Transport.objects.get(domain="autoreply.%s" % dom.name)
+ except Transport.DoesNotExist:
+ onDomainCreated(None, dom)
+ else:
+ continue
+
+ for mb in dom.mailbox_set.all():
+ try:
+ Alias.objects.get(full_address=mb.full_address)
+ except Alias.DoesNotExist:
+ onMailboxCreated(None, mb)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('postfix_autoreply', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(load_initial_data)
+ ]
diff --git a/modoboa/extensions/postfix_autoreply/migrations/__init__.py b/modoboa/extensions/postfix_autoreply/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/postfix_autoreply/models.py b/modoboa/extensions/postfix_autoreply/models.py
index ad8c1e03a..0c53a163b 100644
--- a/modoboa/extensions/postfix_autoreply/models.py
+++ b/modoboa/extensions/postfix_autoreply/models.py
@@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
+
from modoboa.extensions.admin.models import Mailbox
@@ -26,7 +27,8 @@ class ARmessage(models.Model):
)
enabled = models.BooleanField(
_('enabled'),
- help_text=_("Activate/Deactivate your auto reply")
+ help_text=_("Activate/Deactivate your auto reply"),
+ default=False
)
fromdate = models.DateTimeField(default=timezone.now)
untildate = models.DateTimeField(null=True, blank=True)
diff --git a/modoboa/extensions/postfix_autoreply/modo_extension.py b/modoboa/extensions/postfix_autoreply/modo_extension.py
new file mode 100644
index 000000000..fa38fd894
--- /dev/null
+++ b/modoboa/extensions/postfix_autoreply/modo_extension.py
@@ -0,0 +1,47 @@
+# coding: utf-8
+"""
+Postfix auto-replies plugin.
+
+This module provides a way to integrate Modoboa auto-reply
+functionality into Postfix.
+
+"""
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import events, parameters
+
+
+class PostfixAutoreply(ModoExtension):
+
+ """
+ Auto-reply (vacation) functionality using Postfix.
+
+ """
+ name = "postfix_autoreply"
+ label = "Postfix autoreply"
+ version = "1.0"
+ description = ugettext_lazy(
+ "Auto-reply (vacation) functionality using Postfix")
+
+ 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
+
+exts_pool.register_extension(PostfixAutoreply)
+
+
+@events.observe("ExtraUprefsRoutes")
+def extra_routes():
+ from django.conf.urls import url
+
+ return [
+ url(r'^user/autoreply/$',
+ 'modoboa.extensions.postfix_autoreply.views.autoreply',
+ name="autoreply")
+ ]
diff --git a/modoboa/extensions/postfix_relay_domains/__init__.py b/modoboa/extensions/postfix_relay_domains/__init__.py
index 4c4ceac6b..e69de29bb 100644
--- a/modoboa/extensions/postfix_relay_domains/__init__.py
+++ b/modoboa/extensions/postfix_relay_domains/__init__.py
@@ -1,115 +0,0 @@
-# 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 .models import RelayDomain, RelayDomainAlias, Service
-from modoboa.core.extensions import ModoExtension, exts_pool
-from modoboa.lib import events, parameters
-
-
-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):
-
- """Extension declaration."""
-
- 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
- prefix = "modoboa.extensions.postfix_relay_domains."
- if prefix + "general_callbacks" in sys.modules:
- reload(general_callbacks)
- if exts_pool.is_extension_enabled('limits'):
- import limits_callbacks
- if prefix + "limits_callbacks" in sys.modules:
- reload(limits_callbacks)
- if exts_pool.is_extension_enabled('amavis'):
- import amavis_callbacks
- if prefix + "amavis_callbacks" in sys.modules:
- reload(amavis_callbacks)
-
- def destroy(self):
- events.unregister_extension()
- parameters.unregister()
-
-exts_pool.register_extension(PostfixRelayDomains)
diff --git a/modoboa/extensions/postfix_relay_domains/general_callbacks.py b/modoboa/extensions/postfix_relay_domains/general_callbacks.py
index 765b32fcf..404a4013b 100644
--- a/modoboa/extensions/postfix_relay_domains/general_callbacks.py
+++ b/modoboa/extensions/postfix_relay_domains/general_callbacks.py
@@ -127,21 +127,3 @@ def get_import_func(objtype):
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()
diff --git a/modoboa/extensions/postfix_relay_domains/migrations/0001_initial.py b/modoboa/extensions/postfix_relay_domains/migrations/0001_initial.py
new file mode 100644
index 000000000..311a5dc0d
--- /dev/null
+++ b/modoboa/extensions/postfix_relay_domains/migrations/0001_initial.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RelayDomain',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(help_text='The domain name', unique=True, max_length=100, verbose_name='name')),
+ ('target_host', models.CharField(help_text='Remote destination of this domain', max_length=255, verbose_name='target host')),
+ ('enabled', models.BooleanField(default=True, help_text='Check to activate this domain', verbose_name='enabled')),
+ ('verify_recipients', models.BooleanField(default=False, help_text='Check for valid recipients', verbose_name='verify recipients')),
+ ('dates', models.ForeignKey(to='admin.ObjectDates')),
+ ],
+ options={
+ 'ordering': ['name'],
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='RelayDomainAlias',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
+ ('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
+ ('dates', models.ForeignKey(to='admin.ObjectDates')),
+ ('target', models.ForeignKey(verbose_name='target', to='postfix_relay_domains.RelayDomain', help_text='The relay domain this alias points to')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Service',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(help_text='The service name', unique=True, max_length=100, verbose_name='name')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='relaydomain',
+ name='service',
+ field=models.ForeignKey(to='postfix_relay_domains.Service'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/modoboa/extensions/postfix_relay_domains/migrations/0002_load_initial_data.py b/modoboa/extensions/postfix_relay_domains/migrations/0002_load_initial_data.py
new file mode 100644
index 000000000..85024719c
--- /dev/null
+++ b/modoboa/extensions/postfix_relay_domains/migrations/0002_load_initial_data.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+from modoboa.core.extensions import exts_pool
+
+
+def load_initial_data(apps, schema_editor):
+ """Load initial data."""
+ Service = apps.get_model("postfix_relay_domains", "Service")
+ for service_name in ['relay', 'smtp']:
+ Service.objects.get_or_create(name=service_name)
+
+ if not exts_pool.is_extension_installed("modoboa.extensions.limits"):
+ return
+
+ Group = apps.get_model("auth", "Group")
+ Permission = apps.get_model("auth", "Permission")
+ ContentType = apps.get_model("contenttypes", "ContentType")
+ RelayDomain = apps.get_model("postfix_relay_domains", "RelayDomain")
+ RelayDomainAlias = apps.get_model(
+ "postfix_relay_domains", "RelayDomainAlias")
+ LimitsPool = apps.get_model("limits", "LimitsPool")
+ Limit = apps.get_model("limits", "Limit")
+
+ # Add new limits to existing pools. Set maxvalue to 0 because we
+ # don't want to load the parameters module here...
+ new_limits = ["relay_domains_limit", "relay_domain_aliases_limit"]
+ for pool in LimitsPool.objects.all():
+ for lname in new_limits:
+ Limit.objects.create(name=lname, pool=pool, maxvalue=0)
+
+ # Add new permissions to the Resellers group.
+ 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()
+
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('postfix_relay_domains', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(load_initial_data)
+ ]
diff --git a/modoboa/extensions/postfix_relay_domains/migrations/__init__.py b/modoboa/extensions/postfix_relay_domains/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/postfix_relay_domains/models.py b/modoboa/extensions/postfix_relay_domains/models.py
index 5e96959ac..17807373e 100644
--- a/modoboa/extensions/postfix_relay_domains/models.py
+++ b/modoboa/extensions/postfix_relay_domains/models.py
@@ -86,14 +86,16 @@ class RelayDomain(AdminObject):
ugettext_lazy('target host'), max_length=255,
help_text=ugettext_lazy('Remote destination of this domain')
)
- service = models.ForeignKey(Service, default='relay')
+ service = models.ForeignKey(Service)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
- help_text=ugettext_lazy('Check to activate this domain')
+ help_text=ugettext_lazy('Check to activate this domain'),
+ default=True
)
verify_recipients = models.BooleanField(
ugettext_lazy('verify recipients'),
- help_text=ugettext_lazy('Check for valid recipients')
+ help_text=ugettext_lazy('Check for valid recipients'),
+ default=False
)
owners = generic.GenericRelation(ObjectAccess)
@@ -173,7 +175,8 @@ class RelayDomainAlias(AdminObject):
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
- help_text=ugettext_lazy("Check to activate this alias")
+ help_text=ugettext_lazy("Check to activate this alias"),
+ default=True
)
def __str__(self):
diff --git a/modoboa/extensions/postfix_relay_domains/modo_extension.py b/modoboa/extensions/postfix_relay_domains/modo_extension.py
new file mode 100644
index 000000000..47819ba9a
--- /dev/null
+++ b/modoboa/extensions/postfix_relay_domains/modo_extension.py
@@ -0,0 +1,43 @@
+# coding: utf-8
+
+"""Declare postfix_relay_domains extension and register it."""
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import events, parameters
+
+
+EXTENSION_EVENTS = [
+ "RelayDomainCreated",
+ "RelayDomainDeleted",
+ "RelayDomainModified",
+ "RelayDomainAliasCreated",
+ "RelayDomainAliasDeleted",
+ "ExtraRelayDomainForm",
+ "FillRelayDomainInstances"
+]
+
+class PostfixRelayDomains(ModoExtension):
+
+ """Extension declaration."""
+
+ name = "postfix_relay_domains"
+ label = "Postfix relay domains"
+ version = "1.0"
+ description = ugettext_lazy("Relay domains support for Postfix")
+
+ 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 exts_pool.is_extension_installed("modoboa.extensions.limits"):
+ import limits_callbacks
+ if exts_pool.is_extension_installed("modoboa.extensions.amavis"):
+ import amavis_callbacks
+
+exts_pool.register_extension(PostfixRelayDomains)
diff --git a/modoboa/extensions/radicale/__init__.py b/modoboa/extensions/radicale/__init__.py
index d7d884cf3..e69de29bb 100644
--- a/modoboa/extensions/radicale/__init__.py
+++ b/modoboa/extensions/radicale/__init__.py
@@ -1,76 +0,0 @@
-"""
-Radicale management frontend.
-
-"""
-import sys
-
-from django.contrib.auth.models import Permission, Group
-from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext_lazy
-
-from modoboa.lib import events, parameters
-from modoboa.lib.permissions import add_permissions_to_group
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-
-def init_limits_dependant_features():
- """Custom initialization if the limits extension activated.
-
- We mainly define extra permissions.
- """
- add_permissions_to_group(
- "Resellers",
- [("radicale", "usercalendar", "add_usercalendar"),
- ("radicale", "usercalendar", "change_usercalendar"),
- ("radicale", "usercalendar", "delete_usercalendar"),
- ("radicale", "sharedcalendar", "add_sharedcalendar"),
- ("radicale", "sharedcalendar", "change_sharedcalendar"),
- ("radicale", "sharedcalendar", "delete_sharedcalendar")]
- )
-
-
-class Radicale(ModoExtension):
-
- """Radicale extension declaration."""
-
- name = "radicale"
- label = ugettext_lazy("Radicale management")
- version = "1.0"
- url = "calendars"
- description = ugettext_lazy(
- "Management frontend for Radicale, a simple calendar and contact "
- "server."
- )
-
- def init(self):
- """Plugin initialization.
- """
- add_permissions_to_group(
- "DomainAdmins",
- [("radicale", "usercalendar", "add_usercalendar"),
- ("radicale", "usercalendar", "change_usercalendar"),
- ("radicale", "usercalendar", "delete_usercalendar"),
- ("radicale", "sharedcalendar", "add_sharedcalendar"),
- ("radicale", "sharedcalendar", "change_sharedcalendar"),
- ("radicale", "sharedcalendar", "delete_sharedcalendar")]
- )
- if exts_pool.is_extension_enabled('limits'):
- init_limits_dependant_features()
-
- def load(self):
- """Plugin loading.
- """
- from .app_settings import ParametersForm
-
- parameters.register(ParametersForm, "Radicale")
- from modoboa.extensions.radicale import general_callbacks
- if 'modoboa.extensions.radicale.general_callbacks' in sys.modules:
- reload(general_callbacks)
-
- def destroy(self):
- """Plugin unloading.
- """
- events.unregister_extension()
- parameters.unregister()
-
-exts_pool.register_extension(Radicale)
diff --git a/modoboa/extensions/radicale/general_callbacks.py b/modoboa/extensions/radicale/general_callbacks.py
index 5de72ed50..4688f748a 100644
--- a/modoboa/extensions/radicale/general_callbacks.py
+++ b/modoboa/extensions/radicale/general_callbacks.py
@@ -16,20 +16,3 @@ def top_menu(target, user):
"url": reverse('radicale:index')}
]
return []
-
-
-@events.observe('ExtEnabled')
-def extension_enabled(extension):
- """ExtEnabled event listener.
-
- Usefull when *limits* extension is activated after *radicale*.
-
- :param extension: enabled extension
-
- """
- if extension.name == 'limits':
- from modoboa.extensions.radicale import (
- init_limits_dependant_features
- )
-
- init_limits_dependant_features()
diff --git a/modoboa/extensions/radicale/migrations/0001_initial.py b/modoboa/extensions/radicale/migrations/0001_initial.py
new file mode 100644
index 000000000..8654a5fc0
--- /dev/null
+++ b/modoboa/extensions/radicale/migrations/0001_initial.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AccessRule',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('read', models.BooleanField(default=False)),
+ ('write', models.BooleanField(default=False)),
+ ('last_update', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SharedCalendar',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=200)),
+ ('domain', models.ForeignKey(to='admin.Domain')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='UserCalendar',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=200)),
+ ('mailbox', models.ForeignKey(to='admin.Mailbox')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='accessrule',
+ name='calendar',
+ field=models.ForeignKey(related_name='rules', to='radicale.UserCalendar'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='accessrule',
+ name='mailbox',
+ field=models.ForeignKey(to='admin.Mailbox'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='accessrule',
+ unique_together=set([('mailbox', 'calendar')]),
+ ),
+ ]
diff --git a/modoboa/extensions/radicale/migrations/0002_load_initial_data.py b/modoboa/extensions/radicale/migrations/0002_load_initial_data.py
new file mode 100644
index 000000000..b21ff3896
--- /dev/null
+++ b/modoboa/extensions/radicale/migrations/0002_load_initial_data.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+from modoboa.core.extensions import exts_pool
+from modoboa.lib.migration_tools import add_permissions_to_group
+
+
+def load_initial_data(apps, schema_editor):
+ """Load initial data."""
+
+ permissions = [
+ ("radicale", "usercalendar", "add_usercalendar"),
+ ("radicale", "usercalendar", "change_usercalendar"),
+ ("radicale", "usercalendar", "delete_usercalendar"),
+ ("radicale", "sharedcalendar", "add_sharedcalendar"),
+ ("radicale", "sharedcalendar", "change_sharedcalendar"),
+ ("radicale", "sharedcalendar", "delete_sharedcalendar")
+ ]
+
+ add_permissions_to_group(apps, "DomainAdmins", permissions)
+ if not exts_pool.is_extension_installed(
+ "modoboa.extensions.limits"):
+ return
+ add_permissions_to_group(apps, "Resellers", permissions)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('radicale', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(load_initial_data)
+ ]
diff --git a/modoboa/extensions/radicale/migrations/__init__.py b/modoboa/extensions/radicale/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/extensions/radicale/modo_extension.py b/modoboa/extensions/radicale/modo_extension.py
new file mode 100644
index 000000000..02c9fb9dc
--- /dev/null
+++ b/modoboa/extensions/radicale/modo_extension.py
@@ -0,0 +1,33 @@
+"""
+Radicale management frontend.
+
+"""
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import parameters
+
+
+class Radicale(ModoExtension):
+
+ """Radicale extension declaration."""
+
+ name = "radicale"
+ label = ugettext_lazy("Radicale management")
+ version = "1.0"
+ url = "calendars"
+ description = ugettext_lazy(
+ "Management frontend for Radicale, a simple calendar and contact "
+ "server."
+ )
+
+ def load(self):
+ """Plugin loading.
+ """
+ from .app_settings import ParametersForm
+
+ parameters.register(ParametersForm, "Radicale")
+ from modoboa.extensions.radicale import general_callbacks
+
+exts_pool.register_extension(Radicale)
diff --git a/modoboa/extensions/sievefilters/__init__.py b/modoboa/extensions/sievefilters/__init__.py
index 50ef1ebe9..e69de29bb 100644
--- a/modoboa/extensions/sievefilters/__init__.py
+++ b/modoboa/extensions/sievefilters/__init__.py
@@ -1,28 +0,0 @@
-# coding: utf-8
-import sys
-from django.utils.translation import ugettext_lazy
-from modoboa.lib import events, parameters
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-
-class SieveFilters(ModoExtension):
- name = "sievefilters"
- label = "Sieve filters"
- 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_extension()
- parameters.unregister()
-
-exts_pool.register_extension(SieveFilters)
diff --git a/modoboa/extensions/sievefilters/modo_extension.py b/modoboa/extensions/sievefilters/modo_extension.py
new file mode 100644
index 000000000..0b4fe46ba
--- /dev/null
+++ b/modoboa/extensions/sievefilters/modo_extension.py
@@ -0,0 +1,24 @@
+# coding: utf-8
+"""Declare and register the sievefilters extension."""
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import parameters
+
+
+class SieveFilters(ModoExtension):
+ name = "sievefilters"
+ label = "Sieve filters"
+ 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
+
+exts_pool.register_extension(SieveFilters)
diff --git a/modoboa/extensions/stats/__init__.py b/modoboa/extensions/stats/__init__.py
index e08b12b55..e69de29bb 100644
--- a/modoboa/extensions/stats/__init__.py
+++ b/modoboa/extensions/stats/__init__.py
@@ -1,39 +0,0 @@
-# coding: utf-8
-
-"""
-Graphical statistics about emails traffic using RRDtool
-
-This module provides support to retrieve statistics from postfix log :
-sent, received, bounced, rejected
-
-"""
-import sys
-from django.utils.translation import ugettext_lazy
-from modoboa.lib import events, parameters
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-
-class Stats(ModoExtension):
- name = "stats"
- label = "Statistics"
- version = "1.0"
- description = ugettext_lazy(
- "Graphical statistics about emails traffic using RRDtool"
- )
- needs_media = True
-
- def load(self):
- from modoboa.extensions.stats.app_settings import ParametersForm
- events.declare(["GetGraphSets"])
- 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_extension()
- parameters.unregister()
-
-exts_pool.register_extension(Stats)
diff --git a/modoboa/extensions/stats/management/commands/logparser.py b/modoboa/extensions/stats/management/commands/logparser.py
index 3613f056b..bdcb11bc7 100644
--- a/modoboa/extensions/stats/management/commands/logparser.py
+++ b/modoboa/extensions/stats/management/commands/logparser.py
@@ -97,7 +97,8 @@ class LogParser(object):
for dom in Domain.objects.all():
self.domains += [str(dom.name)]
self.data[str(dom.name)] = {}
- if not exts_pool.is_extension_enabled("postfix_relay_domains"):
+ if not exts_pool.is_extension_installed(
+ "modoboa.extensions.postfix_relay_domains"):
return
from modoboa.extensions.postfix_relay_domains.models import RelayDomain
for rdom in RelayDomain.objects.all():
diff --git a/modoboa/extensions/stats/modo_extension.py b/modoboa/extensions/stats/modo_extension.py
new file mode 100644
index 000000000..926063168
--- /dev/null
+++ b/modoboa/extensions/stats/modo_extension.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+"""
+Graphical statistics about emails traffic using RRDtool
+
+This module provides support to retrieve statistics from postfix log :
+sent, received, bounced, rejected
+
+"""
+import sys
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import events, parameters
+
+
+class Stats(ModoExtension):
+ name = "stats"
+ label = "Statistics"
+ version = "1.0"
+ description = ugettext_lazy(
+ "Graphical statistics about emails traffic using RRDtool"
+ )
+ needs_media = True
+
+ def load(self):
+ from modoboa.extensions.stats.app_settings import ParametersForm
+ events.declare(["GetGraphSets"])
+ parameters.register(
+ ParametersForm, ugettext_lazy("Graphical statistics")
+ )
+ from modoboa.extensions.stats import general_callbacks
+
+exts_pool.register_extension(Stats)
diff --git a/modoboa/extensions/stats/views.py b/modoboa/extensions/stats/views.py
index b16756408..de88d898c 100644
--- a/modoboa/extensions/stats/views.py
+++ b/modoboa/extensions/stats/views.py
@@ -58,7 +58,8 @@ def check_domain_access(user, pattern):
:return: a domain name (str) or None.
"""
targets = [Domain]
- if exts_pool.is_extension_enabled("postfix_relay_domains"):
+ if exts_pool.is_extension_installed(
+ "modoboa.extensions.postfix_relay_domains"):
from modoboa.extensions.postfix_relay_domains.models import RelayDomain
targets.append(RelayDomain)
@@ -123,7 +124,8 @@ def graphs(request):
def get_domain_list(request):
"""Get the list of domains (and relay domains) the user can see."""
doms = [dom.name for dom in Domain.objects.get_for_admin(request.user)]
- if exts_pool.is_extension_enabled("postfix_relay_domains"):
+ if exts_pool.is_extension_installed(
+ "modoboa.extensions.postfix_relay_domains"):
from modoboa.extensions.postfix_relay_domains.models import RelayDomain
doms += [
rdom.name for rdom in
diff --git a/modoboa/extensions/webmail/__init__.py b/modoboa/extensions/webmail/__init__.py
index b57e93b88..e69de29bb 100644
--- a/modoboa/extensions/webmail/__init__.py
+++ b/modoboa/extensions/webmail/__init__.py
@@ -1,30 +0,0 @@
-# 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
-from modoboa.core.extensions import ModoExtension, exts_pool
-
-
-class Webmail(ModoExtension):
- name = "webmail"
- label = "Webmail"
- 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_extension()
- parameters.unregister()
-
-exts_pool.register_extension(Webmail)
diff --git a/modoboa/extensions/webmail/modo_extension.py b/modoboa/extensions/webmail/modo_extension.py
new file mode 100644
index 000000000..7ee635a15
--- /dev/null
+++ b/modoboa/extensions/webmail/modo_extension.py
@@ -0,0 +1,25 @@
+# coding: utf-8
+"""Declare and register the webmail extension."""
+
+from django.utils.translation import ugettext_lazy
+
+from modoboa.core.extensions import ModoExtension, exts_pool
+from modoboa.lib import parameters
+
+
+class Webmail(ModoExtension):
+ name = "webmail"
+ label = "Webmail"
+ 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
+
+exts_pool.register_extension(Webmail)
diff --git a/modoboa/lib/events.py b/modoboa/lib/events.py
index 19bbe0b20..57996db07 100644
--- a/modoboa/lib/events.py
+++ b/modoboa/lib/events.py
@@ -1,34 +1,26 @@
# -*- coding: utf-8 -*-
"""
-:mod:`events` --- simple events handling
+:mod:`EVENTS` --- simple EVENTS handling
----------------------------------------
-This module provides a simple way of managing events between Modoboa
+This module provides a simple way of managing EVENTS between Modoboa
core application and additional components.
"""
-import inspect
-import re
-from functools import wraps
-from django.conf import settings
-
-from modoboa.lib.sysutils import guess_extension_name
-
-
-events = []
-callbacks = {}
+EVENTS = []
+CALLBACKS = {}
def declare(nevents):
- """Declare new events
+ """Declare new events.
:param list nevents: a list of event names
"""
for evt in nevents:
- if evt not in events:
- events.append(evt)
+ if evt not in EVENTS:
+ EVENTS.append(evt)
def register(event, callback):
@@ -39,69 +31,32 @@ def register(event, callback):
:param event: the event to listen to
:param callback: a function to execute when the event is raised
"""
- # if not event in events:
+ # if not event in EVENTS:
# print "Event %s not registered" % event
# return 0
- if event not in callbacks.keys():
- callbacks[event] = {}
+ if event not in CALLBACKS.keys():
+ CALLBACKS[event] = {}
fullname = "%s.%s" % (callback.__module__, callback.__name__)
- if fullname not in callbacks[event]:
- callbacks[event][fullname] = callback
+ if fullname not in CALLBACKS[event]:
+ CALLBACKS[event][fullname] = callback
return 1
class observe(object):
- """Event observing decorator
+ """Event observing decorator.
Automatically register the decorated function to observe the given
- event. If the decorated function is located into an extension, we
- check before each call if the extension is enabled or not. If
- that's not the case, the callback is not called.
+ event.
- .. note::
-
- That's not a really optimized behaviour but I haven't found
- another solution to achieve that feature.
-
- :param evtname: the event's name
+ :param evtnames: a list of event names
"""
def __init__(self, *evtnames, **kwargs):
self.evtnames = evtnames
- if "extname" in kwargs:
- self.extname = kwargs["extname"]
-
- def __guess_extension_name(self, modname):
- if modname.startswith('modoboa.extensions'):
- m = re.match(r'modoboa\.extensions\.([^\.]+)', modname)
- if m:
- return m.group(1)
- return None
def __call__(self, f):
- modname = inspect.getmodule(inspect.stack()[1][0]).__name__
- extname = self.extname if hasattr(self, "extname") \
- else self.__guess_extension_name(modname)
-
- @wraps(f)
- def wrapped_f(*args, **kwargs):
- if extname:
- from modoboa.core.models import Extension
- from modoboa.core.extensions import exts_pool
- try:
- ext = Extension.objects.get(name=extname)
- except Extension.DoesNotExist:
- extdef = exts_pool.get_extension(extname)
- if not extdef.always_active:
- return []
- else:
- if not ext.enabled:
- return None
- elif modname not in settings.MODOBOA_APPS:
- return []
- return f(*args, **kwargs)
for evt in self.evtnames:
- register(evt, wrapped_f)
- return wrapped_f
+ register(evt, f)
+ return f
def unregister(event, callback):
@@ -110,57 +65,44 @@ def unregister(event, callback):
:param event: the targeted event
:param callback: the callback to remove
"""
- if event not in events:
+ if event not in EVENTS:
return False
- if event not in callbacks:
+ if event not in CALLBACKS:
return False
fullname = "%s.%s" % (callback.__module__, callback.__name__)
try:
- del callbacks[event][fullname]
+ del CALLBACKS[event][fullname]
except KeyError:
pass
-def unregister_extension(extension=None):
- """Unregister all callbacks declared by an extension.
-
- :param str extension: extension full name (ie. module name)
- """
- if extension is None:
- extension = guess_extension_name()
- for evt, values in callbacks.items():
- for name in values.keys():
- if extension in name:
- del callbacks[evt][name]
-
-
def raiseEvent(event, *args, **kwargs):
"""Raise a specific event
Any additional keyword argument will be passed to registered
- callbacks.
+ CALLBACKS.
:param event: the event to raise
"""
- if event not in events or event not in callbacks:
+ if event not in EVENTS or event not in CALLBACKS:
return 0
- for callback in callbacks[event].values():
+ for callback in CALLBACKS[event].values():
callback(*args, **kwargs)
return 1
def raiseQueryEvent(event, *args, **kwargs):
- """Raise a specific event and wait for answers from callbacks
+ """Raise a specific event and wait for answers from CALLBACKS
Any additional keyword argument will be passed to registered
- callbacks. Callback answers are returned as a list.
+ CALLBACKS. Callback answers are returned as a list.
:param event: the event to raise
"""
result = []
- if event not in events or event not in callbacks:
+ if event not in EVENTS or event not in CALLBACKS:
return result
- for callback in callbacks[event].values():
+ for callback in CALLBACKS[event].values():
tmp = callback(*args, **kwargs)
if tmp is None:
# Callback is registered but associated extension is
@@ -174,15 +116,15 @@ def raiseDictEvent(event, *args):
"""Raise a specific event and return result as a dictionnary
Any additional keyword argument will be passed to registered
- callbacks. Callback answers must be dictionnaries.
+ CALLBACKS. Callback answers must be dictionnaries.
:param event: the event to raise
:return: a dictionnary
"""
result = {}
- if event not in events or event not in callbacks:
+ if event not in EVENTS or event not in CALLBACKS:
return result
- for callback in callbacks[event].values():
+ for callback in CALLBACKS[event].values():
tmp = callback(*args)
if tmp is None:
# Callback is registered but associated extension is
diff --git a/modoboa/lib/middleware.py b/modoboa/lib/middleware.py
index 31b7df90a..b6da7c8c9 100644
--- a/modoboa/lib/middleware.py
+++ b/modoboa/lib/middleware.py
@@ -2,12 +2,8 @@
"""Custom middlewares."""
-import re
+from django.http import HttpResponseRedirect
-from django.http import Http404, HttpResponseRedirect
-
-from modoboa.core.extensions import exts_pool
-from modoboa.core.models import Extension
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.signals import request_accessor
from modoboa.lib.web_utils import (
@@ -15,24 +11,6 @@ from modoboa.lib.web_utils import (
)
-class ExtControlMiddleware(object):
-
- def process_view(self, request, view, args, kwargs):
- m = re.match(r"modoboa\.extensions\.(\w+)", view.__module__)
- if m is None:
- return None
- try:
- ext = Extension.objects.get(name=m.group(1))
- except Extension.DoesNotExist:
- extdef = exts_pool.get_extension(m.group(1))
- if extdef.always_active:
- return None
- raise Http404
- if ext.enabled:
- return None
- raise Http404
-
-
class AjaxLoginRedirect(object):
def process_response(self, request, response):
diff --git a/modoboa/lib/migration_tools.py b/modoboa/lib/migration_tools.py
new file mode 100644
index 000000000..4509379d6
--- /dev/null
+++ b/modoboa/lib/migration_tools.py
@@ -0,0 +1,16 @@
+"""Additional functions available for migration scripts."""
+
+
+def add_permissions_to_group(apps, groupname, permissions):
+ """Add the specified permissions to a django group."""
+ Group = apps.get_model("auth", "Group")
+ Permission = apps.get_model("auth", "Permission")
+ ContentType = apps.get_model("contenttypes", "ContentType")
+
+ grp = Group.objects.get(name=groupname)
+ for appname, modelname, permname in permissions:
+ ct = ContentType.objects.get_by_natural_key(
+ appname, modelname)
+ grp.permissions.add(
+ Permission.objects.get(content_type=ct, codename=permname)
+ )
diff --git a/modoboa/lib/migrations/0001_initial.py b/modoboa/lib/migrations/0001_initial.py
new file mode 100644
index 000000000..02453b798
--- /dev/null
+++ b/modoboa/lib/migrations/0001_initial.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Parameter',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('value', models.CharField(max_length=255)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='UserParameter',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('value', models.CharField(max_length=255)),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/modoboa/lib/migrations/__init__.py b/modoboa/lib/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py
index b1a136b99..144fe1c62 100644
--- a/modoboa/lib/permissions.py
+++ b/modoboa/lib/permissions.py
@@ -145,17 +145,3 @@ def get_object_owner(obj):
except ObjectAccess.DoesNotExist:
return None
return entry.user
-
-
-def add_permissions_to_group(groupname, permissions):
- """Add the specified permissions to a django group.
- """
- from django.contrib.auth.models import Group, Permission
-
- grp = Group.objects.get(name=groupname)
- for appname, modelname, permname in permissions:
- ct = ContentType.objects.get_by_natural_key(
- appname, modelname)
- grp.permissions.add(
- Permission.objects.get(content_type=ct, codename=permname)
- )
diff --git a/requirements.txt b/requirements.txt
index f469abeba..7498ead8b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-django>=1.6,<1.7
+django>=1.7
django-reversion==1.8.5
dj-database-url
south>=1.0