diff --git a/modoboa/admin/api.py b/modoboa/admin/api.py
index 24684c4fa..08facd697 100644
--- a/modoboa/admin/api.py
+++ b/modoboa/admin/api.py
@@ -75,10 +75,6 @@ class AccountViewSet(viewsets.ModelViewSet):
queryset = queryset.filter(mailbox__domain__name=domain)
return queryset
- def perform_destroy(self, instance):
- """Add custom args to delete call."""
- instance.delete(self.request.user)
-
@detail_route(methods=["put"])
def password(self, request, pk=None):
"""Change account password."""
diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py
index f695ee673..9f530be27 100644
--- a/modoboa/admin/app_settings.py
+++ b/modoboa/admin/app_settings.py
@@ -18,7 +18,7 @@ ADMIN_EVENTS = [
"DomainDeleted",
"DomainOwnershipRemoved",
"GetTagsForDomain",
- "ExtraDomainMenuEntries",
+
"ExtraDomainFilters",
"ExtraDomainQsetFilters",
"ExtraDomainTypes",
diff --git a/modoboa/admin/callbacks.py b/modoboa/admin/callbacks.py
index 59e59313a..b092c2516 100644
--- a/modoboa/admin/callbacks.py
+++ b/modoboa/admin/callbacks.py
@@ -1,208 +1,7 @@
"""Event callbacks for modoboa admin."""
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _, ugettext_lazy
-
-from modoboa.core import signals as core_signals
from modoboa.lib import events
-from modoboa.lib.email_utils import split_mailbox
-from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
-from modoboa.parameters import tools as param_tools
from .models import (
Domain, DomainAlias, Mailbox, Alias
)
-
-
-@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")}
- ]
- conditions = (
- user.has_perm("core.add_user"),
- user.has_perm("admin.add_alias")
- )
- if any(conditions):
- 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 hasattr(user, "mailbox"):
- 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.quota] \
- if hasattr(admin, "mailbox") else [""]
- if admin.role != "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:
- 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
- core_signals.can_create_object.send(
- sender="import", context=user, object_type="mailboxes")
- core_signals.can_create_object.send(
- sender="import", context=domain, object_type="mailboxes")
- account.save()
- if Mailbox.objects.filter(address=mailbox, domain=domain).exists():
- raise Conflict(
- _("Mailbox {} already exists").format(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.role == "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.lib.permissions import grant_access_to_object
- from .lib import check_if_domain_exists
-
- if not param_tools.get_global_parameter("auto_create_domain_and_mailbox"):
- return
- localpart, domname = split_mailbox(user.username)
- if user.role != '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 hasattr(request.user, "mailbox"):
- 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 hasattr(account, "mailbox"):
- return
- mb = account.mailbox
- 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/admin/forms/account.py b/modoboa/admin/forms/account.py
index 920b0cd10..19afd9871 100644
--- a/modoboa/admin/forms/account.py
+++ b/modoboa/admin/forms/account.py
@@ -229,10 +229,14 @@ class AccountFormMail(forms.Form, DynamicForm):
"quota": 3
}
self.extra_fields = []
- result = events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb)
- for fname, field in result:
- self.fields[fname] = field
- self.extra_fields.append(fname)
+ results = core_signals.extra_form_fields.send(
+ sender=self.__class__, form="mailform", instance=self.mb)
+ if results:
+ results = reduce(
+ lambda a, b: a + b, [result for result in results])
+ for fname, field in results:
+ self.fields[fname] = field
+ self.extra_fields.append(fname)
if self.mb is not None:
self.fields["email"].required = True
qset = self.mb.aliasrecipient_set.filter(alias__internal=False)
@@ -380,9 +384,9 @@ class AccountFormMail(forms.Form, DynamicForm):
self.cleaned_data["use_domain_quota"] = (
self.cleaned_data["quota_act"])
self.mb.update_from_dict(user, self.cleaned_data)
- events.raiseEvent(
- 'SaveExtraFormFields', 'mailform', self.mb, self.cleaned_data
- )
+ core_signals.extra_form_fields.send(
+ sender=self.__class__, form="mailform", instance=self.mb,
+ cleaned_data=self.cleaned_data)
account.email = self.cleaned_data["email"]
account.save()
diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py
index a1c649526..445986f1e 100644
--- a/modoboa/admin/forms/domain.py
+++ b/modoboa/admin/forms/domain.py
@@ -193,8 +193,9 @@ class DomainFormOptions(forms.Form):
def __init__(self, user, *args, **kwargs):
super(DomainFormOptions, self).__init__(*args, **kwargs)
- result = events.raiseQueryEvent("UserCanSetRole", user, "DomainAdmins")
- if False in result:
+ results = core_signals.user_can_set_role.send(
+ sender=self.__class__, user=user, role="DomainAdmins")
+ if False in [result[1] for result in results]:
self.fields = {}
return
diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py
index b7b6554e3..89bc051b6 100644
--- a/modoboa/admin/handlers.py
+++ b/modoboa/admin/handlers.py
@@ -1,10 +1,22 @@
"""Django signal handlers for admin."""
+from django.core.urlresolvers import reverse
from django.db.models import signals
from django.dispatch import receiver
+from django.utils.translation import ugettext as _
+from django.contrib.contenttypes.models import ContentType
+
+from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
+from modoboa.lib.cryptutils import encrypt
+from modoboa.lib.email_utils import split_mailbox
+from modoboa.lib import exceptions
+from modoboa.lib import permissions
+from modoboa.lib import signals as lib_signals
+from modoboa.parameters import tools as param_tools
+from . import lib
from . import models
from . import postfix_maps
@@ -74,7 +86,7 @@ def manage_alias_for_mailbox(sender, instance, **kwargs):
@receiver(signals.pre_delete, sender=models.Mailbox)
def mailbox_deleted_handler(sender, **kwargs):
- """``Mailbox`` pre_delete signal receiver
+ """``Mailbox`` pre_delete signal receiver.
In order to properly handle deletions (ie. we don't want to leave
orphan records into the db), we define this custom receiver.
@@ -85,7 +97,7 @@ def mailbox_deleted_handler(sender, **kwargs):
from modoboa.lib import events
from modoboa.lib.permissions import ungrant_access_to_object
- mb = kwargs['instance']
+ mb = kwargs["instance"]
events.raiseEvent("MailboxDeleted", mb)
ungrant_access_to_object(mb)
for ralias in mb.aliasrecipient_set.select_related("alias"):
@@ -93,6 +105,15 @@ def mailbox_deleted_handler(sender, **kwargs):
ralias.delete()
if not alias.aliasrecipient_set.exists():
alias.delete()
+ models.Quota.objects.filter(username=mb.full_address).delete()
+ request = lib_signals.get_request()
+ if not request.localconfig.parameters.get_value(
+ "handle_mailboxes", raise_exception=False):
+ return
+ keepdir = request.POST.get("keepdir", "false") == "true"
+ if keepdir:
+ return
+ mb.delete_dir()
@receiver(signals.post_delete, sender=models.Mailbox)
@@ -111,3 +132,166 @@ def register_postfix_maps(sender, **kwargs):
postfix_maps.SenderLoginAliasMap, postfix_maps.SenderLoginMailboxMap,
postfix_maps.SenderLoginMailboxExtraMap
]
+
+
+@receiver(core_signals.account_auto_created)
+def account_auto_created(sender, user, **kwargs):
+ """New account has been auto-created, build the rest."""
+ if not param_tools.get_global_parameter("auto_create_domain_and_mailbox"):
+ return
+ localpart, domname = split_mailbox(user.username)
+ if user.role != 'SimpleUsers' and domname is None:
+ return
+ sadmins = core_models.User.objects.filter(is_superuser=True)
+ try:
+ domain = models.Domain.objects.get(name=domname)
+ except models.Domain.DoesNotExist:
+ label = lib.check_if_domain_exists(
+ domname, [(models.DomainAlias, _("domain alias"))])
+ if label is not None:
+ return
+ domain = models.Domain(name=domname, enabled=True, quota=0)
+ domain.save(creator=sadmins[0])
+ for su in sadmins[1:]:
+ permissions.grant_access_to_object(su, domain)
+ qset = models.Mailbox.objects.filter(domain=domain, address=localpart)
+ if not qset.exists():
+ mb = models.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:]:
+ permissions.grant_access_to_object(su, mb)
+
+
+@receiver(core_signals.account_exported)
+def export_admin_domains(sender, user, **kwargs):
+ """Export administered domains too."""
+ result = [user.mailbox.quota] if hasattr(user, "mailbox") else [""]
+ if user.role != "DomainAdmins":
+ return result
+ return result + [
+ dom.name for dom in models.Domain.objects.get_for_admin(user)]
+
+
+@receiver(core_signals.account_imported)
+def import_account_mailbox(sender, user, account, row, **kwargs):
+ """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:
+ mailbox, domname = split_mailbox(account.email)
+ domain = models.Domain.objects.filter(name=domname).first()
+ if not domain:
+ raise exceptions.BadRequest(
+ _("Account import failed (%s): domain does not exist")
+ % account.username
+ )
+ if not user.can_access(domain):
+ raise exceptions.PermDeniedException
+ core_signals.can_create_object.send(
+ sender="import", context=user, object_type="mailboxes")
+ core_signals.can_create_object.send(
+ sender="import", context=domain, object_type="mailboxes")
+ account.save()
+ qset = models.Mailbox.objects.filter(address=mailbox, domain=domain)
+ if qset.exists():
+ raise exceptions.Conflict(
+ _("Mailbox {} already exists").format(account.email))
+ if len(row) == 1:
+ quota = None
+ else:
+ try:
+ quota = int(row[1].strip())
+ except ValueError:
+ raise exceptions.BadRequest(
+ _("Account import failed (%s): wrong quota value")
+ % account.username
+ )
+ use_domain_quota = True if not quota else False
+ mb = models.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.role == "DomainAdmins":
+ for domname in row[2:]:
+ try:
+ dom = models.Domain.objects.get(name=domname.strip())
+ except models.Domain.DoesNotExist:
+ continue
+ dom.add_admin(account)
+
+
+@receiver(core_signals.extra_admin_menu_entries)
+def admin_menu(sender, location, user, **kwargs):
+ """Add extra menu entries for admins."""
+ if location != "top_menu":
+ return []
+ entries = []
+ if user.has_perm("admin.view_domains"):
+ entries += [
+ {"name": "domains",
+ "url": reverse("admin:domain_list"),
+ "label": _("Domains")}
+ ]
+ conditions = (
+ user.has_perm("core.add_user"),
+ user.has_perm("admin.add_alias")
+ )
+ if any(conditions):
+ entries += [
+ {"name": "identities",
+ "url": reverse("admin:identity_list"),
+ "label": _("Identities")},
+ ]
+ return entries
+
+
+@receiver(core_signals.extra_user_menu_entries)
+def user_menu(sender, location, user, **kwargs):
+ """Add extra menu entries for users."""
+ if location != "uprefs_menu":
+ return []
+ if not hasattr(user, "mailbox"):
+ return []
+ return [
+ {"name": "forward",
+ "class": "ajaxnav",
+ "url": "forward/",
+ "label": _("Forward")}
+ ]
+
+
+@receiver(core_signals.user_login)
+def user_logged_in(sender, username, password, **kwargs):
+ """Store user password in session."""
+ request = lib_signals.get_request()
+ if hasattr(request.user, "mailbox"):
+ request.session["password"] = encrypt(password)
+
+
+@receiver(core_signals.account_role_changed)
+def grant_access_to_all_objects(sender, account, role, **kwargs):
+ """Grant all permissions if new role is SuperAdmin."""
+ if role != "SuperAdmins":
+ return
+ perm_models = [
+ core_models.User, models.Domain, models.DomainAlias, models.Mailbox,
+ models.Alias]
+ for model in perm_models:
+ permissions.grant_access_to_objects(
+ account, model.objects.all(),
+ ContentType.objects.get_for_model(model)
+ )
diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py
index 3e7c10f8f..14bc1d18e 100644
--- a/modoboa/admin/models/mailbox.py
+++ b/modoboa/admin/models/mailbox.py
@@ -75,7 +75,6 @@ class MailboxManager(Manager):
@python_2_unicode_compatible
class Mailbox(AdminObject):
-
"""User mailbox."""
address = models.CharField(
@@ -326,21 +325,6 @@ class Mailbox(AdminObject):
username=self.full_address)
super(Mailbox, self).save(*args, **kwargs)
- def delete(self, keepdir=False):
- """Custom delete method
-
- We try to delete the associated quota in the same time (it may
- has already been removed if we're deleting a domain).
-
- :param bool keepdir: delete the mailbox home dir on the
- filesystem or not
-
- """
- Quota.objects.filter(username=self.full_address).delete()
- if not keepdir:
- self.delete_dir()
- super(Mailbox, self).delete()
-
reversion.register(Mailbox)
diff --git a/modoboa/admin/signals.py b/modoboa/admin/signals.py
index 7d9be0803..dae12a7e0 100644
--- a/modoboa/admin/signals.py
+++ b/modoboa/admin/signals.py
@@ -2,10 +2,14 @@
import django.dispatch
-use_external_recipients = django.dispatch.Signal(providing_args=["recipients"])
+extra_admin_content = django.dispatch.Signal(
+ providing_args=["user", "location", "currentpage"])
extra_domain_actions = django.dispatch.Signal(
providing_args=["user", "domain"])
extra_domain_dashboard_widgets = django.dispatch.Signal(
providing_args=["user", "domain"])
+extra_domain_menu_entries = django.dispatch.Signal(
+ providing_args=["user"])
extra_account_dashboard_widgets = django.dispatch.Signal(
providing_args=["user", "account"])
+use_external_recipients = django.dispatch.Signal(providing_args=["recipients"])
diff --git a/modoboa/admin/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py
index 3cab73a96..a204105c4 100644
--- a/modoboa/admin/templatetags/admin_tags.py
+++ b/modoboa/admin/templatetags/admin_tags.py
@@ -6,7 +6,7 @@ from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _, ugettext_lazy
-from modoboa.lib import events
+from modoboa.core import signals as core_signals
from modoboa.lib.templatetags.lib_tags import render_link
from modoboa.lib.web_utils import render_actions
@@ -47,7 +47,10 @@ def domains_menu(selection, user, ajax_mode=True):
"url": reverse("admin:domain_statistics")}
]
if user.has_perm("admin.add_domain"):
- entries += events.raiseQueryEvent("ExtraDomainMenuEntries", user)
+ extra_entries = signals.extra_domain_menu_entries.send(
+ sender="domains_menu", user=user)
+ for entry in extra_entries:
+ entries += entry[1]
entries += [
{"name": "import",
"label": _("Import"),
@@ -157,7 +160,11 @@ def identity_actions(user, ident):
name = ident.__class__.__name__
objid = ident.id
if name == "User":
- actions = events.raiseQueryEvent("ExtraAccountActions", ident)
+ actions = []
+ result = core_signals.extra_account_actions.send(
+ sender="identity_actions", account=ident)
+ for action in result:
+ actions += result[1]
url = (
reverse("admin:account_change", args=[objid]) +
"?active_tab=default"
@@ -265,7 +272,10 @@ def gender(value, target):
@register.simple_tag
def get_extra_admin_content(user, target, currentpage):
- res = events.raiseQueryEvent(
- "ExtraAdminContent", user, target, currentpage
- )
- return mark_safe("".join(res))
+ results = signals.extra_admin_content.send(
+ sender="get_extra_admin_content",
+ user=user, location=target, currentpage=currentpage)
+ if not results:
+ return ""
+ results = reduce(lambda a, b: a + b, [result[1] for result in results])
+ return mark_safe("".join(results))
diff --git a/modoboa/admin/views/identity.py b/modoboa/admin/views/identity.py
index 466ced291..f2401bceb 100644
--- a/modoboa/admin/views/identity.py
+++ b/modoboa/admin/views/identity.py
@@ -188,8 +188,7 @@ def editaccount(request, pk):
@login_required
@permission_required("core.delete_user")
def delaccount(request, pk):
- keepdir = True if request.POST.get("keepdir", "false") == "true" else False
- User.objects.get(pk=pk).delete(request.user, keep_mb_dir=keepdir)
+ User.objects.get(pk=pk).delete()
return render_to_json_response(
ungettext("Account deleted", "Accounts deleted", 1)
)
diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py
index 95ccfde83..0f5234285 100644
--- a/modoboa/core/__init__.py
+++ b/modoboa/core/__init__.py
@@ -1,67 +1,3 @@
-
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _
-
-from modoboa.lib import events
-
-
-PERMISSIONS = {
- "SimpleUsers": [],
- "DomainAdmins": [
- ["core", "user", "add_user"],
- ["core", "user", "change_user"],
- ["core", "user", "delete_user"],
- ["admin", "domain", "view_domains"],
- ["admin", "domain", "view_domain"],
- ["admin", "mailbox", "add_mailbox"],
- ["admin", "mailbox", "change_mailbox"],
- ["admin", "mailbox", "delete_mailbox"],
- ["admin", "alias", "add_alias"],
- ["admin", "alias", "change_alias"],
- ["admin", "alias", "delete_alias"],
- ["admin", "mailbox", "view_mailboxes"],
- ["admin", "alias", "view_aliases"],
- ],
- "Resellers": [
- ["core", "user", "add_user"],
- ["core", "user", "change_user"],
- ["core", "user", "delete_user"],
- ["admin", "domain", "view_domains"],
- ["admin", "domain", "view_domain"],
- ["admin", "mailbox", "add_mailbox"],
- ["admin", "mailbox", "change_mailbox"],
- ["admin", "mailbox", "delete_mailbox"],
- ["admin", "alias", "add_alias"],
- ["admin", "alias", "change_alias"],
- ["admin", "alias", "delete_alias"],
- ["admin", "mailbox", "view_mailboxes"],
- ["admin", "alias", "view_aliases"],
- ["admin", "domain", "view_domains"],
- ["admin", "domain", "add_domain"],
- ["admin", "domain", "change_domain"],
- ["admin", "domain", "delete_domain"],
- ["admin", "domainalias", "add_domainalias"],
- ["admin", "domainalias", "change_domainalias"],
- ["admin", "domainalias", "delete_domainalias"],
- ]
-}
-
-
-@events.observe("TopNotifications")
-def check_for_new_versions(request, include_all):
- """Check if new versions are available."""
- from modoboa.core.utils import check_for_updates
-
- if not request.user.is_superuser:
- return []
- status, extensions = check_for_updates(request)
- if not status:
- return [{"id": "newversionavailable"}] if include_all else []
- return [{
- "id": "newversionavailable",
- "url": reverse("core:index") + "#info/",
- "text": _("One or more updates are available"),
- "level": "info",
- }]
+"""App config for core."""
default_app_config = "modoboa.core.apps.CoreConfig"
diff --git a/modoboa/core/apps.py b/modoboa/core/apps.py
index df124bd32..0dca0a28f 100644
--- a/modoboa/core/apps.py
+++ b/modoboa/core/apps.py
@@ -6,38 +6,8 @@ from django.utils.translation import ugettext_lazy
BASE_EVENTS = [
"AccountCreated",
- "AccountAutoCreated",
+
"AccountModified",
- "AccountDeleted",
- "AccountExported",
- "AccountImported",
- "PasswordUpdated",
- "ExtraAccountActions",
- "RoleChanged",
- "GetExtraRoles",
- "GetExtraRolePermissions",
- "PasswordChange",
- "UserCanSetRole",
-
- "InitialDataLoaded",
-
- "UserMenuDisplay",
- "AdminMenuDisplay",
- "GetStaticContent",
-
- "UserLogin",
- "UserLogout",
-
- "GetAnnouncement",
-
- "TopNotifications",
- "ExtraAdminContent",
-
- "ExtraUprefsRoutes",
- "ExtraUprefsJS",
-
- "ExtraFormFields",
- "SaveExtraFormFields",
]
@@ -57,7 +27,6 @@ def load_core_settings():
class CoreConfig(AppConfig):
-
"""App configuration."""
name = "modoboa.core"
diff --git a/modoboa/core/constants.py b/modoboa/core/constants.py
index 8adce607f..6a9d2be6e 100644
--- a/modoboa/core/constants.py
+++ b/modoboa/core/constants.py
@@ -42,3 +42,44 @@ LDAP_GROUP_TYPES = (
("posixgroup", "PosixGroup"),
("groupofnames", "GroupOfNames"),
)
+
+PERMISSIONS = {
+ "SimpleUsers": [],
+ "DomainAdmins": [
+ ["core", "user", "add_user"],
+ ["core", "user", "change_user"],
+ ["core", "user", "delete_user"],
+ ["admin", "domain", "view_domains"],
+ ["admin", "domain", "view_domain"],
+ ["admin", "mailbox", "add_mailbox"],
+ ["admin", "mailbox", "change_mailbox"],
+ ["admin", "mailbox", "delete_mailbox"],
+ ["admin", "alias", "add_alias"],
+ ["admin", "alias", "change_alias"],
+ ["admin", "alias", "delete_alias"],
+ ["admin", "mailbox", "view_mailboxes"],
+ ["admin", "alias", "view_aliases"],
+ ],
+ "Resellers": [
+ ["core", "user", "add_user"],
+ ["core", "user", "change_user"],
+ ["core", "user", "delete_user"],
+ ["admin", "domain", "view_domains"],
+ ["admin", "domain", "view_domain"],
+ ["admin", "mailbox", "add_mailbox"],
+ ["admin", "mailbox", "change_mailbox"],
+ ["admin", "mailbox", "delete_mailbox"],
+ ["admin", "alias", "add_alias"],
+ ["admin", "alias", "change_alias"],
+ ["admin", "alias", "delete_alias"],
+ ["admin", "mailbox", "view_mailboxes"],
+ ["admin", "alias", "view_aliases"],
+ ["admin", "domain", "view_domains"],
+ ["admin", "domain", "add_domain"],
+ ["admin", "domain", "change_domain"],
+ ["admin", "domain", "delete_domain"],
+ ["admin", "domainalias", "add_domainalias"],
+ ["admin", "domainalias", "change_domainalias"],
+ ["admin", "domainalias", "delete_domainalias"],
+ ]
+}
diff --git a/modoboa/core/context_processors.py b/modoboa/core/context_processors.py
index 69e3a338a..d3e3d3690 100644
--- a/modoboa/core/context_processors.py
+++ b/modoboa/core/context_processors.py
@@ -1,6 +1,6 @@
"""Custom context processors."""
-from modoboa.lib import events
+from . import signals
def top_notifications(request):
@@ -9,8 +9,11 @@ def top_notifications(request):
return {}
interval = request.localconfig.parameters.get_value(
"top_notifications_check_interval")
+ notifications = signals.get_top_notifications.send(
+ sender="top_notifications", include_all=False)
+ notifications = reduce(
+ lambda a, b: a + b, [notif[1] for notif in notifications])
return {
"notifications_check_interval": interval * 1000,
- "top_notifications": events.raiseQueryEvent(
- "TopNotifications", request, False)
+ "top_notifications": notifications
}
diff --git a/modoboa/core/handlers.py b/modoboa/core/handlers.py
index 576ed3798..477795b9b 100644
--- a/modoboa/core/handlers.py
+++ b/modoboa/core/handlers.py
@@ -2,7 +2,8 @@
import logging
-from django.db.models.signals import post_delete
+from django.core.urlresolvers import reverse
+from django.db.models import signals
from django.dispatch import receiver
from django.utils.translation import ugettext as _
@@ -10,7 +11,13 @@ from django.contrib.sites import models as sites_models
from reversion import revisions as reversion
+from modoboa.lib import exceptions
+from modoboa.lib import permissions
+from modoboa.lib.signals import get_request
+
from . import models
+from . import signals as core_signals
+from . import utils
@receiver(reversion.post_revision_commit)
@@ -44,14 +51,13 @@ def post_revision_commit(sender, **kwargs):
getattr(logger, level)(message)
-@receiver(post_delete)
+@receiver(signals.post_delete)
def log_object_removal(sender, instance, **kwargs):
"""Custom post-delete hook.
We want to know who was responsible for an object deletion.
"""
from reversion.models import Version
- from modoboa.lib.signals import get_request
if not reversion.is_registered(sender):
return
@@ -75,3 +81,50 @@ def create_local_config(sender, **kwargs):
return
models.LocalConfig.objects.create(
site=sites_models.Site.objects.get_current())
+
+
+@receiver(signals.pre_delete, sender=models.User)
+def update_permissions(sender, instance, **kwargs):
+ """Permissions cleanup."""
+ from_user = get_request().user
+ if from_user == instance:
+ raise exceptions.PermDeniedException(
+ _("You can't delete your own account")
+ )
+
+ if not from_user.can_access(instance):
+ raise exceptions.PermDeniedException
+
+ # We send an additional signal before permissions are removed
+ core_signals.account_deleted.send(
+ sender="update_permissions", user=instance)
+ owner = permissions.get_object_owner(instance)
+ if owner == instance:
+ # The default admin is being removed...
+ owner = from_user
+ # Change ownership of existing objects
+ for ooentry in instance.objectaccess_set.filter(is_owner=True):
+ if ooentry.content_object is not None:
+ permissions.grant_access_to_object(
+ owner, ooentry.content_object, True)
+ permissions.ungrant_access_to_object(
+ ooentry.content_object, instance)
+ # Remove existing permissions on this user
+ permissions.ungrant_access_to_object(instance)
+
+
+@receiver(core_signals.get_top_notifications)
+def check_for_new_versions(sender, include_all, **kwargs):
+ """Check if new versions are available."""
+ request = get_request()
+ if not request.user.is_superuser:
+ return []
+ status, extensions = utils.check_for_updates(request)
+ if not status:
+ return [{"id": "newversionavailable"}] if include_all else []
+ return [{
+ "id": "newversionavailable",
+ "url": reverse("core:index") + "#info/",
+ "text": _("One or more updates are available"),
+ "level": "info",
+ }]
diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py
index 09dbcd7bb..d9df3d803 100644
--- a/modoboa/core/management/commands/load_initial_data.py
+++ b/modoboa/core/management/commands/load_initial_data.py
@@ -8,14 +8,14 @@
from django.contrib.auth.models import Group
from django.core.management.base import BaseCommand
-from modoboa.core import PERMISSIONS
-from modoboa.core.extensions import exts_pool
from modoboa.lib.cryptutils import random_key
-from modoboa.lib import events
from modoboa.lib.permissions import add_permissions_to_group
import modoboa.relaydomains.models as relay_models
+from ... import constants
+from ... import extensions
from ... import models
+from ... import signals
class Command(BaseCommand):
@@ -54,27 +54,26 @@ class Command(BaseCommand):
for service_name in ["relay", "smtp"]:
relay_models.Service.objects.get_or_create(name=service_name)
- exts_pool.load_all()
+ extensions.exts_pool.load_all()
- superadmin = models.User.objects.filter(is_superuser=True).first()
- groups = PERMISSIONS.keys() + [
- role[0] for role
- in events.raiseQueryEvent("GetExtraRoles", superadmin, None)
- ]
+ groups = constants.PERMISSIONS.keys()
for groupname in groups:
group, created = Group.objects.get_or_create(name=groupname)
+ results = signals.extra_role_permissions.send(
+ sender=self.__class__, role=groupname)
permissions = (
- PERMISSIONS.get(groupname, []) +
- events.raiseQueryEvent("GetExtraRolePermissions", groupname)
+ constants.PERMISSIONS.get(groupname, []) +
+ reduce(lambda a, b: a + b, [result[1] for result in results])
)
if not permissions:
continue
add_permissions_to_group(group, permissions)
- for extname in exts_pool.extensions.keys():
- extension = exts_pool.get_extension(extname)
+ for extname in extensions.exts_pool.extensions.keys():
+ extension = extensions.exts_pool.get_extension(extname)
extension.load_initial_data()
- events.raiseEvent("InitialDataLoaded", extname)
+ signals.initial_data_loaded.send(
+ sender=self.__class__, extname=extname)
if options["extra_fixtures"]:
from modoboa.admin import factories
diff --git a/modoboa/core/models.py b/modoboa/core/models.py
index 5e4c1a7f6..29617c4ed 100644
--- a/modoboa/core/models.py
+++ b/modoboa/core/models.py
@@ -27,6 +27,7 @@ from modoboa.lib.exceptions import (
from modoboa.parameters import tools as param_tools
from . import constants
+from . import signals
try:
@@ -103,41 +104,6 @@ class User(PermissionsMixin):
super(User, self).__init__(*args, **kwargs)
self.parameters = param_tools.Manager("user", self._parameters)
- def delete(self, fromuser, *args, **kwargs):
- """Custom delete method
-
- To check permissions properly, we need to make a distinction
- between 2 cases:
-
- * If the user owns a mailbox, the check is made on that object
- (useful for domain admins)
-
- * Otherwise, the check is made on the user
- """
- from modoboa.lib.permissions import \
- get_object_owner, grant_access_to_object, ungrant_access_to_object
-
- if fromuser == self:
- raise PermDeniedException(
- _("You can't delete your own account")
- )
-
- if not fromuser.can_access(self):
- raise PermDeniedException
-
- owner = get_object_owner(self)
- if owner == self:
- # The default admin is being removed...
- owner = fromuser
- for ooentry in self.objectaccess_set.filter(is_owner=True):
- if ooentry.content_object is not None:
- grant_access_to_object(owner, ooentry.content_object, True)
- ungrant_access_to_object(ooentry.content_object, self)
-
- events.raiseEvent("AccountDeleted", self, fromuser, **kwargs)
- ungrant_access_to_object(self)
- super(User, self).delete()
-
def _crypt_password(self, raw_value):
"""Crypt the local password using the appropriate scheme.
@@ -177,9 +143,9 @@ class User(PermissionsMixin):
LDAPAuthBackend().update_user_password(
self.username, curvalue, raw_value
)
- events.raiseEvent(
- "PasswordUpdated", self, raw_value, self.pk is None
- )
+ signals.account_password_updated.send(
+ sender=self.__class__,
+ account=self, password=raw_value, created=self.pk is None)
def check_password(self, raw_value):
"""Compare raw_value to current password."""
@@ -329,7 +295,8 @@ class User(PermissionsMixin):
"""
if role is None or self.role == role:
return
- events.raiseEvent("RoleChanged", self, role)
+ signals.account_role_changed.send(
+ sender=self.__class__, account=self, role=role)
self.groups.clear()
if role == "SuperAdmins":
self.is_superuser = True
@@ -378,7 +345,7 @@ class User(PermissionsMixin):
"account", loginname, password, first name, last name, enabled, role
- Additional fields can be added using the *AccountImported* event.
+ Additional fields can be added using the *account_imported* signal.
:param user: a ``core.User`` instance
:param row: a list containing the expected information
@@ -431,7 +398,8 @@ class User(PermissionsMixin):
self.post_create(user)
if len(row) < 8:
return
- events.raiseEvent("AccountImported", user, self, row[7:])
+ signals.account_imported.send(
+ sender=self.__class__, user=user, account=self, row=row[7:])
def to_csv(self, csvwriter):
"""Export this account.
@@ -450,7 +418,10 @@ class User(PermissionsMixin):
self.role,
self.email.encode("utf-8")
]
- row += events.raiseQueryEvent("AccountExported", self)
+ results = signals.account_exported.send(
+ sender=self.__class__, user=self)
+ for result in results:
+ row += result[1]
csvwriter.writerow(row)
reversion.register(User)
@@ -473,7 +444,8 @@ def populate_callback(user, group="SimpleUsers"):
user.post_create(sadmins[0])
for su in sadmins[1:]:
grant_access_to_object(su, user)
- events.raiseEvent("AccountAutoCreated", user)
+ signals.account_auto_created.send(
+ sender="populate_callback", user=user)
class ObjectAccess(models.Model):
diff --git a/modoboa/core/signals.py b/modoboa/core/signals.py
index 3169a6313..c6d406739 100644
--- a/modoboa/core/signals.py
+++ b/modoboa/core/signals.py
@@ -2,6 +2,37 @@
import django.dispatch
+account_auto_created = django.dispatch.Signal(providing_args=["user"])
+account_exported = django.dispatch.Signal(providing_args=["account"])
+account_imported = django.dispatch.Signal(
+ providing_args=["user", "account", "row"])
+account_deleted = django.dispatch.Signal(providing_args=["user"])
+account_role_changed = django.dispatch.Signal(
+ providing_args=["account", "role"])
+account_password_updated = django.dispatch.Signal(
+ providing_args=["account", "password", "created"])
+allow_password_change = django.dispatch.Signal(providing_args=["user"])
can_create_object = django.dispatch.Signal(
providing_args=["context", "object_type", "count"])
+extra_account_actions = django.dispatch.Signal(providing_args=["account"])
+extra_admin_menu_entries = django.dispatch.Signal(
+ providing_args=["location", "user"])
+extra_form_fields = django.dispatch.Signal(
+ providing_args=["form", "instance"])
+extra_role_permissions = django.dispatch.Signal(providing_args=["role"])
+extra_static_content = django.dispatch.Signal(
+ providing_args=["caller", "st_type", "user"])
+extra_uprefs_routes = django.dispatch.Signal()
+extra_user_menu_entries = django.dispatch.Signal(
+ providing_args=["location", "user"])
+get_announcements = django.dispatch.Signal(providing_args=["location"])
+get_top_notifications = django.dispatch.Signal(
+ providing_args=["include_all"])
+initial_data_loaded = django.dispatch.Signal(providing_args=["extname"])
register_postfix_maps = django.dispatch.Signal()
+save_extra_form_fields = django.dispatch.Signal(
+ providing_args=["form", "instance", "cleaned_data"])
+user_can_set_role = django.dispatch.Signal(
+ providing_args=["user", "role", "account"])
+user_login = django.dispatch.Signal(
+ providing_args=["username", "password"])
diff --git a/modoboa/core/templates/core/user_index.html b/modoboa/core/templates/core/user_index.html
index db4896e44..bf38bfb46 100644
--- a/modoboa/core/templates/core/user_index.html
+++ b/modoboa/core/templates/core/user_index.html
@@ -12,7 +12,7 @@
-"""]
diff --git a/modoboa/limits/handlers.py b/modoboa/limits/handlers.py
index b2ffad27d..75657ed29 100644
--- a/modoboa/limits/handlers.py
+++ b/modoboa/limits/handlers.py
@@ -2,6 +2,7 @@
from django.db.models import signals
from django.dispatch import receiver
+from django.template.loader import render_to_string
from django.contrib.contenttypes.models import ContentType
@@ -10,6 +11,7 @@ from modoboa.admin import signals as admin_signals
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.lib import signals as lib_signals
+from modoboa.lib import permissions
from modoboa.parameters import tools as param_tools
from . import lib
@@ -99,3 +101,117 @@ def display_admin_limits(sender, user, account, **kwargs):
account.userobjectlimit_set.select_related("content_type"))
}
}]
+
+
+@receiver(core_signals.account_deleted)
+def move_resource(sender, user, **kwargs):
+ """Move remaining resource to another user."""
+ owner = permissions.get_object_owner(user)
+ if owner.is_superuser or owner.role != "Resellers":
+ return
+ utils.move_pool_resource(owner, user)
+
+
+@receiver(core_signals.user_can_set_role)
+def user_can_set_role(sender, user, role, account=None, **kwargs):
+ """Check if the user can still set this role.
+
+ The only interesting case concerns resellers defining new domain
+ administrators. We want to check if they are allowed to do this
+ operation before any modification is made to :keyword:`account`.
+
+ :param ``User`` user: connected user
+ :param str role: role to check
+ :param ``User`` account: account modified (None on creation)
+ """
+ condition = (
+ not param_tools.get_global_parameter("enable_admin_limits") or
+ role != "DomainAdmins")
+ if condition:
+ return True
+ lname = "domain_admins"
+ condition = (
+ user.is_superuser or
+ not user.userobjectlimit_set.get(name=lname).is_exceeded()
+ )
+ if condition:
+ return True
+ if account is not None and account.role == role:
+ return True
+ return False
+
+
+@receiver(core_signals.extra_static_content)
+def get_static_content(sender, caller, st_type, user, **kwargs):
+ """Add extra static content."""
+ condition = (
+ not param_tools.get_global_parameter("enable_admin_limits") or
+ caller not in ["domains", "identities"] or
+ user.role in ["SuperAdmins", "SimpleUsers"]
+ )
+ if condition:
+ return []
+ if st_type == "css":
+ return ["""
+"""]
+ return ["""
+
+"""]
+
+
+@receiver(admin_signals.extra_admin_content)
+def display_pool_usage(sender, user, location, currentpage, **kwargs):
+ """Display current usage."""
+ condition = (
+ not param_tools.get_global_parameter("enable_admin_limits") or
+ location != "leftcol" or user.is_superuser)
+ if condition:
+ return []
+ if currentpage == "identities":
+ names = ["mailboxes", "mailbox_aliases"]
+ if user.has_perm("admin.add_domain"):
+ names += ["domain_admins"]
+ else:
+ exceptions = ["domain_admins", "mailboxes", "mailbox_aliases"]
+ names = [
+ name for name, tpl in utils.get_user_limit_templates()
+ if name not in exceptions and
+ ("required_role" not in tpl or
+ tpl["required_role"] == user.role)
+ ]
+
+ limits = user.userobjectlimit_set.filter(name__in=names, max_value__gt=0)
+ if len(limits) == 0:
+ return []
+ return [
+ render_to_string("limits/poolusage.html",
+ dict(limits=limits))
+ ]
+
+
+@receiver(core_signals.account_role_changed)
+def move_pool_resource(sender, account, role, **kwargs):
+ """Move remaining resource to owner if needed."""
+ owner = permissions.get_object_owner(account)
+ if not owner or owner.is_superuser or owner.role != "Resellers":
+ # Domain admins can't change the role so nothing to check.
+ return
+
+ if role not in ["DomainAdmins", "Resellers"]:
+ utils.move_pool_resource(owner, account)
diff --git a/modoboa/limits/utils.py b/modoboa/limits/utils.py
index 6cbf3d1b0..25cb45629 100644
--- a/modoboa/limits/utils.py
+++ b/modoboa/limits/utils.py
@@ -12,3 +12,18 @@ def get_user_limit_templates():
def get_domain_limit_templates():
"""Return defined templates."""
return constants.DEFAULT_DOMAIN_LIMITS.items()
+
+
+def move_pool_resource(owner, user):
+ """Move resource from one pool to another.
+
+ When an account doesn't need a pool anymore, we move the
+ associated resource to the pool of its owner.
+ """
+ for name, ltpl in get_user_limit_templates():
+ l = user.userobjectlimit_set.get(name=name)
+ if l.max_value < 0:
+ continue
+ ol = owner.userobjectlimit_set.get(name=name)
+ ol.max_value += l.max_value
+ ol.save()
diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py
index b7c173ae3..1be26d468 100644
--- a/modoboa/parameters/tools.py
+++ b/modoboa/parameters/tools.py
@@ -174,7 +174,6 @@ def get_global_parameter(name, app=None, **kwargs):
:param name: the parameter's name
:param app: the application owning the parameter
:return: the corresponding value
-
"""
if app is None:
app = guess_extension_name()
diff --git a/modoboa/relaydomains/constants.py b/modoboa/relaydomains/constants.py
new file mode 100644
index 000000000..951bfa1af
--- /dev/null
+++ b/modoboa/relaydomains/constants.py
@@ -0,0 +1,12 @@
+"""Relaydomains app constants."""
+
+PERMISSIONS = {
+ "Resellers": [
+ ("relaydomains", "relaydomain", "add_relaydomain"),
+ ("relaydomains", "relaydomain", "change_relaydomain"),
+ ("relaydomains", "relaydomain", "delete_relaydomain"),
+ ("relaydomains", "service", "add_service"),
+ ("relaydomains", "service", "change_service"),
+ ("relaydomains", "service", "delete_service")
+ ]
+}
diff --git a/modoboa/relaydomains/general_callbacks.py b/modoboa/relaydomains/general_callbacks.py
index f1342d564..56f3e7de3 100644
--- a/modoboa/relaydomains/general_callbacks.py
+++ b/modoboa/relaydomains/general_callbacks.py
@@ -1,46 +1,11 @@
"""General event callbacks."""
-from django.conf import settings
-from django.template import Template, Context
from django.utils.translation import ugettext_lazy, ugettext as _
from modoboa.lib import events
from .forms import RelayDomainWizardStep, RelayDomainFormGeneral
-PERMISSIONS = {
- "Resellers": [
- ("relaydomains", "relaydomain", "add_relaydomain"),
- ("relaydomains", "relaydomain", "change_relaydomain"),
- ("relaydomains", "relaydomain", "delete_relaydomain"),
- ("relaydomains", "service", "add_service"),
- ("relaydomains", "service", "change_service"),
- ("relaydomains", "service", "delete_service")
- ]
-}
-
-
-@events.observe("GetExtraRolePermissions")
-def extra_permissions(rolename):
- """Return extra permissions for Resellers."""
- return PERMISSIONS.get(rolename, [])
-
-
-@events.observe('GetStaticContent')
-def static_content(caller, st_type, user):
- if caller != 'domains' or st_type != 'js':
- return []
-
- t = Template("""
-
-""")
- return [t.render(Context({'STATIC_URL': settings.STATIC_URL}))]
-
@events.observe('ExtraDomainFilters')
def extra_domain_filters():
diff --git a/modoboa/relaydomains/handlers.py b/modoboa/relaydomains/handlers.py
index bd326199e..c2d5adcee 100644
--- a/modoboa/relaydomains/handlers.py
+++ b/modoboa/relaydomains/handlers.py
@@ -1,13 +1,16 @@
"""Django signal handlers for relaydomains."""
+from django.conf import settings
from django.db.models import signals
from django.dispatch import receiver
+from django.template import Template, Context
from modoboa.admin import models as admin_models
from modoboa.admin.signals import use_external_recipients
from modoboa.core import signals as core_signals
from modoboa.lib.email_utils import split_mailbox
+from . import constants
from . import models
from . import postfix_maps
@@ -52,3 +55,26 @@ def register_postfix_maps(sender, **kwargs):
postfix_maps.SplitedDomainsTransportMap,
postfix_maps.RelayRecipientVerification
]
+
+
+@receiver(core_signals.extra_role_permissions)
+def extra_role_permissions(sender, role, **kwargs):
+ """Add permissions to the Resellers group."""
+ return constants.PERMISSIONS.get(role, [])
+
+
+@receiver(core_signals.extra_static_content)
+def static_content(sender, caller, st_type, user, **kwargs):
+ """Add extra static content."""
+ if caller != "domains" or st_type != "js":
+ return []
+
+ t = Template("""
+
+""")
+ return [t.render(Context({"STATIC_URL": settings.STATIC_URL}))]
diff --git a/modoboa/urls.py b/modoboa/urls.py
index 8568c5558..e01bc7268 100644
--- a/modoboa/urls.py
+++ b/modoboa/urls.py
@@ -2,7 +2,7 @@ from django.conf import settings
from django.conf.urls import patterns, include, url
from modoboa.core.extensions import exts_pool
-from modoboa.lib import events
+from modoboa.core import signals as core_signals
urlpatterns = patterns(
@@ -22,10 +22,14 @@ urlpatterns += patterns(
*exts_pool.load_all()
)
-urlpatterns += patterns(
- '',
- *events.raiseQueryEvent("ExtraUprefsRoutes")
-)
+extra_routes = core_signals.extra_uprefs_routes.send(sender="urls")
+if extra_routes:
+ extra_routes = reduce(
+ lambda a, b: a + b, [route for route in extra_routes])
+ urlpatterns += patterns(
+ '',
+ *extra_routes
+ )
# API urls
urlpatterns += patterns(