Django 1.7 migrations + cleanup.

This commit is contained in:
Antoine Nguyen
2015-01-29 11:31:50 +01:00
parent c534cdc09e
commit 9b3d00027f
66 changed files with 1615 additions and 1239 deletions
-22
View File
@@ -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
===================
-14
View File
@@ -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):
"""
+3 -11
View File
@@ -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])
-9
View File
@@ -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
)
@@ -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',
)
+16 -33
View File
@@ -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"]:
@@ -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()
@@ -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)
+81
View File
@@ -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')]),
),
]
@@ -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',
),
]
View File
-71
View File
@@ -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)
+22 -26
View File
@@ -1,26 +1,22 @@
{% load i18n %}{% load url from future %}
<h2>{% trans "Extensions" %} <small>{% trans "Choose the extension(s) to use" %}</small></h2><hr />
<form id="settings_form" method="POST" action="{% url 'core:extension_save' %}">
{% csrf_token %}
<table id="extensions" class="table table-hover">
<thead>
<tr>
<th><input type="checkbox" id="toggle_extensions_sel"></th>
<th>{% trans "Name" %}</th>
<th>{% trans "Version" %}</th>
<th>{% trans "Description" %}</th>
</tr>
</thead>
<tbody>
{% for extension in extensions %}
<tr>
<td><input type="checkbox" name="select_{{ extension.name }}" value="{{ extension.name }}"{% if extension.selection %} checked=""{% endif %}></td>
<td>{{ extension.label }}</td>
<td>{{ extension.version }}</td>
<td>{{ extension.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
<a href="#" class="btn btn-primary" id="update" name="update">{% trans 'Save' %}</a>
{% load i18n %}
{% load url from future %}
<h2>{% trans "Extensions" %} <small>{% trans "The extension(s) you are using" %}</small></h2><hr />
<table id="extensions" class="table table-hover">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Version" %}</th>
<th>{% trans "Description" %}</th>
</tr>
</thead>
<tbody>
{% for extension in extensions %}
<tr>
<td>{{ extension.label }}</td>
<td>{{ extension.version }}</td>
<td>{{ extension.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
-2
View File
@@ -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"),
+1 -33
View File
@@ -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"):
-263
View File
@@ -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)
@@ -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')]),
),
]
+2 -1
View File
@@ -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'
+4 -3
View File
@@ -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)
@@ -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:
+261
View File
@@ -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)
-61
View File
@@ -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)
@@ -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,),
),
]
@@ -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)
]
@@ -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)
-70
View File
@@ -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)
@@ -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')]),
),
]
@@ -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)
]
@@ -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)
@@ -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")
]
@@ -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')]),
),
]
@@ -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)
]
@@ -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)
@@ -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")
]
@@ -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)
@@ -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()
@@ -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,
),
]
@@ -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)
]
@@ -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):
@@ -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)
-76
View File
@@ -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)
@@ -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()
@@ -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')]),
),
]
@@ -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)
]
@@ -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)
@@ -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)
@@ -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)
-39
View File
@@ -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)
@@ -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():
@@ -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)
+4 -2
View File
@@ -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
-30
View File
@@ -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)
@@ -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)
+30 -88
View File
@@ -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
+1 -23
View File
@@ -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):
+16
View File
@@ -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)
)
+38
View File
@@ -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,),
),
]
View File
-14
View File
@@ -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)
)
+1 -1
View File
@@ -1,4 +1,4 @@
django>=1.6,<1.7
django>=1.7
django-reversion==1.8.5
dj-database-url
south>=1.0