From 9b3d00027fc7ef778589b42187ca4a7c68afbe4a Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 29 Jan 2015 11:31:50 +0100 Subject: [PATCH] Django 1.7 migrations + cleanup. --- doc/extending/events.rst | 22 -- modoboa/core/__init__.py | 14 - modoboa/core/app_settings.py | 14 +- modoboa/core/commands/deploy.py | 9 - .../core/commands/templates/settings.py.tpl | 1 - modoboa/core/extensions.py | 49 ++-- .../commands/clean_unknown_extensions.py | 26 -- .../management/commands/manage_extensions.py | 58 ---- modoboa/core/migrations/0001_initial.py | 81 ++++++ .../core/migrations/0002_delete_extension.py | 17 ++ modoboa/core/migrations/__init__.py | 0 modoboa/core/models.py | 71 ----- modoboa/core/templates/core/extensions.html | 48 ++-- modoboa/core/urls.py | 2 - modoboa/core/views/admin.py | 34 +-- modoboa/extensions/admin/__init__.py | 263 ------------------ .../admin/migrations/0001_initial.py | 161 +++++++++++ .../extensions/admin/migrations/__init__.py | 0 modoboa/extensions/admin/models/alias.py | 3 +- modoboa/extensions/admin/models/domain.py | 7 +- .../extensions/admin/models/domain_alias.py | 6 +- modoboa/extensions/admin/modo_extension.py | 261 +++++++++++++++++ modoboa/extensions/amavis/__init__.py | 61 ---- .../amavis/migrations/0001_initial.py | 187 +++++++++++++ .../migrations/0002_load_initial_data.py | 51 ++++ .../extensions/amavis/migrations/__init__.py | 0 modoboa/extensions/amavis/modo_extension.py | 34 +++ modoboa/extensions/limits/__init__.py | 70 ----- .../limits/migrations/0001_initial.py | 47 ++++ .../migrations/0002_load_initial_data.py | 47 ++++ .../extensions/limits/migrations/__init__.py | 0 modoboa/extensions/limits/modo_extension.py | 35 +++ .../extensions/postfix_autoreply/__init__.py | 77 ----- .../migrations/0001_initial.py | 73 +++++ .../migrations/0002_load_initial_data.py | 40 +++ .../postfix_autoreply/migrations/__init__.py | 0 .../extensions/postfix_autoreply/models.py | 4 +- .../postfix_autoreply/modo_extension.py | 47 ++++ .../postfix_relay_domains/__init__.py | 115 -------- .../general_callbacks.py | 18 -- .../migrations/0001_initial.py | 59 ++++ .../migrations/0002_load_initial_data.py | 57 ++++ .../migrations/__init__.py | 0 .../postfix_relay_domains/models.py | 11 +- .../postfix_relay_domains/modo_extension.py | 43 +++ modoboa/extensions/radicale/__init__.py | 76 ----- .../extensions/radicale/general_callbacks.py | 17 -- .../radicale/migrations/0001_initial.py | 66 +++++ .../migrations/0002_load_initial_data.py | 37 +++ .../radicale/migrations/__init__.py | 0 modoboa/extensions/radicale/modo_extension.py | 33 +++ modoboa/extensions/sievefilters/__init__.py | 28 -- .../extensions/sievefilters/modo_extension.py | 24 ++ modoboa/extensions/stats/__init__.py | 39 --- .../stats/management/commands/logparser.py | 3 +- modoboa/extensions/stats/modo_extension.py | 35 +++ modoboa/extensions/stats/views.py | 6 +- modoboa/extensions/webmail/__init__.py | 30 -- modoboa/extensions/webmail/modo_extension.py | 25 ++ modoboa/lib/events.py | 118 ++------ modoboa/lib/middleware.py | 24 +- modoboa/lib/migration_tools.py | 16 ++ modoboa/lib/migrations/0001_initial.py | 38 +++ modoboa/lib/migrations/__init__.py | 0 modoboa/lib/permissions.py | 14 - requirements.txt | 2 +- 66 files changed, 1615 insertions(+), 1239 deletions(-) delete mode 100644 modoboa/core/management/commands/clean_unknown_extensions.py delete mode 100644 modoboa/core/management/commands/manage_extensions.py create mode 100644 modoboa/core/migrations/0001_initial.py create mode 100644 modoboa/core/migrations/0002_delete_extension.py create mode 100644 modoboa/core/migrations/__init__.py create mode 100644 modoboa/extensions/admin/migrations/0001_initial.py create mode 100644 modoboa/extensions/admin/migrations/__init__.py create mode 100644 modoboa/extensions/admin/modo_extension.py create mode 100644 modoboa/extensions/amavis/migrations/0001_initial.py create mode 100644 modoboa/extensions/amavis/migrations/0002_load_initial_data.py create mode 100644 modoboa/extensions/amavis/migrations/__init__.py create mode 100644 modoboa/extensions/amavis/modo_extension.py create mode 100644 modoboa/extensions/limits/migrations/0001_initial.py create mode 100644 modoboa/extensions/limits/migrations/0002_load_initial_data.py create mode 100644 modoboa/extensions/limits/migrations/__init__.py create mode 100644 modoboa/extensions/limits/modo_extension.py create mode 100644 modoboa/extensions/postfix_autoreply/migrations/0001_initial.py create mode 100644 modoboa/extensions/postfix_autoreply/migrations/0002_load_initial_data.py create mode 100644 modoboa/extensions/postfix_autoreply/migrations/__init__.py create mode 100644 modoboa/extensions/postfix_autoreply/modo_extension.py create mode 100644 modoboa/extensions/postfix_relay_domains/migrations/0001_initial.py create mode 100644 modoboa/extensions/postfix_relay_domains/migrations/0002_load_initial_data.py create mode 100644 modoboa/extensions/postfix_relay_domains/migrations/__init__.py create mode 100644 modoboa/extensions/postfix_relay_domains/modo_extension.py create mode 100644 modoboa/extensions/radicale/migrations/0001_initial.py create mode 100644 modoboa/extensions/radicale/migrations/0002_load_initial_data.py create mode 100644 modoboa/extensions/radicale/migrations/__init__.py create mode 100644 modoboa/extensions/radicale/modo_extension.py create mode 100644 modoboa/extensions/sievefilters/modo_extension.py create mode 100644 modoboa/extensions/stats/modo_extension.py create mode 100644 modoboa/extensions/webmail/modo_extension.py create mode 100644 modoboa/lib/migration_tools.py create mode 100644 modoboa/lib/migrations/0001_initial.py create mode 100644 modoboa/lib/migrations/__init__.py 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" %}


-
- {% csrf_token %} - - - - - - - - - - - {% for extension in extensions %} - - - - - - - {% endfor %} - -
{% trans "Name" %}{% trans "Version" %}{% trans "Description" %}
{{ extension.label }}{{ extension.version }}{{ extension.description }}
-
-{% trans 'Save' %} +{% load i18n %} +{% load url from future %} + +

{% trans "Extensions" %} {% trans "The extension(s) you are using" %}


+ + + + + + + + + + {% for extension in extensions %} + + + + + + {% endfor %} + +
{% trans "Name" %}{% trans "Version" %}{% trans "Description" %}
{{ extension.label }}{{ extension.version }}{{ extension.description }}
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