diff --git a/extensions/limits/templatetags/limits_tags.py b/extensions/limits/templatetags/limits_tags.py
index 739eb115b..e61597397 100644
--- a/extensions/limits/templatetags/limits_tags.py
+++ b/extensions/limits/templatetags/limits_tags.py
@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from modoboa.lib.webutils import static_url, render_actions
-from modoboa.extensions import limits
+from modoboa.extensions.limits import views
register = template.Library()
@@ -16,11 +16,11 @@ def resellers_menu(user):
"img" : static_url("pics/add.png"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:350,y:300}}",
- "url" : reverse(limits.views.new_reseller)},
+ "url" : reverse(views.new_reseller)},
{"name" : "remove",
"label" : _("Remove"),
"img" : static_url("pics/remove.png"),
- "url" : reverse(limits.views.delete_resellers)
+ "url" : reverse(views.delete_resellers)
}]
return render_to_string("common/menulist.html",
{"entries" : entries, "user" : user})
@@ -29,13 +29,13 @@ def resellers_menu(user):
def reseller_actions(user, resid):
actions = [
{"name" : "editreseller",
- "url" : reverse(limits.views.edit_reseller, args=[resid]),
+ "url" : reverse(views.edit_reseller, args=[resid]),
"img" : static_url("pics/edit.png"),
"title" : _("Edit reseller"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:350,y:300}}"},
{"name" : "editlimits",
- "url" : reverse(limits.views.edit_limits_pool, args=[resid]),
+ "url" : reverse(views.edit_limits_pool, args=[resid]),
"img" : static_url("pics/settings.png"),
"title" : _("Edit limits allocated to this reseller"),
"class" : "boxed",
diff --git a/extensions/limits/urls.py b/extensions/limits/urls.py
index cadf99fed..17f89d920 100644
--- a/extensions/limits/urls.py
+++ b/extensions/limits/urls.py
@@ -2,9 +2,6 @@ from django.conf.urls.defaults import *
urlpatterns = patterns(
'modoboa.extensions.limits.views',
- (r'^$', 'resellers'),
- (r'reseller/new/$', 'new_reseller'),
(r'reseller/edit/(?P
\d+)/$', 'edit_reseller'),
- (r'reseller/delete/$', 'delete_resellers'),
(r'pool/edit/(?P\d+)/$', 'edit_limits_pool'),
)
diff --git a/extensions/limits/views.py b/extensions/limits/views.py
index 9c9475a4f..714c457e4 100644
--- a/extensions/limits/views.py
+++ b/extensions/limits/views.py
@@ -1,103 +1,58 @@
# coding: utf-8
-from django.utils.translation import ugettext as _, ungettext
+from django.utils.translation import ugettext as _
from django.db import IntegrityError
from django.contrib.auth.models import *
from django.contrib import messages
from django.contrib.auth.decorators \
import login_required, user_passes_test
from django.core.urlresolvers import reverse
-from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-from tables import *
from forms import *
-from modoboa.lib import parameters
from modoboa.lib.webutils import _render, ajax_response
@login_required
@user_passes_test(lambda u: u.is_superuser)
-def resellers(request, tplname="limits/resellers.html"):
- users = User.objects.filter(groups__name="Resellers")
- paginator = Paginator(users,
- int(parameters.get_admin("ITEMS_PER_PAGE", app="admin")))
- page = int(request.GET.get("page", "1"))
- try:
- subset = paginator.page(page)
- except (EmptyPage, PageNotAnInteger):
- subset = paginator.page(paginator.num_pages)
-
- ctx = dict(table=ResellersTable(request, subset.object_list),
- page=subset,
- paginator=paginator)
- return _render(request, tplname, ctx)
-
-def validate_reseller(request, form, action, tplname="limits/reseller.html"):
- error = None
- if form.is_valid():
- try:
- form.save()
- except IntegrityError:
- error = _("User with this Username already exists")
- else:
- msg = _("Reseller created") if action == "create" \
- else _("Reseller updated")
- messages.info(request, msg, fail_silently=True)
- return ajax_response(request, url=reverse(resellers))
- submit_label = _("Create") if action == "create" else _("Update")
- ctx = dict(form=form, error=error, submit_label=submit_label)
- return ajax_response(request, status="ko", template=tplname, **ctx)
-
-@login_required
-@user_passes_test(lambda u: u.is_superuser)
-def new_reseller(request, tplname="limits/reseller.html"):
- if request.method == "POST":
- form = ResellerWithPasswordForm(request.POST)
- return validate_reseller(request, form, "create")
-
- form = ResellerWithPasswordForm()
- ctx = dict(form=form, submit_label=_("Create"))
- return _render(request, tplname, ctx)
-
-@login_required
-@user_passes_test(lambda u: u.is_superuser)
-def edit_reseller(request, resid, tplname="limits/reseller.html"):
+def edit_reseller(request, resid, tplname="admin/add_permission.html"):
reseller = User.objects.get(pk=resid)
+ ctx = dict(title=_("Edit reseller"), submit_label=_("Update"),
+ addurl=reverse(edit_reseller, args=[resid]))
if request.method == "POST":
if request.POST.get("password1", "") != "" and \
request.POST.get("password2", "") != "":
form = ResellerWithPasswordForm(request.POST, instance=reseller)
else:
form = ResellerForm(request.POST, instance=reseller)
- return validate_reseller(request, form, action="update")
+ if form.is_valid():
+ try:
+ form.save()
+ except IntegrityError:
+ error = _("User with this Username already exists")
+ else:
+ messages.info(request, _("Reseller updated"), fail_silently=True)
+ return ajax_response(request,
+ url=reverse("modoboa.admin.views.permissions"))
+
+ ctx.update(form=form, error=error)
+ return ajax_response(request, status="ko", template=tplname, **ctx)
form = ResellerWithPasswordForm(instance=reseller)
- ctx = dict(form=form, submit_label=_("Update"))
+ ctx.update(form=form)
return _render(request, tplname, ctx)
-@login_required
-@user_passes_test(lambda u: u.is_superuser)
-def delete_resellers(request):
- selection = map(lambda idx: int(idx), request.GET.get("selection", "").split(","))
- if not len(selection):
- raise Exception("")
-
- User.objects.filter(id__in=selection).delete()
- msg = ungettext("Reselled deleted", "Resellers deleted", len(selection))
- messages.info(request, msg, fail_silently=True)
- return ajax_response(request)
-
@login_required
@user_passes_test(lambda u: u.is_superuser)
def edit_limits_pool(request, resid, tplname="limits/pool.html"):
reseller = User.objects.get(pk=resid)
+ ctx = dict(reseller=reseller)
if request.method == "POST":
form = ResellerPoolForm(request.POST)
if form.is_valid():
form.save_new_limits(reseller.limitspool)
messages.info(request, _("Pool updated"), fail_silently=True)
- return ajax_response(request, url=reverse(resellers))
- ctx = dict(form=form)
+ return ajax_response(request, url=reverse("modoboa.admin.views.permissions"))
+ ctx.update(form=form)
return ajax_response(request, status="ko", template=tplname, **ctx)
form = ResellerPoolForm()
form.load_from_user(reseller)
- ctx = dict(form=form, reseller=reseller)
+ ctx.update(form=form)
return _render(request, tplname, ctx)
diff --git a/lib/events.py b/lib/events.py
index 25d23004a..76e475d99 100644
--- a/lib/events.py
+++ b/lib/events.py
@@ -8,17 +8,30 @@ This module provides a simple way of managing events between Modoboa
core application and additional components.
"""
+from functools import wraps
+import inspect
+import re
events = [
"CanCreateDomain",
"CreateDomain",
"DeleteDomain",
+
+ "CanCreateDomainAlias",
+ "DomainAliasCreated",
+ "DomainAliasDeleted",
+
+ "CanCreateMailbox",
"CreateMailbox",
"DeleteMailbox",
"ModifyMailbox",
- "GetUserDomains",
-
+ "CanCreateMailboxAlias",
+ "MailboxAliasCreated",
+ "MailboxAliasDeleted",
+
+ "DomainAdminCreated",
+
"PasswordChange",
"UserMenuDisplay",
@@ -33,7 +46,9 @@ events = [
"UserLogin",
"UserLogout",
- "GetAnnouncement"
+ "GetAnnouncement",
+
+ "AdminFooterDisplay",
]
callbacks = {}
@@ -54,6 +69,49 @@ def register(event, callback):
callbacks[event].append(callback)
return 1
+class observe(object):
+ """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.
+
+ .. 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
+ """
+ def __init__(self, evtname):
+ self.evtname = evtname
+
+ 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.__guess_extension_name(modname)
+ @wraps(f)
+ def wrapped_f(*args):
+ if extname:
+ from modoboa.admin.models import Extension
+ try:
+ ext = Extension.objects.get(name=extname)
+ except Extension.DoesNotExist:
+ return []
+ if not ext.enabled:
+ return []
+ return f(*args)
+
+ register(self.evtname, wrapped_f)
+ return wrapped_f
+
def unregister(event, callback):
"""Unregister a callback for a specific event
diff --git a/lib/exceptions.py b/lib/exceptions.py
new file mode 100644
index 000000000..381064da9
--- /dev/null
+++ b/lib/exceptions.py
@@ -0,0 +1,18 @@
+# coding: utf-8
+"""
+:mod:`exceptions` --- Custom Modoboa exceptions
+-----------------------------------------------
+
+"""
+from django.utils.translation import ugettext as _
+
+class ModoboaException(Exception):
+ pass
+
+class PermDeniedException(ModoboaException):
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return _("Permission denied: %s" % self.msg)
+
diff --git a/lib/ldaputils.py b/lib/ldaputils.py
index 9d9f42143..aa1fff912 100644
--- a/lib/ldaputils.py
+++ b/lib/ldaputils.py
@@ -33,6 +33,7 @@ Extracted from `this blog
one instance to another.
"""
+import sys
import ldap
import re
import base64
diff --git a/lib/middleware.py b/lib/middleware.py
index 7f74557e0..f4dab641f 100644
--- a/lib/middleware.py
+++ b/lib/middleware.py
@@ -3,6 +3,8 @@
import re
from django.http import Http404, HttpResponseRedirect
from modoboa.admin.models import Extension
+from modoboa.lib.webutils import _render_error, ajax_response
+from modoboa.lib.exceptions import ModoboaException
class ExtControlMiddleware(object):
def process_view(self, request, view, args, kwargs):
@@ -22,3 +24,14 @@ class AjaxLoginRedirect(object):
if type(response) == HttpResponseRedirect:
response.status_code = 278
return response
+
+class CommonExceptionCatcher(object):
+ def process_exception(self, request, exception):
+ if not isinstance(exception, ModoboaException):
+ return None
+
+ if not request.is_ajax():
+ return _render_error(request, errortpl="error_simple",
+ user_context=dict(error=str(exception)))
+
+ return ajax_response(request, status="ko", respmsg=str(exception))
diff --git a/static/css/box.css b/static/css/box.css
index 9c0d49370..6c7c16e84 100644
--- a/static/css/box.css
+++ b/static/css/box.css
@@ -26,7 +26,7 @@ input {
}
input[type=text], input[type=password] {
- width: 140px
+ width: 140px;
}
/*input:hover {
@@ -102,6 +102,10 @@ td.even {
font-weight: bold;
}
+.center {
+ text-align: center;
+}
+
#id_username {
width: 120px;
}
@@ -139,6 +143,15 @@ td.even {
clear: both;
}
+ul {
+ list-style: none;
+ padding: 0;
+}
+
+li {
+ display: inline;
+}
+
/*
* clearfix related
*/
@@ -164,3 +177,15 @@ html[xmlns] .clearfix {
height: 1%;
overflow: visible;
}
+
+/*
+ * Domain admin form
+ */
+#createfrom {
+ margin: 10px auto;
+ width: auto;
+}
+
+#permform > #id_name {
+ width: 180px;
+}
\ No newline at end of file
diff --git a/static/css/default.css b/static/css/default.css
index 6d5a5ed31..2df04d698 100644
--- a/static/css/default.css
+++ b/static/css/default.css
@@ -39,9 +39,14 @@ div#footer {
font-size: 1em;
}
+#adminfo {
+ float: left;
+ padding: 5px;
+}
+
#navbar {
float: right;
- padding: 0;
+ padding: 5px;
margin: 0;
text-align: right;
}
diff --git a/static/js/confirmation.js b/static/js/confirmation.js
index cd3c9e75e..670e90cfa 100644
--- a/static/js/confirmation.js
+++ b/static/js/confirmation.js
@@ -66,7 +66,7 @@ var Confirmation = new Class({
}
params += item.get("name") + "=true";
});
- new Request.JSON({
+ new Request.JSON.mdb({
method: "get",
url: Confirmation.url,
onSuccess: function(res) {
@@ -76,10 +76,7 @@ var Confirmation = new Class({
infobox.error(res.respmsg);
infobox.hide(2);
}
- },
- onFailure: function(xhr) {
- $(parent.document.body).set("html", xhr.responseText);
- }
+ }
}).send(params);
},
diff --git a/static/js/form.js b/static/js/form.js
index 7f7e62dac..f37a80356 100644
--- a/static/js/form.js
+++ b/static/js/form.js
@@ -1,3 +1,133 @@
+var Form = new Class({
+ Implements: [Options],
+
+ options: {
+ resptarget: null
+ },
+
+ callbacks: {
+ error: {},
+ success: {},
+ extra_data: {}
+ },
+
+ initialize: function(options) {
+ this.setOptions(options);
+
+ $(document.body)
+ .removeEvents("submit")
+ .addEvent("submit", this.submit.bind(this));
+
+ this.init_help_messages();
+ },
+
+ init_help_messages: function() {
+ $$("input[type!=submit]", "select").addEvents({
+ focus: function(evt) {
+ var helpdiv = $(evt.target.name + "_helptext");
+ if (helpdiv) {
+ var coords = evt.target.getCoordinates();
+ helpdiv.setPosition({x: 10, y: coords['top'] + coords['height'] + 5});
+ helpdiv.setStyle("display", "block");
+ }
+ },
+ blur: function(evt) {
+ var helpdiv = $(evt.target.name + "_helptext");
+ if (helpdiv) {
+ helpdiv.setStyle("display", "none");
+ }
+ }
+ });
+ },
+
+ register_callback: function(category, name, callback) {
+ if (this.callbacks[category][name] == undefined) {
+ return false;
+ }
+ this.callbacks[category][name] = callback;
+ return true;
+ },
+
+ register_error_callback: function(name, callback) {
+ return this.register_callback("error", name, callback);
+ },
+
+ register_success_callback: function(name, callback) {
+ return this.register_callback("success", name, callback);
+ },
+
+ register_extra_data_callback: function(name, callback) {
+ return this.register_callback("extra_data", name, callback);
+ },
+
+ submit: function(event) {
+ var form = event.target;
+ var myid = form.get("id");
+ var data = form.toQueryString();
+
+ console.log("submit");
+
+ event.stop();
+ if (this.callbacks["extra_data"][myid] != undefined) {
+ if (data != "") {
+ data += "&";
+ }
+ data += this.callbacks["extra_data"][myid]();
+ }
+
+ new Request.JSON({
+ url: form.get("action"),
+ method: form.get("method"),
+ data: data,
+ onSuccess: function(response) {
+ if (response.status == "ko") {
+ if (response.norefresh != undefined && response.norefresh == true) {
+ $$("div[class=error]").set("html", response.respmsg);
+ } else {
+ var target = (this.options.target)
+ ? $(this.options.target) : $(document.body);
+
+ target.set("html", response.content);
+ this.init_help_messages();
+ /*$(document.body).addEvent("submit", ajax_submit);*/
+ if (this.callbacks["error"][myid] != undefined) {
+ this.callbacks["error"][myid]();
+ }
+ }
+ return;
+ }
+ if ($defined(response.respmsg)) {
+ if ($defined(parent.current_anchor)) {
+ parent.current_anchor.loading_message = response.respmsg;
+ parent.current_anchor.loading_color = "green";
+ } else {
+ parent.infobox.info(response.respmsg);
+ parent.infobox.hide(1);
+ }
+ }
+ if ($defined(response.ajaxnav)) {
+ if ($defined(response.url)) {
+ parent.current_anchor.baseurl(response.url, 1);
+ }
+ parent.current_anchor.update(1);
+ } else if ($defined(response.url)) {
+ parent.location.href = response.url;
+ }
+ if (this.callbacks["success"][myid] != undefined) {
+ this.callbacks["success"][myid](response);
+ }
+ parent.SqueezeBox.close();
+ }.bind(this),
+ onFailure: function(xhr) {
+ parent.document.getElement('iframe')
+ .setStyles({width: 800, height: 600});
+ $(document.body).set("html", xhr.responseText);
+ }
+ }).send();
+ }
+});
+
+
var error_callbacks = {
permform: function() {
if ($defined($("id_domain"))) {
@@ -36,70 +166,74 @@ function register_extra_data_callback(name, callback) {
}
function ajax_submit(event) {
- var form = event.target;
- var myid = form.get("id");
- var data = form.toQueryString();
+ var form = event.target;
+ var myid = form.get("id");
+ var data = form.toQueryString();
- event.stop();
- if ($defined(extra_data_callbacks[myid])) {
- if (data != "") {
- data += "&";
+ event.stop();
+ if ($defined(extra_data_callbacks[myid])) {
+ if (data != "") {
+ data += "&";
+ }
+ data += extra_data_callbacks[myid]();
}
- data += extra_data_callbacks[myid]();
- }
- new Request({
- method: form.get("method"),
- data: data,
- onSuccess: function(responseText){
- var response = JSON.decode(responseText);
+ new Request.JSON({
+ url: form.get("action"),
+ method: form.get("method"),
+ data: data,
+ onSuccess: function(response) {
+ if (response.status == "ko") {
+ if (response.norefresh != undefined && response.norefresh == true) {
+ $$("div[class=error]").set("html", response.respmsg);
+ } else {
+ var target = (resptarget != undefined)
+ ? $(resptarget) : $(document.body);
- if (response.status == "ko") {
- if ($defined(response.norefresh) && response.norefresh == true) {
- $("error").set("html", response.respmsg);
- } else {
- $(document.body).set("html", response.content);
- $(document.body).addEvent("submit", ajax_submit);
- if ($defined(error_callbacks[myid])) {
- error_callbacks[myid]();
- }
+ target.set("html", response.content);
+ /*$(document.body).addEvent("submit", ajax_submit);*/
+ if ($defined(error_callbacks[myid])) {
+ error_callbacks[myid]();
+ }
+ }
+ return;
+ }
+ if ($defined(response.respmsg)) {
+ if ($defined(parent.current_anchor)) {
+ parent.current_anchor.loading_message = response.respmsg;
+ parent.current_anchor.loading_color = "green";
+ } else {
+ parent.infobox.info(response.respmsg);
+ parent.infobox.hide(1);
+ }
+ }
+ if ($defined(response.ajaxnav)) {
+ if ($defined(response.url)) {
+ parent.current_anchor.baseurl(response.url, 1);
+ }
+ parent.current_anchor.update(1);
+ } else if ($defined(response.url)) {
+ parent.location.href = response.url;
+ }
+ if ($defined(success_callbacks[myid])) {
+ success_callbacks[myid](response);
+ }
+ parent.SqueezeBox.close();
+ },
+ onFailure: function(xhr) {
+ parent.document.getElement('iframe').setStyles({width: 800, height: 600});
+ $(document.body).set("html", xhr.responseText);
}
- } else {
- if ($defined(response.respmsg)) {
- if ($defined(parent.current_anchor)) {
- parent.current_anchor.loading_message = response.respmsg;
- parent.current_anchor.loading_color = "green";
- } else {
- parent.infobox.info(response.respmsg);
- parent.infobox.hide(1);
- }
- }
- if ($defined(response.ajaxnav)) {
- if ($defined(response.url)) {
- parent.current_anchor.baseurl(response.url, 1);
- }
- parent.current_anchor.update(1);
- } else if ($defined(response.url)) {
- parent.location.href = response.url;
- }
- if ($defined(success_callbacks[myid])) {
- success_callbacks[myid](response);
- }
- parent.SqueezeBox.close();
- }
- },
- onFailure: function(xhr) {
- parent.document.getElement('iframe').setStyles({width: 800, height: 600});
- $(document.body).set("html", xhr.responseText);
- }
- }).send();
+ }).send();
}
-window.addEvent("domready", function() {
- $(document.body).addEvent("submit", ajax_submit);
+function initform() {
+ $(document.body)
+ .removeEvents("submit")
+ .addEvent("submit", ajax_submit);
$$("input[type!=submit]", "select").addEvents({
- focus: function(evt) {
+ focus: function(evt) {
var helpdiv = $(evt.target.name + "_helptext");
if (helpdiv) {
var coords = evt.target.getCoordinates();
@@ -114,4 +248,8 @@ window.addEvent("domready", function() {
}
}
});
-});
+}
+
+/*window.addEvent("domready", function() {
+ initform();
+});*/
diff --git a/static/js/permissions.js b/static/js/permissions.js
index 491845f8f..6b7c45080 100644
--- a/static/js/permissions.js
+++ b/static/js/permissions.js
@@ -16,6 +16,10 @@ window.addEvent('domready', function() {
}
}, $('accordion'));
+ SqueezeBox.assign($("content").getElements('a[class=boxed]'), {
+ parse: 'rel'
+ });
+
$$("a[name=addperm]").addEvent("click", function(evt) {
evt.stop();
var sizes = current_sizes.split(" ");
diff --git a/static/js/utils.js b/static/js/utils.js
index 48b11aaaf..e1ca1c490 100644
--- a/static/js/utils.js
+++ b/static/js/utils.js
@@ -25,6 +25,7 @@ Request.JSON.mdb = new Class({
Extends: Request.JSON,
options: {
+ format: "json",
onComplete: ajax_login_redirect,
onFailure: function(xhr) {
$(document.body).setStyle("overflow", "auto");
diff --git a/templates/admin/add_domain_admin.html b/templates/admin/add_domain_admin.html
index 3d1bad5da..df5304d0c 100644
--- a/templates/admin/add_domain_admin.html
+++ b/templates/admin/add_domain_admin.html
@@ -1,28 +1,57 @@
-{% extends "admin/add_permission.html" %}
+{% extends "layout_simple.html" %}
+{% load i18n %}
{% block html_head %}
-{{ block.super }}
+
+
+
+
+
{% endblock %}
+{% block content %}
+{{ title }}
+{% if error %}{{ error }}
{% endif %}
+
+
+
+
+{% endblock %}
diff --git a/templates/admin/add_permission.html b/templates/admin/add_permission.html
index 189f5d80b..8c14c9b8c 100644
--- a/templates/admin/add_permission.html
+++ b/templates/admin/add_permission.html
@@ -2,18 +2,24 @@
{% load i18n %}
{% block html_head %}
+
{% endblock %}
{% block content %}
{{ title }}
{% if error %}{{ error }}
{% endif %}
+{% if not addurl %}
{% url admin.views.add_permission as addurl %}
+{% endif %}
{% endblock %}
diff --git a/templates/admin/adminform.html b/templates/admin/adminform.html
index ac06a6c27..102f345bb 100644
--- a/templates/admin/adminform.html
+++ b/templates/admin/adminform.html
@@ -1,10 +1,15 @@
{% extends "layout_simple.html" %}
{% block html_head %}
+
{% endblock %}
{% block content %}
{{ title }}
-{% if error %}{{ error }}
{% endif %}
+{% if error %}{{ error }}{% endif %}
diff --git a/templates/admin/domain_admin_promotion.html b/templates/admin/domain_admin_promotion.html
new file mode 100644
index 000000000..5be510a1c
--- /dev/null
+++ b/templates/admin/domain_admin_promotion.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+
diff --git a/templates/admin/listing.html b/templates/admin/listing.html
index 79dd8bf9d..114f0ba19 100644
--- a/templates/admin/listing.html
+++ b/templates/admin/listing.html
@@ -1,5 +1,5 @@
{% extends "admin/domains_header.html" %}
-{% load i18n %}
+{% load i18n admin_extras %}
{% block middletitle %}{{ title }}{% endblock %}
{% block subcontent %}
@@ -7,6 +7,7 @@
{% endblock %}
{% block footer %}
+{% get_footer_content user selection %}
{% if total %}
diff --git a/userprefs/templatetags/uprefs_extras.py b/userprefs/templatetags/uprefs_extras.py
index b69fe1eec..e341ef4dd 100644
--- a/userprefs/templatetags/uprefs_extras.py
+++ b/userprefs/templatetags/uprefs_extras.py
@@ -49,8 +49,7 @@ def options_menu(user):
@register.simple_tag
def uprefs_menu(user):
entries = []
- entries += events.raiseQueryEvent("UserMenuDisplay",
- target="uprefs_menu")
+ entries += events.raiseQueryEvent("UserMenuDisplay", "uprefs_menu")
return render_to_string('common/menulist.html',
{"entries" : entries, "user" : user})
diff --git a/userprefs/views.py b/userprefs/views.py
index 7d620d44a..a9b2a5917 100644
--- a/userprefs/views.py
+++ b/userprefs/views.py
@@ -8,8 +8,8 @@ from django.utils.translation import ugettext as _
from modoboa.lib import parameters, events
from modoboa.lib.webutils import _render, _render_error, ajax_response
from forms import *
-from modoboa.admin.models import Mailbox, Alias, AdminError
-from modoboa.admin.lib import is_domain_admin
+from modoboa.admin.models import Mailbox, Alias
+from modoboa.admin.lib import is_domain_admin, AdminError
from modoboa.auth.lib import encrypt
@login_required