diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py
index 824c49add..8e81e7145 100644
--- a/modoboa/core/__init__.py
+++ b/modoboa/core/__init__.py
@@ -15,6 +15,7 @@ base_events = [
"RoleChanged",
"GetExtraRoles",
"PasswordChange",
+ "UserCanSetRole",
"UserMenuDisplay",
"AdminMenuDisplay",
@@ -32,7 +33,9 @@ base_events = [
"ExtraAdminContent",
"ExtraUprefsRoutes",
- "ExtraUprefsJS"
+ "ExtraUprefsJS",
+
+ "GetExtraParameters"
]
diff --git a/modoboa/core/exceptions.py b/modoboa/core/exceptions.py
deleted file mode 100644
index 063db7ad9..000000000
--- a/modoboa/core/exceptions.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-:mod:`exceptions` --- Custom exceptions of the admin. application
------------------------------------------------------------------
-"""
-
-from modoboa.lib.exceptions import ModoboaException
-
-
-class AdminError(ModoboaException):
- """
- Just a Custom exception to identify errors coming from the admin
- application.
- """
- pass
diff --git a/modoboa/core/extensions.py b/modoboa/core/extensions.py
index 4b399bd24..ecd65de4a 100644
--- a/modoboa/core/extensions.py
+++ b/modoboa/core/extensions.py
@@ -49,6 +49,13 @@ class ExtensionsPool(object):
self.extensions[name]["instance"] = self.extensions[name]["cls"]()
return self.extensions[name]["instance"]
+ def is_extension_enabled(self, name):
+ from modoboa.core.models import Extension
+
+ if not name in self.extensions:
+ return False
+ return Extension.objects.get(name=name).enabled
+
def get_extension_infos(self, name):
instance = self.get_extension(name)
if instance is None:
diff --git a/modoboa/core/models.py b/modoboa/core/models.py
index 4af548ad3..db0050044 100644
--- a/modoboa/core/models.py
+++ b/modoboa/core/models.py
@@ -17,10 +17,11 @@ from django.contrib.auth.models import (
UserManager, Group, AbstractBaseUser, PermissionsMixin
)
from modoboa.lib import events, md5crypt, parameters
-from modoboa.lib.exceptions import PermDeniedException
+from modoboa.lib.exceptions import (
+ PermDeniedException, InternalError, BadRequest
+)
from modoboa.lib.sysutils import exec_cmd
from modoboa.core.extensions import exts_pool
-from modoboa.core.exceptions import AdminError
try:
from modoboa.lib.ldaputils import *
@@ -72,7 +73,9 @@ class User(AbstractBaseUser, PermissionsMixin):
get_object_owner, grant_access_to_object, ungrant_access_to_object
if fromuser == self:
- raise AdminError(_("You can't delete your own account"))
+ raise PermDeniedException(
+ _("You can't delete your own account")
+ )
if not fromuser.can_access(self):
raise PermDeniedException
@@ -133,15 +136,15 @@ class User(AbstractBaseUser, PermissionsMixin):
self.password = self._crypt_password(raw_value)
else:
if not ldap_available:
- raise AdminError(
+ raise InternalError(
_("Failed to update password: LDAP module not installed")
)
ab = LDAPAuthBackend()
try:
ab.update_user_password(self.username, curvalue, raw_value)
- except LDAPException, e:
- raise AdminError(_("Failed to update password: %s" % str(e)))
+ except LDAPException as e:
+ raise InternalError(_("Failed to update password: %s" % str(e)))
events.raiseEvent(
"PasswordUpdated", self, raw_value, self.pk is None
)
@@ -197,7 +200,7 @@ class User(AbstractBaseUser, PermissionsMixin):
try:
return self.groups.all()[0].name
except IndexError:
- return "SimpleUsers"
+ return "---"
@property
def enabled(self):
@@ -318,7 +321,7 @@ class User(AbstractBaseUser, PermissionsMixin):
:param crypt_password:
"""
if len(row) < 7:
- raise AdminError(_("Invalid line"))
+ raise BadRequest(_("Invalid line"))
role = row[6].strip()
if not user.is_superuser and not role in ["SimpleUsers", "DomainAdmins"]:
raise PermDeniedException(
@@ -327,11 +330,11 @@ class User(AbstractBaseUser, PermissionsMixin):
self.username = row[1].strip()
if role == "SimpleUsers":
if (len(row) < 8 or not row[7].strip()):
- raise AdminError(
+ raise BadRequest(
_("The simple user '%s' must have a valid email address" % self.username)
)
if self.username != row[7].strip():
- raise AdminError(
+ raise BadRequest(
_("username and email fields must not differ for '%s'" % self.username)
)
diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py
index fdd377cb2..678c41caf 100644
--- a/modoboa/core/views/admin.py
+++ b/modoboa/core/views/admin.py
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import (
)
from modoboa.lib import parameters
from modoboa.lib.webutils import (
- ajax_simple_response, _render_to_string
+ _render_to_string, render_to_json_response
)
from modoboa.lib.listing import get_sort_order, get_listing_page
from modoboa.core.models import Extension, Log
@@ -24,7 +24,7 @@ def viewsettings(request, tplname='core/settings_header.html'):
@login_required
@user_passes_test(lambda u: u.is_superuser)
def viewparameters(request, tplname='core/parameters.html'):
- return ajax_simple_response({
+ return render_to_json_response({
"status": "ok",
"left_selection": "parameters",
"content": _render_to_string(request, tplname, {
@@ -42,12 +42,10 @@ def saveparameters(request):
form.save()
form.to_django_settings()
continue
- return ajax_simple_response({
- "status": "ko", "prefix": form.app, "errors": form.errors
- })
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Parameters saved")
- ))
+ return render_to_json_response(
+ {'form_errors': form.errors, 'prefix': form.app}, status=400
+ )
+ return render_to_json_response(_("Parameters saved"))
@login_required
@@ -68,8 +66,7 @@ def viewextensions(request, tplname='core/extensions.html'):
ext["selection"] = False
tbl = ExtensionsTable(request, exts)
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": _render_to_string(request, tplname, {"extensions": tbl})
})
@@ -91,16 +88,13 @@ def saveextensions(request):
if not ext in found:
ext.off()
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Modifications applied.")
- ))
+ return render_to_json_response(_("Modifications applied."))
@login_required
@user_passes_test(lambda u: u.is_superuser)
def information(request, tplname="core/information.html"):
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": render_to_string(tplname, {})
})
@@ -118,8 +112,7 @@ def logs(request, tplname="core/logs.html"):
Log.objects.all().order_by("%s%s" % (sort_dir, sort_order)),
request.GET.get("page", 1)
)
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": render_to_string(tplname, {
"logs": page.object_list,
}),
diff --git a/modoboa/core/views/user.py b/modoboa/core/views/user.py
index c42054526..d3cb2e8ac 100644
--- a/modoboa/core/views/user.py
+++ b/modoboa/core/views/user.py
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import login_required
from modoboa.lib import events, parameters
from modoboa.lib.webutils import (
- ajax_simple_response, _render_to_string
+ _render_to_string, render_to_json_response
)
from modoboa.lib.cryptutils import encrypt
from modoboa.core.forms import ProfileForm
@@ -32,17 +32,11 @@ def profile(request, tplname='core/user_profile.html'):
form.save()
if update_password and form.cleaned_data["confirmation"] != "":
request.session["password"] = encrypt(form.cleaned_data["confirmation"])
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Profile updated")
- ))
- return ajax_simple_response({
- "status": "ko",
- "errors": form.errors
- })
+ return render_to_json_response(_("Profile updated"))
+ return render_to_json_response({'form_errors': form.errors}, status=400)
form = ProfileForm(update_password, instance=request.user)
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": _render_to_string(request, tplname, {
"form": form
})
@@ -57,16 +51,13 @@ def preferences(request):
if form.is_valid():
form.save()
continue
- return ajax_simple_response({
- "status": "ko", "prefix": form.app, "errors": form.errors
- })
+ return render_to_json_response({
+ "prefix": form.app, "form_errors": form.errors
+ }, status=400)
- return ajax_simple_response({
- "status": "ok", "respmsg": _("Preferences saved")
- })
+ return render_to_json_response(_("Preferences saved"))
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": _render_to_string(request, "core/user_preferences.html", {
"forms": parameters.get_user_forms(request.user)
})
diff --git a/modoboa/extensions/admin/__init__.py b/modoboa/extensions/admin/__init__.py
index 22e84f0c7..7cfd3d63a 100644
--- a/modoboa/extensions/admin/__init__.py
+++ b/modoboa/extensions/admin/__init__.py
@@ -3,14 +3,12 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.lib import parameters, events
-from modoboa.lib.exceptions import PermDeniedException
+from modoboa.lib.exceptions import PermDeniedException, BadRequest
from modoboa.lib.emailutils import split_mailbox
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias
)
-
admin_events = [
"DomainCreated",
"DomainModified",
@@ -146,7 +144,7 @@ def import_account_mailbox(user, account, row):
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
- raise AdminError(
+ raise BadRequest(
_("Account import failed (%s): domain does not exist" % account.username)
)
if not user.can_access(domain):
diff --git a/modoboa/extensions/admin/exceptions.py b/modoboa/extensions/admin/exceptions.py
deleted file mode 100644
index d0414b27b..000000000
--- a/modoboa/extensions/admin/exceptions.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# coding: utf-8
-"""
-:mod:`exceptions` --- Custom exceptions of the admin. application
------------------------------------------------------------------
-"""
-
-from modoboa.lib.exceptions import ModoboaException
-
-
-class AdminError(ModoboaException):
- """
- Just a Custom exception to identify errors coming from the admin
- application.
- """
- pass
-
-
-class BadDestination(Exception):
- pass
diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py
index bcbc54c86..c8afb5fae 100644
--- a/modoboa/extensions/admin/forms/account.py
+++ b/modoboa/extensions/admin/forms/account.py
@@ -2,14 +2,13 @@ from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.http import QueryDict
from modoboa.lib import events, parameters
-from modoboa.lib.exceptions import PermDeniedException
+from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DomainNameField, DynamicForm, TabForms
)
from modoboa.core.models import User
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
@@ -22,7 +21,9 @@ class AccountFormGeneral(forms.ModelForm):
choices=[('', ugettext_lazy("Choose"))],
help_text=ugettext_lazy("What level of permission this user will have")
)
- password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
+ password1 = forms.CharField(
+ label=_("Password"), widget=forms.PasswordInput
+ )
password2 = forms.CharField(
label=ugettext_lazy("Confirmation"),
widget=forms.PasswordInput,
@@ -90,7 +91,7 @@ class AccountFormGeneral(forms.ModelForm):
def save(self, commit=True):
account = super(AccountFormGeneral, self).save(commit=False)
if self.user == account and not self.cleaned_data["is_active"]:
- raise AdminError(_("You can't disable your own account"))
+ raise PermDeniedException(_("You can't disable your own account"))
if commit:
if "password1" in self.cleaned_data \
and self.cleaned_data["password1"] != "":
@@ -105,14 +106,21 @@ class AccountFormMail(forms.Form, DynamicForm):
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
required=False,
- help_text=_("Quota in MB for this mailbox. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),
+ help_text=_("Quota in MB for this mailbox. Define a custom value or "
+ "use domain's default one. Leave empty to define an "
+ "unlimited value (not allowed for domain "
+ "administrators)."),
widget=forms.widgets.TextInput(attrs={"class": "span1"})
)
quota_act = forms.BooleanField(required=False)
aliases = forms.EmailField(
label=ugettext_lazy("Alias(es)"),
required=False,
- help_text=ugettext_lazy("Alias(es) of this mailbox. Indicate only one address per input, press ENTER to add a new input. Use the '*' character to create a 'catchall' alias (ex: *@domain.tld).")
+ help_text=ugettext_lazy(
+ "Alias(es) of this mailbox. Indicate only one address per input, "
+ "press ENTER to add a new input. Use the '*' character to create "
+ "a 'catchall' alias (ex: *@domain.tld)."
+ )
)
def __init__(self, *args, **kwargs):
@@ -143,6 +151,52 @@ class AccountFormMail(forms.Form, DynamicForm):
"""Ensure lower case emails"""
return self.cleaned_data["email"].lower()
+ def create_mailbox(self, user, account):
+ locpart, domname = split_mailbox(self.cleaned_data["email"])
+ try:
+ domain = Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ raise NotFound(_("Domain does not exist"))
+ if not user.can_access(domain):
+ raise PermDeniedException
+ try:
+ Mailbox.objects.get(address=locpart, domain=domain)
+ except Mailbox.DoesNotExist:
+ pass
+ else:
+ raise Conflict(
+ _("Mailbox %s already exists" % self.cleaned_data["email"])
+ )
+ events.raiseEvent("CanCreate", user, "mailboxes")
+ self.mb = Mailbox(address=locpart, domain=domain, user=account,
+ use_domain_quota=self.cleaned_data["quota_act"])
+ self.mb.set_quota(self.cleaned_data["quota"],
+ user.has_perm("admin.add_domain"))
+ self.mb.save(creator=user)
+
+ def update_mailbox(self, user, account):
+ newaddress = None
+ if self.cleaned_data["email"] != self.mb.full_address:
+ newaddress = self.cleaned_data["email"]
+ elif account.group == "SimpleUsers" and account.username != self.mb.full_address:
+ newaddress = account.username
+ if newaddress is not None:
+ local_part, domname = split_mailbox(newaddress)
+ try:
+ domain = Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ raise NotFound(_("Domain does not exist"))
+ if not user.can_access(domain):
+ raise PermDeniedException
+ self.mb.rename(local_part, domain)
+
+ self.mb.use_domain_quota = self.cleaned_data["quota_act"]
+ override_rules = True \
+ if not self.mb.quota or user.has_perm("admin.add_domain") \
+ else False
+ self.mb.set_quota(self.cleaned_data["quota"], override_rules)
+ self.mb.save()
+
def save(self, user, account):
if self.cleaned_data["email"] == "":
return None
@@ -151,74 +205,43 @@ class AccountFormMail(forms.Form, DynamicForm):
self.cleaned_data["quota"] = None
if not hasattr(self, "mb") or self.mb is None:
- locpart, domname = split_mailbox(self.cleaned_data["email"])
- try:
- domain = Domain.objects.get(name=domname)
- except Domain.DoesNotExist:
- raise AdminError(_("Domain does not exist"))
- if not user.can_access(domain):
- raise PermDeniedException
- try:
- Mailbox.objects.get(address=locpart, domain=domain)
- except Mailbox.DoesNotExist:
- pass
- else:
- raise AdminError(_("Mailbox %s already exists" % self.cleaned_data["email"]))
- events.raiseEvent("CanCreate", user, "mailboxes")
- self.mb = Mailbox(address=locpart, domain=domain, user=account,
- use_domain_quota=self.cleaned_data["quota_act"])
- self.mb.set_quota(self.cleaned_data["quota"],
- user.has_perm("admin.add_domain"))
- self.mb.save(creator=user)
+ self.create_mailbox(user, account)
else:
- newaddress = None
- if self.cleaned_data["email"] != self.mb.full_address:
- newaddress = self.cleaned_data["email"]
- elif account.group == "SimpleUsers" and account.username != self.mb.full_address:
- newaddress = account.username
- if newaddress is not None:
- local_part, domname = split_mailbox(newaddress)
- try:
- domain = Domain.objects.get(name=domname)
- except Domain.DoesNotExist:
- raise AdminError(_("Domain does not exist"))
- if not user.can_access(domain):
- raise PermDeniedException
- self.mb.rename(local_part, domain)
-
- self.mb.use_domain_quota = self.cleaned_data["quota_act"]
- override_rules = True \
- if not self.mb.quota or user.has_perm("admin.add_domain") \
- else False
- self.mb.set_quota(self.cleaned_data["quota"], override_rules)
- self.mb.save()
+ self.update_mailbox(user, account)
account.email = self.cleaned_data["email"]
account.save()
+ aliases = []
for name, value in self.cleaned_data.iteritems():
if not name.startswith("aliases"):
continue
if value == "":
continue
- local_part, domname = split_mailbox(value)
- try:
- self.mb.alias_set.get(address=local_part, domain__name=domname)
- except Alias.DoesNotExist:
- pass
- else:
- continue
- events.raiseEvent("CanCreate", user, "mailbox_aliases")
- al = Alias(address=local_part, enabled=account.is_active)
- al.domain = Domain.objects.get(name=domname)
- al.save(int_rcpts=[self.mb], creator=user)
+ aliases.append(value)
for alias in self.mb.alias_set.all():
- if len(alias.get_recipients()) >= 2:
- continue
- if not len(filter(lambda name: self.cleaned_data[name] == alias.full_address,
- self.cleaned_data.keys())):
+ if not alias.full_address in aliases:
+ if len(alias.get_recipients()) >= 2:
+ continue
alias.delete()
+ else:
+ aliases.remove(alias.full_address)
+ if aliases:
+ events.raiseEvent(
+ "CanCreate", user, "mailbox_aliases", len(aliases)
+ )
+ for alias in aliases:
+ local_part, domname = split_mailbox(alias)
+ try:
+ self.mb.alias_set.get(address=local_part, domain__name=domname)
+ except Alias.DoesNotExist:
+ pass
+ else:
+ continue
+ al = Alias(address=local_part, enabled=account.is_active)
+ al.domain = Domain.objects.get(name=domname)
+ al.save(int_rcpts=[self.mb], creator=user)
return self.mb
diff --git a/modoboa/extensions/admin/forms/alias.py b/modoboa/extensions/admin/forms/alias.py
index 89bfe4e68..555b98802 100644
--- a/modoboa/extensions/admin/forms/alias.py
+++ b/modoboa/extensions/admin/forms/alias.py
@@ -1,11 +1,11 @@
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.http import QueryDict
+from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DynamicForm
)
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
@@ -14,11 +14,17 @@ from modoboa.extensions.admin.models import (
class AliasForm(forms.ModelForm, DynamicForm):
email = forms.EmailField(
label=ugettext_lazy("Email address"),
- help_text=ugettext_lazy("The distribution list address. Use the '*' character to create a 'catchall' address (ex: *@domain.tld).")
+ help_text=ugettext_lazy(
+ "The distribution list address. Use the '*' character to create a "
+ "'catchall' address (ex: *@domain.tld)."
+ )
)
recipients = forms.EmailField(
label=ugettext_lazy("Recipients"), required=False,
- help_text=ugettext_lazy("Mailbox(es) this alias will point to. Indicate only one address per input, press ENTER to add a new input.")
+ help_text=ugettext_lazy(
+ "Mailbox(es) this alias will point to. Indicate only one address "
+ "per input, press ENTER to add a new input."
+ )
)
class Meta:
@@ -58,7 +64,9 @@ class AliasForm(forms.ModelForm, DynamicForm):
except Domain.DoesNotExist:
raise forms.ValidationError(_("Domain does not exist"))
if not self.user.can_access(domain):
- raise forms.ValidationError(_("You don't have access to this domain"))
+ raise forms.ValidationError(
+ _("You don't have access to this domain")
+ )
return self.cleaned_data["email"]
def set_recipients(self):
@@ -78,7 +86,9 @@ class AliasForm(forms.ModelForm, DynamicForm):
continue
local_part, domname = split_mailbox(v)
if domname is None:
- raise AdminError("%s %s" % (_("Invalid mailbox"), v))
+ raise BadRequest(
+ u"%s %s" % (_("Invalid mailbox"), v)
+ )
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
@@ -94,20 +104,26 @@ class AliasForm(forms.ModelForm, DynamicForm):
try:
rcpt = Mailbox.objects.get(domain=domain, address=local_part)
except Mailbox.DoesNotExist:
- raise AdminError(_("Local recipient %s not found" % v))
+ raise NotFound(
+ _("Local recipient %s not found" % v)
+ )
if rcpt in self.int_rcpts:
- raise AdminError(_("Recipient %s already present" % v))
+ raise Conflict(
+ _("Recipient %s already present" % v)
+ )
self.int_rcpts += [rcpt]
total += 1
continue
if v in self.ext_rcpts:
- raise AdminError(_("Recipient %s already present" % v))
+ raise Conflict(
+ _("Recipient %s already present" % v)
+ )
self.ext_rcpts += [v]
total += 1
if total == 0:
- raise AdminError(_("No recipient defined"))
+ raise BadRequest(_("No recipient defined"))
def save(self, commit=True):
alias = super(AliasForm, self).save(commit=False)
diff --git a/modoboa/extensions/admin/forms/domain.py b/modoboa/extensions/admin/forms/domain.py
index 59682ae6c..c3686fdc9 100644
--- a/modoboa/extensions/admin/forms/domain.py
+++ b/modoboa/extensions/admin/forms/domain.py
@@ -2,11 +2,11 @@ from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events
+from modoboa.lib.exceptions import Conflict
from modoboa.lib.formutils import (
DomainNameField, YesNoField, DynamicForm, TabForms
)
from modoboa.core.models import User
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias, Quota
)
@@ -126,25 +126,29 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
d.save()
Mailbox.objects.filter(domain=d, use_domain_quota=True) \
.update(quota=d.quota)
+ aliases = []
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
if v in ["", None]:
continue
- try:
- d.domainalias_set.get(name=v)
- except DomainAlias.DoesNotExist:
- pass
- else:
- continue
- events.raiseEvent("CanCreate", user, "domain_aliases")
- al = DomainAlias(name=v, target=d, enabled=d.enabled)
- al.save(creator=user)
-
+ aliases.append(v)
for dalias in d.domainalias_set.all():
- if not len(filter(lambda name: self.cleaned_data[name] == dalias.name,
- self.cleaned_data.keys())):
+ if not dalias.name in aliases:
dalias.delete()
+ else:
+ aliases.remove(dalias.name)
+ if aliases:
+ events.raiseEvent("CanCreate", user, "domain_aliases", len(aliases))
+ for alias in aliases:
+ try:
+ d.domainalias_set.get(name=alias)
+ except DomainAlias.DoesNotExist:
+ pass
+ else:
+ continue
+ al = DomainAlias(name=alias, target=d, enabled=d.enabled)
+ al.save(creator=user)
if old_mail_homes is not None:
self.update_mailbox_quotas(d)
@@ -179,8 +183,11 @@ class DomainFormOptions(forms.Form):
required=False
)
- def __init__(self, *args, **kwargs):
+ def __init__(self, user, *args, **kwargs):
super(DomainFormOptions, self).__init__(*args, **kwargs)
+ if False in events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins'):
+ self.fields = {}
+ return
if args:
if args[0].get("create_dom_admin", "no") == "yes":
self.fields["dom_admin_username"].required = True
@@ -192,6 +199,8 @@ class DomainFormOptions(forms.Form):
return self.cleaned_data["dom_admin_username"]
def save(self, user, domain):
+ if not self.fields:
+ return
if self.cleaned_data["create_dom_admin"] == "no":
return
username = "%s@%s" % (self.cleaned_data["dom_admin_username"], domain.name)
@@ -200,7 +209,8 @@ class DomainFormOptions(forms.Form):
except User.DoesNotExist:
pass
else:
- raise AdminError(_("User '%s' already exists" % username))
+ raise Conflict(_("User '%s' already exists" % username))
+ events.raiseEvent("CanCreate", user, "mailboxes")
da = User(username=username, email=username, is_active=True)
da.set_password("password")
da.save()
@@ -212,6 +222,7 @@ class DomainFormOptions(forms.Form):
mb.save(creator=user)
if self.cleaned_data["create_aliases"] == "yes":
+ events.raiseEvent("CanCreate", user, "mailbox_aliases")
al = Alias(address="postmaster", domain=domain, enabled=True)
al.save(int_rcpts=[mb], creator=user)
diff --git a/modoboa/extensions/admin/forms/forward.py b/modoboa/extensions/admin/forms/forward.py
index fa1e7a53a..9cf7eb643 100644
--- a/modoboa/extensions/admin/forms/forward.py
+++ b/modoboa/extensions/admin/forms/forward.py
@@ -1,7 +1,7 @@
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
+from modoboa.lib.exceptions import BadRequest, PermDeniedException
from modoboa.lib.emailutils import split_mailbox
-from modoboa.extensions.admin.exceptions import BadDestination
from modoboa.extensions.admin.models import (
Domain
)
@@ -28,10 +28,14 @@ class ForwardForm(forms.Form):
for d in rawdata.split(","):
local_part, domname = split_mailbox(d)
if not local_part or not domname or not len(domname):
- raise BadDestination("Invalid mailbox syntax for %s" % d)
+ raise BadRequest("Invalid mailbox syntax for %s" % d)
try:
Domain.objects.get(name=domname)
except Domain.DoesNotExist:
self.dests += [d]
else:
- raise BadDestination(_("You can't define a forward to a local destination. Please ask your administrator to create an alias instead."))
+ raise PermDeniedException(
+ _("You can't define a forward to a local destination. "
+ "Please ask your administrator to create an alias "
+ "instead.")
+ )
diff --git a/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py b/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py
index 56a5d24a1..056b4a55e 100644
--- a/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py
+++ b/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py
@@ -4,8 +4,8 @@ from optparse import make_option
from django.core.management.base import BaseCommand
from modoboa.lib import parameters
from modoboa.lib.sysutils import exec_cmd
+from modoboa.lib.exceptions import InternalError
from modoboa.extensions.admin import AdminConsole
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import MailboxOperation
@@ -83,7 +83,7 @@ class Command(BaseCommand):
continue
try:
f(ope)
- except (OperationError, AdminError) as e:
+ except (OperationError, InternalError) as e:
self.logger.critical('%s failed (reason: %s)',
ope, str(e).encode('utf-8'))
else:
diff --git a/modoboa/extensions/admin/models/alias.py b/modoboa/extensions/admin/models/alias.py
index e0ec8ca57..e8720ef5e 100644
--- a/modoboa/extensions/admin/models/alias.py
+++ b/modoboa/extensions/admin/models/alias.py
@@ -1,19 +1,23 @@
import reversion
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
-from modoboa.lib import events
from modoboa.lib.emailutils import split_mailbox
-from modoboa.lib.exceptions import PermDeniedException
-from modoboa.extensions.admin.exceptions import AdminError
+from modoboa.lib.exceptions import PermDeniedException, BadRequest
from .base import AdminObject
from .domain import Domain
from .mailbox import Mailbox
class Alias(AdminObject):
+ """
+ Mailbox alias.
+ """
address = models.CharField(
ugettext_lazy('address'), max_length=254,
- help_text=ugettext_lazy("The alias address (without the domain part). For a 'catch-all' address, just enter an * character.")
+ help_text=ugettext_lazy(
+ "The alias address (without the domain part). For a 'catch-all' "
+ "address, just enter an * character."
+ )
)
domain = models.ForeignKey(Domain)
mboxes = models.ManyToManyField(
@@ -139,12 +143,12 @@ class Alias(AdminObject):
"""
if len(row) < expected_elements:
- raise AdminError(_("Invalid line: %s" % row))
+ raise BadRequest(_("Invalid line: %s" % row))
localpart, domname = split_mailbox(row[1].strip())
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
- raise AdminError(_("Domain '%s' does not exist" % domname))
+ raise BadRequest(_("Domain '%s' does not exist" % domname))
if not user.can_access(domain):
raise PermDeniedException
self.address = localpart
@@ -171,7 +175,7 @@ class Alias(AdminObject):
target = Mailbox.objects.get(address=localpart,
domain__name=domname)
except Mailbox.DoesNotExist:
- raise AdminError(_("Local recipient %s not found" % rcpt))
+ raise BadRequest(_("Local recipient %s not found" % rcpt))
int_rcpts += [target]
self.save(int_rcpts=int_rcpts, ext_rcpts=ext_rcpts, creator=user)
diff --git a/modoboa/extensions/admin/models/domain.py b/modoboa/extensions/admin/models/domain.py
index 3e4294070..7a0b909df 100644
--- a/modoboa/extensions/admin/models/domain.py
+++ b/modoboa/extensions/admin/models/domain.py
@@ -4,8 +4,8 @@ from django.db.models.manager import Manager
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.contenttypes import generic
from modoboa.lib import events, parameters
+from modoboa.lib.exceptions import BadRequest
from modoboa.core.models import User, ObjectAccess
-from modoboa.extensions.admin.exceptions import AdminError
from .base import AdminObject
@@ -123,12 +123,12 @@ class Domain(AdminObject):
def from_csv(self, user, row):
if len(row) < 4:
- raise AdminError(_("Invalid line"))
+ raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
try:
self.quota = int(row[2].strip())
except ValueError:
- raise AdminError(_("Invalid quota value for domain '%s'" % self.name))
+ raise BadRequest(_("Invalid quota value for domain '%s'" % self.name))
self.enabled = (row[3].strip() == 'True')
self.save(creator=user)
diff --git a/modoboa/extensions/admin/models/domain_alias.py b/modoboa/extensions/admin/models/domain_alias.py
index 3d087dc11..382ba74e3 100644
--- a/modoboa/extensions/admin/models/domain_alias.py
+++ b/modoboa/extensions/admin/models/domain_alias.py
@@ -1,8 +1,7 @@
import reversion
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
-from modoboa.lib import events
-from modoboa.extensions.admin.exceptions import AdminError
+from modoboa.lib.exceptions import BadRequest
from .base import AdminObject
from .domain import Domain
@@ -37,13 +36,13 @@ class DomainAlias(AdminObject):
:param row: a list containing the alias definition
"""
if len(row) < 4:
- raise AdminError(_("Invalid line"))
+ raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
domname = row[2].strip()
try:
self.target = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
- raise AdminError(_("Unknown domain %s" % domname))
+ raise BadRequest(_("Unknown domain %s" % domname))
self.enabled = row[3].strip() == 'True'
self.save(creator=user)
diff --git a/modoboa/extensions/admin/models/mailbox.py b/modoboa/extensions/admin/models/mailbox.py
index ab1da3584..a16e478a0 100644
--- a/modoboa/extensions/admin/models/mailbox.py
+++ b/modoboa/extensions/admin/models/mailbox.py
@@ -9,9 +9,9 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from modoboa.lib import parameters, events
+from modoboa.lib.exceptions import BadRequest, InternalError
from modoboa.lib.sysutils import exec_cmd
from modoboa.core.models import User
-from modoboa.extensions.admin.exceptions import AdminError
from .base import AdminObject
from .domain import Domain
@@ -110,7 +110,7 @@ class Mailbox(AdminObject):
"doveadm user %s -f home" % self.full_address, **options
)
if code:
- raise AdminError(_("Failed to retrieve mailbox location (%s)" % output))
+ raise InternalError(_("Failed to retrieve mailbox location (%s)" % output))
self.__mail_home = output.strip()
return self.__mail_home
@@ -158,13 +158,13 @@ class Mailbox(AdminObject):
else:
self.quota = 0
elif int(value) > self.domain.quota and not override_rules:
- raise AdminError(
+ raise BadRequest(
_("Quota is greater than the allowed domain's limit (%dM)" % self.domain.quota)
)
else:
self.quota = value
if not self.quota and self.domain.quota and not override_rules:
- raise AdminError(_("A quota is required"))
+ raise BadRequest(_("A quota is required"))
def get_quota(self):
"""Get quota limit.
diff --git a/modoboa/extensions/admin/static/admin/js/admin.js b/modoboa/extensions/admin/static/admin/js/admin.js
index 87f1a51c3..313bc332e 100644
--- a/modoboa/extensions/admin/static/admin/js/admin.js
+++ b/modoboa/extensions/admin/static/admin/js/admin.js
@@ -143,8 +143,8 @@ Admin.prototype = {
reload_listing: function(data) {
this.navobj.update(true);
- if (data.respmsg) {
- $("body").notify("success", data.respmsg, 2000);
+ if (data) {
+ $("body").notify("success", data, 2000);
}
}
};
@@ -180,11 +180,7 @@ Domains.prototype = {
checkboxes: deloptions,
success_cb: $.proxy(this.reload_listing, this)
});
- $("a[name=delrelaydomain]").confirm({
- question: function() { return this.$element.attr('title'); },
- method: "DELETE",
- success_cb: $.proxy(this.reload_listing, this)
- });
+ $(document).trigger('domain_listing_refresh');
},
change_inputs_state: function(value) {
@@ -239,7 +235,7 @@ Domains.prototype = {
1: this.optionsform_prefill
},
error_callbacks: {
- 1: this.generalform_init,
+ //1: this.generalform_init,
2: $.proxy(this.optionsform_init, this)
},
success_callback: $.proxy(this.reload_listing, this)
@@ -397,11 +393,7 @@ Identities.prototype = {
$("#wizard").cwizard({
formid: "newaccount_form",
transition_callbacks: {
- 1: this.mailform_prefill
- },
- error_callbacks: {
- 1: $.proxy(this.generalform_init, this),
- 2: $.proxy(this.mailform_init, this)
+ 2: this.mailform_prefill
},
success_callback: $.proxy(this.reload_listing, this)
});
@@ -409,10 +401,9 @@ Identities.prototype = {
editaccount_cb: function() {
this.accountform_init();
- $('.submit').one('click', $.proxy(function(e) {
+ $('.submit').on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "accountform",
- error_cb: $.proxy(this.editaccount_cb, this),
reload_on_success: false,
success_cb: $.proxy(this.reload_listing, this)
});
@@ -425,10 +416,9 @@ Identities.prototype = {
choices: get_domains_list
});
$("#id_recipients").dynamic_input();
- $(".submit").one('click', $.proxy(function(e) {
+ $(".submit").on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "aliasform",
- error_cb: $.proxy(this.aliasform_cb, this),
reload_on_success: false,
success_cb: $.proxy(this.reload_listing, this)
});
diff --git a/modoboa/extensions/admin/templates/admin/dlistform.html b/modoboa/extensions/admin/templates/admin/aliasform.html
similarity index 67%
rename from modoboa/extensions/admin/templates/admin/dlistform.html
rename to modoboa/extensions/admin/templates/admin/aliasform.html
index 20f3cd51d..6a13e26c9 100644
--- a/modoboa/extensions/admin/templates/admin/dlistform.html
+++ b/modoboa/extensions/admin/templates/admin/aliasform.html
@@ -1,5 +1,4 @@
-{% extends "common/generic_modal_form.html" %}
-{% load lib_tags %}
+{% extends "common/generic_modal_form.html" %}{% load lib_tags %}
{% block formbody %}
{% render_field form.email %}
{% render_fields_group form "recipients" %}
diff --git a/modoboa/extensions/admin/templates/admin/domain_options_form.html b/modoboa/extensions/admin/templates/admin/domain_options_form.html
index c8acba112..b1c050f85 100644
--- a/modoboa/extensions/admin/templates/admin/domain_options_form.html
+++ b/modoboa/extensions/admin/templates/admin/domain_options_form.html
@@ -1,4 +1,5 @@
-{% load i18n lib_tags %}
+{% load i18n lib_tags %}{% if form.fields %}
{% render_field form.create_dom_admin %}
{% render_field_appended form.dom_admin_username "@" %}
{% render_field form.create_aliases %}
+{% else %}
{% trans "You can't create a new domain administrator" %}
{% endif %}
diff --git a/modoboa/extensions/admin/tests/account.py b/modoboa/extensions/admin/tests/account.py
index fa8773bc1..6f316eb17 100644
--- a/modoboa/extensions/admin/tests/account.py
+++ b/modoboa/extensions/admin/tests/account.py
@@ -19,9 +19,9 @@ class AccountTestCase(ModoTestCase):
username="tester@test.com", first_name="Tester", last_name="Toto",
password1="toto", password2="toto", role="SimpleUsers",
quota_act=True,
- is_active=True, email="tester@test.com", stepid=2
+ is_active=True, email="tester@test.com", stepid='step2'
)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.newaccount"),
values
)
@@ -39,26 +39,26 @@ class AccountTestCase(ModoTestCase):
self.assertEqual(mb.domain.mailbox_count, 3)
values["username"] = "pouet@test.com"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[account.id]),
values
)
mb = Mailbox.objects.get(pk=mb.id)
self.assertEqual(mb.full_address, "pouet@test.com")
- self.check_ajax_get(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.delaccount",
args=[account.id]),
{}
)
- def _set_quota(self, email, value, expected_status="ok"):
+ def _set_quota(self, email, value, expected_status=200):
account = User.objects.get(username=email)
values = {
"username": email, "role": "SimpleUsers", "quota_act": False,
"is_active": True, "quota": value, "email": email
}
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[account.id]),
values, status=expected_status
@@ -70,7 +70,7 @@ class AccountTestCase(ModoTestCase):
def test_set_nul_quota_as_domainadmin(self):
self.clt.logout()
self.assertTrue(self.clt.login(username="admin@test.com", password="toto"))
- self._set_quota("user@test.com", 0, "ko")
+ self._set_quota("user@test.com", 0, 424)
self.clt.logout()
self.assertTrue(self.clt.login(username="admin@test2.com", password="toto"))
self._set_quota("user@test2.com", 0)
@@ -92,25 +92,24 @@ class PermissionsTestCase(ModoTestCase):
self.clt.logout()
def test_domain_admins(self):
- self.values["role"] = "DomainAdmins"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[self.user.id]),
self.values
)
- self.assertEqual(self.user.group == "DomainAdmins", True)
+ self.assertEqual(self.user.group, "DomainAdmins")
self.values["role"] = "SimpleUsers"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[self.user.id]),
self.values
)
- self.assertEqual(self.user.group == 'DomainAdmins', False)
+ self.assertNotEqual(self.user.group, 'DomainAdmins')
def test_superusers(self):
self.values["role"] = "SuperAdmins"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[self.user.id]),
self.values
@@ -118,7 +117,7 @@ class PermissionsTestCase(ModoTestCase):
self.assertEqual(User.objects.get(username="user@test.com").is_superuser, True)
self.values["role"] = "SimpleUsers"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[self.user.id]),
self.values
@@ -135,7 +134,7 @@ class PermissionsTestCase(ModoTestCase):
password1="", password2="",
quota=10, is_active=True, email="admin@test.com"
)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[admin.id]),
values
@@ -144,7 +143,7 @@ class PermissionsTestCase(ModoTestCase):
self.assertEqual(admin.can_access(Domain.objects.get(name="test.com")), True)
values["role"] = "SuperAdmins"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[admin.id]),
values
@@ -178,25 +177,23 @@ class PermissionsTestCase(ModoTestCase):
username="superadmin2@test.com", first_name="Super",
last_name="Admin", password1="toto", password2="toto",
role="SuperAdmins", is_active=True,
- email="superadmin2@test.com", stepid=2
+ email="superadmin2@test.com", stepid='step2'
)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.newaccount"),
values
)
account = User.objects.get(username="superadmin2@test.com")
self.clt.logout()
self.clt.login(username="admin@test.com", password="toto")
- self.check_ajax_get(
+ self.ajax_get(
reverse("modoboa.extensions.admin.views.identity.delaccount",
args=[account.id]),
- {},
- status="ko", respmsg="Permission denied"
+ {}, 403
)
- self.check_ajax_get(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.delaccount",
- args=[account.id]), {},
- status="ko", respmsg="Permission denied"
+ args=[account.id]), {}, 403
)
def test_domainadmin_dlist_local_domain_not_owned(self):
@@ -208,6 +205,6 @@ class PermissionsTestCase(ModoTestCase):
recipients_1="user@test2.com",
enabled=True
)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.newdlist"), values
)
diff --git a/modoboa/extensions/admin/tests/alias.py b/modoboa/extensions/admin/tests/alias.py
index 1cb9a277d..ff6fbc840 100644
--- a/modoboa/extensions/admin/tests/alias.py
+++ b/modoboa/extensions/admin/tests/alias.py
@@ -21,7 +21,7 @@ class AliasTestCase(ModoTestCase):
is_active=user.is_active, email="user@test.com",
aliases="toto@test.com", aliases_1="titi@test.com"
)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[user.id]),
values
@@ -29,7 +29,7 @@ class AliasTestCase(ModoTestCase):
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
del values["aliases_1"]
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.editaccount",
args=[user.id]),
values
@@ -42,7 +42,7 @@ class AliasTestCase(ModoTestCase):
recipients_1="admin@test.com",
recipients_2="ext@titi.com",
enabled=True)
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.newdlist"), values
)
user = User.objects.get(username="user@test.com")
@@ -53,14 +53,14 @@ class AliasTestCase(ModoTestCase):
dlist = Alias.objects.get(address="all", domain__name="test.com")
self.assertEqual(len(dlist.get_recipients()), 3)
del values["recipients_1"]
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.editalias",
args=[dlist.id]),
values
)
self.assertEqual(dlist.get_recipients_count(), 2)
- self.check_ajax_get(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.delalias") + "?selection=%d" \
% dlist.id, {}
)
@@ -69,21 +69,21 @@ class AliasTestCase(ModoTestCase):
def test_forward(self):
values = dict(email="forward2@test.com", recipients="rcpt@dest.com")
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.newforward"), values
)
fwd = Alias.objects.get(address="forward2", domain__name="test.com")
self.assertEqual(fwd.get_recipients_count(), 1)
values["recipients"] = "rcpt2@dest.com"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.editalias",
args=[fwd.id]),
values
)
self.assertEqual(fwd.get_recipients_count(), 1)
- self.check_ajax_get(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.delalias") + "?selection=%d" \
% fwd.id, {}
)
@@ -92,7 +92,7 @@ class AliasTestCase(ModoTestCase):
def test_forward_and_local_copies(self):
values = dict(email="user@test.com", recipients="rcpt@dest.com")
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.newforward"), values
)
fwd = Alias.objects.get(address="user", domain__name="test.com")
@@ -100,7 +100,7 @@ class AliasTestCase(ModoTestCase):
values["recipients"] = "rcpt@dest.com"
values["recipients_1"] = "user@test.com"
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.alias.editalias", args=[fwd.id]),
values
)
diff --git a/modoboa/extensions/admin/tests/domain.py b/modoboa/extensions/admin/tests/domain.py
index 234e0f117..929afd3c8 100644
--- a/modoboa/extensions/admin/tests/domain.py
+++ b/modoboa/extensions/admin/tests/domain.py
@@ -19,9 +19,9 @@ class DomainTestCase(ModoTestCase):
"""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "no",
- "stepid": 2
+ "stepid": 'step2'
}
- self.check_ajax_post(reverse("modoboa.extensions.admin.views.domain.newdomain"), values)
+ self.ajax_post(reverse("modoboa.extensions.admin.views.domain.newdomain"), values)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.name, "pouet.com")
self.assertEqual(dom.quota, 100)
@@ -35,9 +35,9 @@ class DomainTestCase(ModoTestCase):
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
- "stepid": 2
+ "stepid": 'step2'
}
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.newdomain"),
values
)
@@ -53,9 +53,9 @@ class DomainTestCase(ModoTestCase):
values = {
"name": "pouet.com", "quota": 0, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
- "stepid": 2
+ "stepid": 'step2'
}
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.newdomain"),
values
)
@@ -75,7 +75,7 @@ class DomainTestCase(ModoTestCase):
"name": "pouet.com", "quota": 100, "enabled": True
}
dom = Domain.objects.get(name="test.com")
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.editdomain",
args=[dom.id]),
values
@@ -88,7 +88,7 @@ class DomainTestCase(ModoTestCase):
"""Test the removal of a domain
"""
dom = Domain.objects.get(name="test.com")
- self.check_ajax_get(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.deldomain",
args=[dom.id]),
{}
diff --git a/modoboa/extensions/admin/tests/domain_alias.py b/modoboa/extensions/admin/tests/domain_alias.py
index d79e2a645..9930d0d16 100644
--- a/modoboa/extensions/admin/tests/domain_alias.py
+++ b/modoboa/extensions/admin/tests/domain_alias.py
@@ -31,7 +31,7 @@ class DomainAliasTestCase(ModoTestCase):
dom = Domain.objects.get(name="test.com")
values = dict(name=dom.name, quota=dom.quota, enabled=dom.enabled,
aliases="domalias.net", aliases_1="domalias.com")
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.editdomain",
args=[dom.id]),
values
@@ -39,7 +39,7 @@ class DomainAliasTestCase(ModoTestCase):
self.assertEqual(dom.domainalias_set.count(), 2)
del values["aliases_1"]
- self.check_ajax_post(
+ self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.editdomain",
args=[dom.id]),
values
diff --git a/modoboa/extensions/admin/views/alias.py b/modoboa/extensions/admin/views/alias.py
index b03c32fd3..c8410615f 100644
--- a/modoboa/extensions/admin/views/alias.py
+++ b/modoboa/extensions/admin/views/alias.py
@@ -6,59 +6,50 @@ from django.contrib.auth.decorators import (
login_required, permission_required
)
from modoboa.lib import events
-from modoboa.lib.webutils import ajax_response, ajax_simple_response
-from modoboa.lib.exceptions import PermDeniedException
-from modoboa.extensions.admin.exceptions import AdminError
+from modoboa.lib.webutils import render_to_json_response
+from modoboa.lib.exceptions import PermDeniedException, Conflict
from modoboa.extensions.admin.forms import AliasForm
from modoboa.extensions.admin.models import Alias
-def _validate_alias(request, form, successmsg, tplname, commonctx, callback=None):
+def _validate_alias(request, form, successmsg, callback=None):
"""Alias validation
Common function shared between creation and modification actions.
"""
- error = None
if form.is_valid():
form.set_recipients()
try:
alias = form.save()
except IntegrityError:
- raise AdminError(_("Alias with this name already exists"))
+ raise Conflict(_("Alias with this name already exists"))
if callback:
callback(request.user, alias)
- return ajax_simple_response({"status": "ok", "respmsg": successmsg})
+ return render_to_json_response(successmsg)
- if "targets" in request.POST:
- targets = request.POST.getlist("targets")
- commonctx["targets"] = targets[:-1]
-
- commonctx["form"] = form
- commonctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **commonctx)
+ return render_to_json_response({'form_errors': form.errors}, status=400)
def _new_alias(request, title, action, successmsg,
- tplname="common/generic_modal_form.html"):
+ tplname="admin/aliasform.html"):
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
- ctx = {
- "title": title,
- "action": action,
- "formid": "aliasform",
- "action_label": _("Create"),
- "action_classes": "submit"
- }
if request.method == "POST":
def callback(user, alias):
alias.post_create(user)
form = AliasForm(request.user, request.POST)
return _validate_alias(
- request, form, successmsg, tplname, ctx, callback
+ request, form, successmsg, callback
)
- form = AliasForm(request.user)
- ctx["form"] = form
+ ctx = {
+ "title": title,
+ "action": action,
+ "formid": "aliasform",
+ "action_label": _("Create"),
+ "action_classes": "submit",
+ "form": AliasForm(request.user)
+ }
return render(request, tplname, ctx)
@@ -94,29 +85,29 @@ def newforward(request):
@login_required
@permission_required("admin.change_alias")
-def editalias(request, alid, tplname="common/generic_modal_form.html"):
+@transaction.commit_on_success
+def editalias(request, alid, tplname="admin/aliasform.html"):
alias = Alias.objects.get(pk=alid)
if not request.user.can_access(alias):
raise PermDeniedException
- ctx = dict(
- action=reverse(editalias, args=[alias.id]),
- formid="aliasform",
- title=alias.full_address,
- action_label=_("Update"),
- action_classes="submit"
- )
- if len(alias.get_recipients()) >= 2:
- successmsg = _("Distribution list modified")
- elif alias.extmboxes != "":
- successmsg = _("Forward modified")
- else:
- successmsg = _("Alias modified")
if request.method == "POST":
+ if len(alias.get_recipients()) >= 2:
+ successmsg = _("Distribution list modified")
+ elif alias.extmboxes != "":
+ successmsg = _("Forward modified")
+ else:
+ successmsg = _("Alias modified")
form = AliasForm(request.user, request.POST, instance=alias)
- return _validate_alias(request, form, successmsg, tplname, ctx)
+ return _validate_alias(request, form, successmsg)
- form = AliasForm(request.user, instance=alias)
- ctx["form"] = form
+ ctx = {
+ 'action': reverse(editalias, args=[alias.id]),
+ 'formid': 'aliasform',
+ 'title': alias.full_address,
+ 'action_label': _('Update'),
+ 'action_classes': 'submit',
+ 'form': AliasForm(request.user, instance=alias)
+ }
return render(request, tplname, ctx)
@@ -141,4 +132,4 @@ def delalias(request):
alias.delete()
msg = ungettext(msg, msgs, len(selection))
- return ajax_simple_response({"status": "ok", "respmsg": msg})
+ return render_to_json_response(msg)
diff --git a/modoboa/extensions/admin/views/domain.py b/modoboa/extensions/admin/views/domain.py
index 812cc4928..29b43132c 100644
--- a/modoboa/extensions/admin/views/domain.py
+++ b/modoboa/extensions/admin/views/domain.py
@@ -2,7 +2,6 @@ from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
from django.db import transaction
-from django.db.models import Q
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import (
@@ -10,13 +9,15 @@ from django.contrib.auth.decorators import (
)
from modoboa.lib import parameters, events
from modoboa.lib.webutils import (
- ajax_simple_response, _render_to_string
+ ajax_simple_response, _render_to_string,
+ render_to_json_response
)
from modoboa.lib.formutils import CreationWizard
-from modoboa.lib.exceptions import PermDeniedException
+from modoboa.lib.exceptions import (
+ ModoboaException, PermDeniedException, BadRequest
+)
from modoboa.lib.templatetags.lib_tags import pagination_bar
from modoboa.extensions.admin.lib import get_sort_order, get_listing_page
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.models import Domain, Mailbox
from modoboa.extensions.admin.forms import (
DomainForm, DomainFormGeneral, DomainFormOptions
@@ -90,50 +91,49 @@ def domains_list(request):
def newdomain(request, tplname="common/wizard_forms.html"):
events.raiseEvent("CanCreate", request.user, "domains")
- def newdomain_cb(steps):
- genform = steps[0]["form"]
- genform.is_valid()
- domain = genform.save(request.user)
- domain.post_create(request.user)
- steps[1]["form"].save(request.user, domain)
-
- commonctx = {"title": _("New domain"),
- "action_label": _("Create"),
- "action_classes": "submit",
- "action": reverse(newdomain),
- "formid": "domform"}
- cwizard = CreationWizard(newdomain_cb)
+ cwizard = CreationWizard()
cwizard.add_step(DomainFormGeneral, _("General"),
[dict(classes="btn-inverse next", label=_("Next"))],
formtpl="admin/domain_general_form.html")
- cwizard.add_step(DomainFormOptions, _("Options"),
- [dict(classes="btn-primary submit", label=_("Create")),
- dict(classes="btn-inverse prev", label=_("Previous"))],
- formtpl="admin/domain_options_form.html")
+ cwizard.add_step(
+ DomainFormOptions, _("Options"),
+ [dict(classes="btn-primary submit", label=_("Create")),
+ dict(classes="btn-inverse prev", label=_("Previous"))],
+ formtpl="admin/domain_options_form.html",
+ new_args=[request.user]
+ )
if request.method == "POST":
retcode, data = cwizard.validate_step(request)
if retcode == -1:
- raise AdminError(data)
+ raise BadRequest(data)
if retcode == 1:
- return ajax_simple_response(dict(
- status="ok", title=cwizard.get_title(data + 1), stepid=data
- ))
- if retcode == 2:
- return ajax_simple_response(
- dict(status="ok", respmsg=_("Domain created"))
+ return render_to_json_response(
+ {'title': cwizard.get_title(data + 1), 'stepid': data}
)
+ if retcode == 2:
+ genform = cwizard.steps[0]["form"]
+ domain = genform.save(request.user)
+ domain.post_create(request.user)
+ try:
+ cwizard.steps[1]["form"].save(request.user, domain)
+ except ModoboaException as e:
+ transaction.rollback()
+ raise
+ return render_to_json_response(_("Domain created"))
+ return render_to_json_response({
+ 'stepid': data, 'form_errors': cwizard.errors
+ }, status=400)
- from modoboa.lib.templatetags.lib_tags import render_form
- return ajax_simple_response(dict(
- status="ko", stepid=data,
- form=render_form(cwizard.steps[data]["form"])
- ))
-
+ ctx = {"title": _("New domain"),
+ "action_label": _("Create"),
+ "action_classes": "submit",
+ "action": reverse(newdomain),
+ "formid": "domform"}
cwizard.create_forms()
- commonctx.update(steps=cwizard.steps)
- commonctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
- return render(request, tplname, commonctx)
+ ctx.update(steps=cwizard.steps)
+ ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
+ return render(request, tplname, ctx)
@login_required
@@ -144,44 +144,32 @@ def editdomain(request, dom_id, tplname="admin/editdomainform.html"):
if not request.user.can_access(domain):
raise PermDeniedException
+ instances = dict(general=domain)
+ events.raiseEvent("FillDomainInstances", request.user, domain, instances)
+ if request.method == "POST":
+ domain.oldname = domain.name
+ form = DomainForm(request.user, request.POST, instances=instances)
+ if form.is_valid():
+ form.save(request.user)
+ events.raiseEvent("DomainModified", domain)
+ return render_to_json_response(_("Domain modified"))
+ return render_to_json_response({
+ 'form_errors': form.errors
+ }, status=400)
+
domadmins = [u for u in domain.admins
if request.user.can_access(u) and not u.is_superuser]
if not request.user.is_superuser:
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
-
- instances = dict(general=domain)
- events.raiseEvent("FillDomainInstances", request.user, domain, instances)
-
- commonctx = {"title": domain.name,
- "action_label": _("Update"),
- "action_classes": "submit",
- "action": reverse(editdomain, args=[dom_id]),
- "formid": "domform",
- "domain": domain}
- if request.method == "POST":
- error = None
- domain.oldname = domain.name
- form = DomainForm(request.user, request.POST, instances=instances)
- if form.is_valid():
- try:
- form.save(request.user)
- except AdminError, e:
- error = str(e)
- else:
- events.raiseEvent("DomainModified", domain)
- return ajax_simple_response({
- "status": "ok", "respmsg": _("Domain modified")
- })
-
- commonctx["tabs"] = form
- return ajax_simple_response({
- "status": "ko", "respmsg": error,
- "content": _render_to_string(request, tplname, commonctx)
- })
-
- commonctx["tabs"] = DomainForm(request.user, instances=instances)
- commonctx["domadmins"] = domadmins
- return render(request, tplname, commonctx)
+ ctx = {"title": domain.name,
+ "action_label": _("Update"),
+ "action_classes": "submit",
+ "action": reverse(editdomain, args=[dom_id]),
+ "formid": "domform",
+ "domain": domain,
+ "tabs": DomainForm(request.user, instances=instances),
+ "domadmins": domadmins}
+ return render(request, tplname, ctx)
@login_required
@@ -198,8 +186,8 @@ def deldomain(request, dom_id):
if not request.user.can_access(dom):
raise PermDeniedException
if mb and mb.domain == dom:
- raise AdminError(_("You can't delete your own domain"))
+ raise PermDeniedException(_("You can't delete your own domain"))
dom.delete(request.user, keepdir)
msg = ungettext("Domain deleted", "Domains deleted", 1)
- return ajax_simple_response({"status": "ok", "respmsg": msg})
+ return render_to_json_response(msg)
diff --git a/modoboa/extensions/admin/views/identity.py b/modoboa/extensions/admin/views/identity.py
index a2f1e1bc4..7ab550a68 100644
--- a/modoboa/extensions/admin/views/identity.py
+++ b/modoboa/extensions/admin/views/identity.py
@@ -7,9 +7,12 @@ from django.contrib.auth.decorators import (
)
from django.views.decorators.csrf import ensure_csrf_cookie
from modoboa.lib import parameters, events
-from modoboa.lib.exceptions import PermDeniedException
+from modoboa.lib.exceptions import (
+ ModoboaException, PermDeniedException, BadRequest
+)
from modoboa.lib.webutils import (
- ajax_simple_response, _render_to_string, ajax_response
+ ajax_simple_response, _render_to_string,
+ render_to_json_response
)
from modoboa.lib.formutils import CreationWizard
from modoboa.lib.templatetags.lib_tags import pagination_bar
@@ -18,7 +21,6 @@ from modoboa.extensions.admin.models import Mailbox, Domain
from modoboa.extensions.admin.lib import (
get_sort_order, get_listing_page, get_identities
)
-from modoboa.extensions.admin.exceptions import AdminError
from modoboa.extensions.admin.forms import (
AccountForm, AccountFormGeneral, AccountFormMail
)
@@ -99,7 +101,7 @@ def list_quotas(request, tplname="admin/quotas.html"):
order_by=["%s%s" % (sort_dir, sort_order)]
)
else:
- raise AdminError(_("Invalid request"))
+ raise BadRequest(_("Invalid request"))
page = get_listing_page(mboxes, request.GET.get("page", 1))
return ajax_simple_response({
"status": "ok",
@@ -115,35 +117,7 @@ def list_quotas(request, tplname="admin/quotas.html"):
@permission_required("core.add_user")
@transaction.commit_on_success
def newaccount(request, tplname='common/wizard_forms.html'):
- def create_account(steps):
- """Account creation callback
-
- Called when all creation steps have been validated.
-
- :param steps: the steps data
- """
- genform = steps[0]["form"]
- genform.is_valid()
- account = genform.save()
- account.post_create(request.user)
-
- mailform = steps[1]["form"]
- try:
- mailform.save(request.user, account)
- except AdminError:
- # A bit uggly: transaction management doesn't work very
- # well with nested functions. Need to wait for django 1.6
- # and atomicity.
- account.delete(request.user, False)
- raise
-
- ctx = dict(
- title=_("New account"),
- action=reverse(newaccount),
- formid="newaccount_form",
- submit_label=_("Create")
- )
- cwizard = CreationWizard(create_account)
+ cwizard = CreationWizard()
cwizard.add_step(AccountFormGeneral, _("General"),
[dict(classes="btn-inverse next", label=_("Next"))],
new_args=[request.user])
@@ -155,22 +129,35 @@ def newaccount(request, tplname='common/wizard_forms.html'):
if request.method == "POST":
retcode, data = cwizard.validate_step(request)
if retcode == -1:
- raise AdminError(data)
+ raise BadRequest(data)
if retcode == 1:
- return ajax_simple_response(dict(
- status="ok", title=cwizard.get_title(data + 1), stepid=data
- ))
+ return render_to_json_response(
+ {'title': cwizard.get_title(data + 1), 'stepid': data}
+ )
if retcode == 2:
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Account created")
- ))
-
- from modoboa.lib.templatetags.lib_tags import render_form
- return ajax_simple_response(dict(
- status="ko", stepid=data,
- form=render_form(cwizard.steps[data]["form"])
- ))
+ genform = cwizard.steps[0]["form"]
+ account = genform.save()
+ try:
+ account.post_create(request.user)
+ mailform = cwizard.steps[1]["form"]
+ mailform.save(request.user, account)
+ except ModoboaException:
+ # A bit uggly: transaction management doesn't work very
+ # well with nested functions. Need to wait for django 1.6
+ # and atomicity.
+ account.delete(request.user, False)
+ raise
+ return render_to_json_response(_("Account created"))
+ return render_to_json_response({
+ 'stepid': data, 'form_errors': cwizard.errors
+ }, status=400)
+ ctx = {
+ 'title': _("New account"),
+ 'action': reverse(newaccount),
+ 'formid': 'newaccount_form',
+ 'submit_label': _("Create")
+ }
cwizard.create_forms()
ctx.update(steps=cwizard.steps)
ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
@@ -191,14 +178,6 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
instances = dict(general=account, mail=mb, perms=account)
events.raiseEvent("FillAccountInstances", request.user, account, instances)
- ctx = dict(
- title=account.username,
- formid="accountform",
- action=reverse(editaccount, args=[accountid]),
- action_label=_("Update"),
- action_classes="submit"
- )
-
if request.method == "POST":
classes = {}
form = AccountForm(request.user, request.POST,
@@ -209,15 +188,17 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
if form.is_valid(optional_only=True):
events.raiseEvent("AccountModified", account, form.account)
form.save()
- return ajax_simple_response(
- dict(status="ok", respmsg=_("Account updated"))
- )
- transaction.rollback()
+ return render_to_json_response(_("Account updated"))
+ return render_to_json_response({'form_errors': form.errors}, status=400)
- ctx["tabs"] = form
- return ajax_response(request, status="ko", template=tplname, **ctx)
-
- ctx["tabs"] = AccountForm(request.user, instances=instances)
+ ctx = {
+ 'title': account.username,
+ 'formid': 'accountform',
+ 'action': reverse(editaccount, args=[accountid]),
+ 'action_label': _('Update'),
+ 'action_classes': 'submit',
+ 'tabs': AccountForm(request.user, instances=instances)
+ }
active_tab_id = request.GET.get("active_tab", "default")
if active_tab_id != "default":
ctx["tabs"].active_id = active_tab_id
@@ -229,11 +210,10 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
@transaction.commit_on_success
def delaccount(request, accountid):
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
-
User.objects.get(pk=accountid).delete(request.user, keep_mb_dir=keepdir)
-
- msg = ungettext("Account deleted", "Accounts deleted", 1)
- return ajax_simple_response({"status": "ok", "respmsg": msg})
+ return render_to_json_response(
+ ungettext("Account deleted", "Accounts deleted", 1)
+ )
@login_required
@@ -242,12 +222,12 @@ def remove_permission(request):
domid = request.GET.get("domid", None)
daid = request.GET.get("daid", None)
if domid is None or daid is None:
- raise AdminError(_("Invalid request"))
+ raise BadRequest(_("Invalid request"))
try:
account = User.objects.get(pk=daid)
domain = Domain.objects.get(pk=domid)
except (User.DoesNotExist, Domain.DoesNotExist):
- raise AdminError(_("Invalid request"))
+ raise BadRequest(_("Invalid request"))
if not request.user.can_access(account) or not request.user.can_access(domain):
raise PermDeniedException
domain.remove_admin(account)
diff --git a/modoboa/extensions/admin/views/user.py b/modoboa/extensions/admin/views/user.py
index 37fdd6d09..7c0382c41 100644
--- a/modoboa/extensions/admin/views/user.py
+++ b/modoboa/extensions/admin/views/user.py
@@ -2,10 +2,9 @@ from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.webutils import (
- ajax_response, ajax_simple_response, _render_to_string
+ render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
-from modoboa.extensions.admin.exceptions import BadDestination
from modoboa.extensions.admin.models import Alias, Mailbox
from modoboa.extensions.admin.forms import ForwardForm
@@ -13,12 +12,7 @@ from modoboa.extensions.admin.forms import ForwardForm
@login_required
@needs_mailbox()
def forward(request, tplname='admin/forward.html'):
- try:
- mb = request.user.mailbox_set.all()[0]
- except IndexError:
- raise ModoboaException(
- _("You need a mailbox in order to define a forward")
- )
+ mb = request.user.mailbox_set.all()[0]
try:
al = Alias.objects.get(address=mb.address,
domain__name=mb.domain.name)
@@ -28,28 +22,23 @@ def forward(request, tplname='admin/forward.html'):
form = ForwardForm(request.POST)
error = None
if form.is_valid():
- try:
- if al is None:
- al = Alias()
- al.address = mb.address
- al.domain = mb.domain
- al.enabled = mb.user.is_active
- intdests = []
- if form.cleaned_data["keepcopies"]:
- intdests += [mb]
- form.parse_dest()
- al.save(int_rcpts=intdests, ext_rcpts=form.dests)
- if request.user.group != "SimpleUsers":
- al.post_create(request.user)
- return ajax_response(request, respmsg=_("Forward updated"))
- except BadDestination, e:
- error = str(e)
+ if al is None:
+ al = Alias()
+ al.address = mb.address
+ al.domain = mb.domain
+ al.enabled = mb.user.is_active
+ intdests = []
+ if form.cleaned_data["keepcopies"]:
+ intdests += [mb]
+ form.parse_dest()
+ al.save(int_rcpts=intdests, ext_rcpts=form.dests)
+ if request.user.group != "SimpleUsers":
+ al.post_create(request.user)
+ return render_to_json_response(_("Forward updated"))
- return ajax_simple_response(dict(
- status="ko",
- errors=form.errors,
- respmsg=error
- ))
+ return render_to_json_response(
+ {'form_errors': form.errors}, status=400
+ )
form = ForwardForm()
if al is not None:
@@ -60,8 +49,7 @@ def forward(request, tplname='admin/forward.html'):
pass
else:
form.fields["keepcopies"].initial = True
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": _render_to_string(request, tplname, {
"form": form
})
diff --git a/modoboa/extensions/limits/__init__.py b/modoboa/extensions/limits/__init__.py
index 5c991598f..7f49c549f 100644
--- a/modoboa/extensions/limits/__init__.py
+++ b/modoboa/extensions/limits/__init__.py
@@ -10,16 +10,22 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.core.extensions import ModoExtension, exts_pool
+from .models import LimitTemplates
from .forms import ResourcePoolForm
import controls
+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"
+ "Per administrator resources to limit the number of objects "
+ "they can create"
)
def init(self):
@@ -48,6 +54,7 @@ class Limits(ModoExtension):
def load(self):
from modoboa.extensions.limits.app_settings import ParametersForm
parameters.register(ParametersForm, ugettext_lazy("Limits"))
+ events.declare(levents)
def destroy(self):
parameters.unregister()
@@ -74,7 +81,13 @@ def display_pool_usage(user, target, currentpage):
if user.has_perm("admin.add_domain"):
names += ["domain_admins_limit"]
else:
- names = ["domains_limit", "domain_aliases_limit"]
+ names = [
+ tpl[0] for tpl in LimitTemplates().templates
+ if tpl[0] not in ["domain_admins_limit", "mailboxes_limit",
+ "mailbox_aliases_limit"]
+ and (len(tpl) == 3 or tpl[3] == user.group)
+ ]
+
limits = user.limitspool.limit_set.filter(name__in=names, maxvalue__gt=0)
if len(limits) == 0:
return []
@@ -85,7 +98,8 @@ def display_pool_usage(user, target, currentpage):
def extra_account_form(user, account=None):
if not user.group in ["SuperAdmins", "Resellers"]:
return []
- if account is not None and not account.group in ["Resellers", "DomainAdmins"]:
+ if account is not None and \
+ not account.group in ["Resellers", "DomainAdmins"]:
return []
return [
@@ -126,7 +140,7 @@ def get_static_content(caller, user):
margin: 5px 0;
}
-.resource .progress {
+.resource .progress {
margin-bottom: 0px;
}
diff --git a/modoboa/extensions/limits/app_settings.py b/modoboa/extensions/limits/app_settings.py
index 6ce6665cb..e5b510cff 100644
--- a/modoboa/extensions/limits/app_settings.py
+++ b/modoboa/extensions/limits/app_settings.py
@@ -39,3 +39,7 @@ class ParametersForm(AdminParametersForm):
help_text=_("Maximum number of allowed aliases for a new administrator"),
widget=forms.widgets.TextInput(attrs={"class": "span1"})
)
+
+ def __init__(self, *args, **kwargs):
+ super(AdminParametersForm, self).__init__(*args, **kwargs)
+ self._load_extra_parameters('A')
diff --git a/modoboa/extensions/limits/controls.py b/modoboa/extensions/limits/controls.py
index 18a7b96c2..7ddd2f212 100644
--- a/modoboa/extensions/limits/controls.py
+++ b/modoboa/extensions/limits/controls.py
@@ -6,43 +6,27 @@
"""
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
-from .lib import LimitReached
-from .models import limits_tpl, LimitsPool
+from .lib import LimitReached, inc_limit_usage, dec_limit_usage
+from .models import LimitTemplates, LimitsPool
-def check_limit(user, lname):
+def check_limit(user, lname, count=1):
"""Check if a user has reached a defined limit
We check if the user will not reach the given limit after this
creation.
- Raise a ``LimitReached`` exception if the limit is reached.
-
:param user: a ``User`` object
+ :param int count: the number of objects the user tries to create
+ :raises: ``LimitReached``
"""
try:
- if user.limitspool.will_be_reached(lname):
+ if user.limitspool.will_be_reached(lname, count):
raise LimitReached(user.limitspool.get_limit(lname))
except LimitsPool.DoesNotExist:
pass
-def inc_limit(user, lname):
- try:
- user.limitspool.inc_curvalue(lname)
- except LimitsPool.DoesNotExist:
- pass
-
-
-def dec_limit(user, lname):
- if user is None:
- return
- try:
- user.limitspool.dec_curvalue(lname)
- except LimitsPool.DoesNotExist:
- pass
-
-
def move_pool_resource(owner, user):
"""Move resource from one pool to another
@@ -54,7 +38,7 @@ def move_pool_resource(owner, user):
except LimitsPool.DoesNotExist:
return
if not owner.is_superuser:
- for ltpl in limits_tpl:
+ for ltpl in LimitTemplates().templates:
l = user.limitspool.get_limit(ltpl[0])
if l.maxvalue < 0:
continue
@@ -68,13 +52,13 @@ def move_pool_resource(owner, user):
@events.observe('DomainCreated')
def inc_nb_domains(user, domain):
- inc_limit(user, 'domains_limit')
+ inc_limit_usage(user, 'domains_limit')
@events.observe('DomainDeleted')
def dec_nb_domains(domain):
owner = get_object_owner(domain)
- dec_limit(owner, 'domains_limit')
+ dec_limit_usage(owner, 'domains_limit')
for domalias in domain.domainalias_set.all():
dec_nb_domaliases(domalias)
for mailbox in domain.mailbox_set.all():
@@ -85,7 +69,7 @@ def dec_nb_domains(domain):
@events.observe('DomainAliasCreated')
def inc_nb_domaliases(user, domalias):
- inc_limit(user, 'domain_aliases_limit')
+ inc_limit_usage(user, 'domain_aliases_limit')
@events.observe('DomainAliasDeleted')
@@ -96,12 +80,12 @@ def dec_nb_domaliases(domainaliases):
domainaliases = [domainaliases]
for domainalias in domainaliases:
owner = get_object_owner(domainalias)
- dec_limit(owner, 'domain_aliases_limit')
+ dec_limit_usage(owner, 'domain_aliases_limit')
@events.observe('MailboxCreated')
def inc_nb_mailboxes(user, mailbox):
- inc_limit(user, 'mailboxes_limit')
+ inc_limit_usage(user, 'mailboxes_limit')
@events.observe('MailboxDeleted')
@@ -112,23 +96,23 @@ def dec_nb_mailboxes(mailboxes):
mailboxes = [mailboxes]
for mailbox in mailboxes:
owner = get_object_owner(mailbox)
- dec_limit(owner, 'mailboxes_limit')
+ dec_limit_usage(owner, 'mailboxes_limit')
@events.observe('MailboxAliasCreated')
def inc_nb_mbaliases(user, mailboxalias):
- inc_limit(user, 'mailbox_aliases_limit')
+ inc_limit_usage(user, 'mailbox_aliases_limit')
@events.observe('MailboxAliasDeleted')
def dec_nb_mbaliases(mailboxalias):
owner = get_object_owner(mailboxalias)
- dec_limit(owner, 'mailbox_aliases_limit')
+ dec_limit_usage(owner, 'mailbox_aliases_limit')
@events.observe('CanCreate')
-def can_create_new_object(user, objtype):
- check_limit(user, '%s_limit' % objtype)
+def can_create_new_object(user, objtype, count=1):
+ check_limit(user, '%s_limit' % objtype, count)
@events.observe("AccountCreated")
@@ -140,12 +124,31 @@ def create_pool(user):
if user.belongs_to_group("DomainAdmins"):
check_limit(owner, 'domain_admins_limit')
- inc_limit(owner, 'domain_admins_limit')
+ inc_limit_usage(owner, 'domain_admins_limit')
if user.group in ["DomainAdmins", "Resellers"]:
- p = LimitsPool(user=user)
- p.save()
- p.create_limits()
+ p, created = LimitsPool.objects.get_or_create(user=user)
+ p.create_limits(owner)
+
+
+@events.observe("UserCanSetRole")
+def user_can_set_role(user, role):
+ """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 newrole: role to check
+ """
+ if role == 'DomainAdmins':
+ lname = 'domain_admins_limit'
+ else:
+ return [True]
+ if user.is_superuser or not user.limitspool.will_be_reached(lname):
+ return [True]
+ return [False]
@events.observe("AccountModified")
@@ -164,19 +167,19 @@ def on_account_modified(old, new):
except LimitsPool.DoesNotExist:
p = LimitsPool(user=new)
p.save()
- p.create_limits()
+ p.create_limits(owner)
if not new.group in ["DomainAdmins", "Resellers"]:
move_pool_resource(owner, new)
if old.oldgroup == "DomainAdmins":
if new.group != "DomainAdmins":
- dec_limit(owner, 'domain_admins_limit')
+ dec_limit_usage(owner, 'domain_admins_limit')
return
if new.group == "DomainAdmins":
check_limit(owner, 'domain_admins_limit')
- inc_limit(owner, 'domain_admins_limit')
+ inc_limit_usage(owner, 'domain_admins_limit')
@events.observe("AccountDeleted")
@@ -188,4 +191,4 @@ def on_account_deleted(account, byuser, **kwargs):
move_pool_resource(owner, account)
if account.group == "DomainAdmins":
- dec_limit(owner, 'domain_admins_limit')
+ dec_limit_usage(owner, 'domain_admins_limit')
diff --git a/modoboa/extensions/limits/forms.py b/modoboa/extensions/limits/forms.py
index 8a5ac4fcf..a763284e2 100644
--- a/modoboa/extensions/limits/forms.py
+++ b/modoboa/extensions/limits/forms.py
@@ -1,44 +1,26 @@
# coding: utf-8
-
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import parameters
-from .models import limits_tpl
+from .models import LimitTemplates
from .lib import BadLimitValue, UnsufficientResource
class ResourcePoolForm(forms.Form):
- domain_admins_limit = forms.IntegerField(
- label=ugettext_lazy("Domain admins"),
- help_text=ugettext_lazy("Maximum number of domain administrators this user can create")
- )
- domains_limit = forms.IntegerField(
- label=ugettext_lazy("Domains"),
- help_text=ugettext_lazy("Maximum number of domains this user can create")
- )
- domain_aliases_limit = forms.IntegerField(
- label=ugettext_lazy("Domain aliases"),
- help_text=ugettext_lazy("Maximum number of domain aliases this user can create")
- )
- mailboxes_limit = forms.IntegerField(
- label=ugettext_lazy("Mailboxes"),
- help_text=ugettext_lazy("Maximum number of mailboxes this user can create")
- )
- mailbox_aliases_limit = forms.IntegerField(
- label=ugettext_lazy("Mailbox aliases"),
- help_text=ugettext_lazy("Maximum number of mailbox aliases this user can create")
- )
def __init__(self, *args, **kwargs):
+ self.account = None
if "instance" in kwargs:
self.account = kwargs["instance"]
del kwargs["instance"]
super(ResourcePoolForm, self).__init__(*args, **kwargs)
+ for tpl in LimitTemplates().templates:
+ if len(tpl) > 3 and self.account is not None and self.account.group != tpl[3]:
+ continue
+ self.fields[tpl[0]] = forms.IntegerField(
+ label=tpl[1], help_text=tpl[2]
+ )
if hasattr(self, "account"):
- if self.account.group != "Resellers":
- del self.fields["domain_admins_limit"]
- del self.fields["domains_limit"]
- del self.fields["domain_aliases_limit"]
self.load_from_user(self.account)
def check_limit_value(self, lname):
@@ -46,29 +28,30 @@ class ResourcePoolForm(forms.Form):
raise forms.ValidationError(_("Invalid limit"))
return self.cleaned_data[lname]
- def clean_domains_limit(self):
- return self.check_limit_value("domains_limit")
-
- def clean_domain_aliases_limit(self):
- return self.check_limit_value("domain_aliases_limit")
-
- def clean_mailboxes_limit(self):
- return self.check_limit_value("mailboxes_limit")
-
- def clean_mailbox_aliases_limit(self):
- return self.check_limit_value("mailbox_aliases_limit")
+ def clean(self):
+ cleaned_data = super(ResourcePoolForm, self).clean()
+ for lname in self.fields.keys():
+ if not lname in self._errors and cleaned_data[lname] < -1:
+ self._errors[lname] = self.error_class([_('Invalid limit')])
+ del cleaned_data[lname]
+ return cleaned_data
def load_from_user(self, user):
- for l in limits_tpl:
- if not l[0] in self.fields:
- continue
- self.fields[l[0]].initial = user.limitspool.getmaxvalue(l[0])
+ for lname in self.fields.keys():
+ self.fields[lname].initial = user.limitspool.getmaxvalue(lname)
# The following lines will become useless in a near
# future.
- if self.fields[l[0]].initial == -2:
- self.fields[l[0]].initial = parameters.get_admin("DEFLT_%s" % l[0].upper())
+ if self.fields[lname].initial == -2:
+ self.fields[lname].initial = parameters.get_admin("DEFLT_%s" % lname.upper())
def allocate_from_pool(self, limit, pool):
+ """Allocate resource using an existing pool.
+
+ When a reseller creates a domain administrator, he generally
+ assigns him resource to create new objetcs. As a reseller may
+ also be limited, the resource he gives is taken from its own
+ pool.
+ """
ol = pool.get_limit(limit.name)
if ol.maxvalue == -2:
raise BadLimitValue(_("Your resources are not initialized yet"))
@@ -92,7 +75,7 @@ class ResourcePoolForm(forms.Form):
from modoboa.lib.permissions import get_object_owner
owner = get_object_owner(self.account)
- for ltpl in limits_tpl:
+ for ltpl in LimitTemplates().templates:
if not ltpl[0] in self.cleaned_data:
continue
l = self.account.limitspool.limit_set.get(name=ltpl[0])
diff --git a/modoboa/extensions/limits/lib.py b/modoboa/extensions/limits/lib.py
index 5a395b86c..a4f7b2f1d 100644
--- a/modoboa/extensions/limits/lib.py
+++ b/modoboa/extensions/limits/lib.py
@@ -6,9 +6,12 @@
"""
from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
+from .models import LimitsPool
class LimitReached(ModoboaException):
+ http_code = 403
+
def __init__(self, limit):
self.limit = limit
@@ -17,6 +20,8 @@ class LimitReached(ModoboaException):
class UnsufficientResource(ModoboaException):
+ http_code = 424
+
def __init__(self, limit):
self.limit = limit
@@ -26,3 +31,22 @@ class UnsufficientResource(ModoboaException):
class BadLimitValue(ModoboaException):
pass
+
+
+def inc_limit_usage(user, lname):
+ """Increase a given limit usage.
+
+ """
+ try:
+ user.limitspool.inc_curvalue(lname)
+ except LimitsPool.DoesNotExist:
+ pass
+
+
+def dec_limit_usage(user, lname):
+ if user is None:
+ return
+ try:
+ user.limitspool.dec_curvalue(lname)
+ except LimitsPool.DoesNotExist:
+ pass
diff --git a/modoboa/extensions/limits/models.py b/modoboa/extensions/limits/models.py
index 80f521289..c249aa27b 100644
--- a/modoboa/extensions/limits/models.py
+++ b/modoboa/extensions/limits/models.py
@@ -2,27 +2,53 @@
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import parameters
+from modoboa.lib.singleton import Singleton
from django.conf import settings
+from modoboa.lib import events
-limits_tpl = [
- ("domain_admins_limit", ugettext_lazy("Domain admins")),
- ("domains_limit", ugettext_lazy("Domains")),
- ("domain_aliases_limit", ugettext_lazy("Domain aliases")),
- ("mailboxes_limit", ugettext_lazy("Mailboxes")),
- ("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"))
-]
+
+class LimitTemplates(Singleton):
+
+ def __init__(self):
+ self.templates = [
+ ("domain_admins_limit", ugettext_lazy("Domain admins"),
+ ugettext_lazy("Maximum number of domain administrators this user can create"),
+ 'Resellers'),
+ ("domains_limit", ugettext_lazy("Domains"),
+ ugettext_lazy("Maximum number of domains this user can create"),
+ 'Resellers'),
+ ("domain_aliases_limit", ugettext_lazy("Domain aliases"),
+ ugettext_lazy("Maximum number of domain aliases this user can create"),
+ 'Resellers'),
+ ("mailboxes_limit", ugettext_lazy("Mailboxes"),
+ ugettext_lazy("Maximum number of mailboxes this user can create")),
+ ("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"),
+ ugettext_lazy("Maximum number of mailbox aliases this user can create"))
+ ]
+ self.templates += events.raiseQueryEvent('GetExtraLimitTemplates')
class LimitsPool(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
- def create_limits(self):
- for ltpl in limits_tpl:
- l = Limit()
- l.name = ltpl[0]
- l.pool = self
- l.maxvalue = int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper()))
- l.save()
+ def create_limits(self, creator):
+ """Create limits for this pool.
+
+ All limits defined into ``LimitTemplates`` will be created. If
+ the creator is a super administrator, the default maximum values
+ will be used.
+
+ :param ``User`` creator: user creating this pool
+ """
+ for ltpl in LimitTemplates().templates:
+ try:
+ Limit.objects.get(name=ltpl[0], pool=self)
+ except Limit.DoesNotExist:
+ maxvalue = int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper())) \
+ if creator.is_superuser else 0
+ Limit.objects.create(
+ name=ltpl[0], pool=self, maxvalue=maxvalue
+ )
def getcurvalue(self, lname):
l = self.limit_set.get(name=lname)
@@ -32,6 +58,11 @@ class LimitsPool(models.Model):
l = self.limit_set.get(name=lname)
return l.maxvalue
+ def set_maxvalue(self, lname, value):
+ l = self.limit_set.get(name=lname)
+ l.maxvalue = value
+ l.save()
+
def inc_curvalue(self, lname, nb=1):
l = self.limit_set.get(name=lname)
l.curvalue += nb
@@ -92,7 +123,7 @@ class Limit(models.Model):
@property
def label(self):
- for l in limits_tpl:
+ for l in LimitTemplates().templates:
if l[0] == self.name:
return l[1]
return ""
diff --git a/modoboa/extensions/limits/tests.py b/modoboa/extensions/limits/tests.py
index 410b9f30c..0bae19785 100644
--- a/modoboa/extensions/limits/tests.py
+++ b/modoboa/extensions/limits/tests.py
@@ -1,8 +1,13 @@
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ExtTestCase
-from modoboa.core.factories import UserFactory
-from modoboa.extensions.admin.factories import DomainFactory, MailboxFactory
+from modoboa.lib import parameters
from modoboa.core.models import User
+from modoboa.core.factories import UserFactory
+from modoboa.extensions.admin.factories import (
+ DomainFactory, MailboxFactory, populate_database
+)
+from modoboa.extensions.admin.models import Alias, Domain, DomainAlias
+from modoboa.extensions.limits.models import LimitTemplates
class PermissionsTestCase(ExtTestCase):
@@ -26,13 +31,342 @@ class PermissionsTestCase(ExtTestCase):
values = dict(
username="reseller@test.com", first_name="Reseller", last_name="",
password1="toto", password2="toto", role="Resellers",
- is_active=True, email="reseller@test.com", stepid=2
+ is_active=True, email="reseller@test.com", stepid='step2'
)
- self.check_ajax_post(reverse("modoboa.extensions.admin.views.identity.newaccount"), values)
+ self.ajax_post(reverse("modoboa.extensions.admin.views.identity.newaccount"), values)
account = User.objects.get(username="reseller@test.com")
self.clt.logout()
self.clt.login(username="admin@test.com", password="toto")
- self.check_ajax_get(
+ resp = self.ajax_post(
reverse("modoboa.extensions.admin.views.identity.delaccount", args=[account.id]),
- {}, status="ko", respmsg="Permission denied"
+ {}, status=403
)
+ self.assertEqual(resp, "Permission denied")
+
+
+class ResourceTestCase(ExtTestCase):
+ fixtures = ["initial_users.json"]
+ name = "limits"
+
+ def setUp(self):
+ super(ResourceTestCase, self).setUp()
+ populate_database()
+ for tpl in LimitTemplates().templates:
+ parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits')
+
+ def _create_account(self, username, role='SimpleUsers', status=200):
+ values = dict(
+ username=username, first_name="Tester", last_name="Toto",
+ password1="toto", password2="toto", role=role,
+ quota_act=True,
+ is_active=True, email=username, stepid='step2',
+ )
+ return self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.newaccount"),
+ values, status
+ )
+
+ def _create_alias(self, email, rcpt='user@test.com', status=200):
+ values = dict(
+ email=email, recipients=rcpt, enabled=True
+ )
+ return self.ajax_post(
+ reverse("modoboa.extensions.admin.views.alias.newalias"),
+ values, status
+ )
+
+ def _create_domain(self, name, status=200, withtpl=False):
+ values = {
+ "name": name, "quota": 100, "create_dom_admin": "no",
+ "create_aliases": "no", "stepid": 'step2'
+ }
+ if withtpl:
+ values['create_dom_admin'] = 'yes'
+ values['dom_admin_username'] = 'admin'
+ values['create_aliases'] = 'yes'
+ return self.ajax_post(
+ reverse("modoboa.extensions.admin.views.domain.newdomain"),
+ values, status
+ )
+
+ def _domain_alias_operation(self, optype, domain, name, status=200):
+ dom = Domain.objects.get(name=domain)
+ values = {
+ 'name': dom.name, 'quota': dom.quota, 'enabled': dom.enabled,
+ }
+ aliases = [alias.name for alias in dom.domainalias_set.all()]
+ if optype == 'add':
+ aliases.append(name)
+ else:
+ aliases.remove(name)
+ for cpt, alias in enumerate(aliases):
+ fname = 'aliases' if not cpt else 'aliases_%d' % cpt
+ values[fname] = alias
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.domain.editdomain",
+ args=[dom.id]),
+ values, status
+ )
+
+ def _check_limit(self, name, curvalue, maxvalue):
+ l = self.user.limitspool.get_limit('%s_limit' % name)
+ self.assertEqual(l.curvalue, curvalue)
+ self.assertEqual(l.maxvalue, maxvalue)
+
+
+class DomainAdminTestCase(ResourceTestCase):
+
+ def setUp(self):
+ super(DomainAdminTestCase, self).setUp()
+ self.user = User.objects.get(username='admin@test.com')
+ self.user.limitspool.set_maxvalue('mailboxes_limit', 2)
+ self.user.limitspool.set_maxvalue('mailbox_aliases_limit', 2)
+ self.clt.logout()
+ self.clt.login(username='admin@test.com', password='toto')
+
+ def test_mailboxes_limit(self):
+ self._create_account('tester1@test.com')
+ self._check_limit('mailboxes', 1, 2)
+ self._create_account('tester2@test.com')
+ self._check_limit('mailboxes', 2, 2)
+ resp = self._create_account('tester3@test.com', status=403)
+ self._check_limit('mailboxes', 2, 2)
+ self.ajax_post(
+ reverse('modoboa.extensions.admin.views.identity.delaccount',
+ args=[User.objects.get(username='tester2@test.com').id]),
+ {}
+ )
+ self._check_limit('mailboxes', 1, 2)
+
+ def test_aliases_limit(self):
+ self._create_alias('alias1@test.com')
+ self._check_limit('mailbox_aliases', 1, 2)
+ self._create_alias('alias2@test.com')
+ self._check_limit('mailbox_aliases', 2, 2)
+ resp = self._create_alias('alias3@test.com', status=403)
+ self._check_limit('mailbox_aliases', 2, 2)
+ self.ajax_post(
+ reverse('modoboa.extensions.admin.views.alias.delalias') + '?selection=%d' \
+ % Alias.objects.get(address='alias2', domain__name='test.com').id,
+ {}
+ )
+ self._check_limit('mailbox_aliases', 1, 2)
+
+ def test_aliases_limit_through_account_form(self):
+ user = User.objects.get(username='user@test.com')
+ values = dict(
+ username=user.username, role=user.group,
+ is_active=user.is_active, email=user.email, quota_act=True,
+ aliases="alias1@test.com", aliases_1="alias2@test.com"
+ )
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values
+ )
+ Alias.objects.get(address='alias1', domain__name='test.com')
+ self._check_limit('mailbox_aliases', 2, 2)
+
+
+class ResellerTestCase(ResourceTestCase):
+
+ def setUp(self):
+ super(ResellerTestCase, self).setUp()
+ self.user = UserFactory.create(
+ username='reseller', groups=('Resellers',)
+ )
+ self.clt.logout()
+ self.clt.login(username='reseller', password='toto')
+
+ def test_domains_limit(self):
+ self._create_domain('domain1.tld')
+ self._check_limit('domains', 1, 2)
+ self._create_domain('domain2.tld')
+ self._check_limit('domains', 2, 2)
+ resp = self._create_domain('domain3.tld', 403)
+ self._check_limit('domains', 2, 2)
+ self.ajax_post(
+ reverse('modoboa.extensions.admin.views.domain.deldomain',
+ args=[Domain.objects.get(name='domain2.tld').id]),
+ {}
+ )
+ self._check_limit('domains', 1, 2)
+
+ def test_domain_aliases_limit(self):
+ self._create_domain('pouet.com')
+ self._domain_alias_operation('add', 'pouet.com', 'domain_alias1.tld')
+ self._check_limit('domain_aliases', 1, 2)
+ self._domain_alias_operation('add', 'pouet.com', 'domain_alias2.tld')
+ self._check_limit('domain_aliases', 2, 2)
+ resp = self._domain_alias_operation(
+ 'add', 'pouet.com', 'domain_alias3.tld', 403
+ )
+ self._check_limit('domain_aliases', 2, 2)
+ self._domain_alias_operation('delete', 'pouet.com', 'domain_alias2.tld')
+ self._check_limit('domain_aliases', 1, 2)
+
+ def test_domain_admins_limit(self):
+ self._create_domain('domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ self._check_limit('domain_admins', 1, 2)
+ self._create_account('admin2@domain.tld', role='DomainAdmins')
+ self._check_limit('domain_admins', 2, 2)
+ resp = self._create_account('admin3@domain.tld', role='DomainAdmins', status=400)
+ self.assertEqual(
+ resp['form_errors']['role'][0],
+ 'Select a valid choice. DomainAdmins is not one of the available choices.'
+ )
+ self._check_limit('domain_admins', 2, 2)
+
+ self.user.limitspool.set_maxvalue('mailboxes_limit', 3)
+ self._create_account('user1@domain.tld')
+ user = User.objects.get(username='user1@domain.tld')
+ values = {
+ 'username': user.username, 'role': 'DomainAdmins', 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email
+ }
+ resp = self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values, status=400
+ )
+ self.assertEqual(
+ resp['form_errors']['role'][0],
+ 'Select a valid choice. DomainAdmins is not one of the available choices.'
+ )
+ self._check_limit('domain_admins', 2, 2)
+
+ def test_domain_admin_resource_are_empty(self):
+ self._create_domain('domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ domadmin = User.objects.get(username='admin1@domain.tld')
+ for l in ['mailboxes', 'mailbox_aliases']:
+ self.assertEqual(
+ domadmin.limitspool.get_limit('%s_limit' % l).maxvalue, 0
+ )
+
+ def test_domain_admins_limit_from_domain_tpl(self):
+ self.user.limitspool.set_maxvalue('domains_limit', 3)
+ self._create_domain('domain1.tld', withtpl=True)
+ self._create_domain('domain2.tld', withtpl=True)
+ self._check_limit('domain_admins', 2, 2)
+ self._check_limit('domains', 2, 3)
+ resp = self._create_domain('domain3.tld', status=200, withtpl=True)
+ self._check_limit('domain_admins', 2, 2)
+ self._check_limit('domains', 3, 3)
+
+ def test_allocate_from_pool(self):
+ self._create_domain('domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ user = User.objects.get(username='admin1@domain.tld')
+
+ # Give 1 mailbox and 2 aliases to the admin -> should work
+ values = {
+ 'username': user.username, 'role': user.group, 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email,
+ 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
+ }
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values
+ )
+ self._check_limit('mailboxes', 1, 1)
+ self._check_limit('mailbox_aliases', 0, 0)
+
+ # Delete the admin -> resources should go back to the
+ # reseller's pool
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.delaccount",
+ args=[user.id]),
+ {}
+ )
+ self._check_limit('mailboxes', 0, 2)
+ self._check_limit('mailbox_aliases', 0, 2)
+
+ def test_restore_resources(self):
+ self._create_domain('domain.tld')
+ dom = Domain.objects.get(name='domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ user = User.objects.get(username='admin1@domain.tld')
+ values = {
+ 'username': user.username, 'role': user.group, 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email,
+ 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
+ }
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values
+ )
+ dom.add_admin(user)
+ self.clt.logout()
+ self.clt.login(username='admin1@domain.tld', password='toto')
+ self._create_account('user1@domain.tld')
+ self._create_alias('alias1@domain.tld', 'user1@domain.tld')
+ self._create_alias('alias2@domain.tld', 'user1@domain.tld')
+ self.clt.logout()
+ self.clt.login(username='reseller', password='toto')
+ # Delete the admin -> resources should go back to the
+ # reseller's pool
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.delaccount",
+ args=[user.id]),
+ {}
+ )
+ self._check_limit('mailboxes', 1, 2)
+ self._check_limit('mailbox_aliases', 2, 2)
+
+ def test_change_role(self):
+ self._create_domain('domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ user = User.objects.get(username='admin1@domain.tld')
+
+ # Give 1 mailbox and 2 aliases to the admin -> should work
+ values = {
+ 'username': user.username, 'role': user.group, 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email,
+ 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2
+ }
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values
+ )
+ self._check_limit('mailboxes', 1, 1)
+ self._check_limit('mailbox_aliases', 0, 0)
+
+ # Change admin role to SimpleUser -> resources should go back
+ # to the reseller.
+ values = {
+ 'username': user.username, 'role': 'SimpleUsers', 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email,
+ }
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values
+ )
+ self._check_limit('mailboxes', 1, 2)
+ self._check_limit('mailbox_aliases', 0, 2)
+
+
+ def test_allocate_too_much(self):
+ self._create_domain('domain.tld')
+ self._create_account('admin1@domain.tld', role='DomainAdmins')
+ user = User.objects.get(username='admin1@domain.tld')
+
+ # Give 2 mailboxes and 3 aliases to the admin -> should fail.
+ values = {
+ 'username': user.username, 'role': user.group, 'quota_act': True,
+ 'is_active': user.is_active, 'email': user.email,
+ 'mailboxes_limit': 2, 'mailbox_aliases_limit': 3
+ }
+ resp = self.ajax_post(
+ reverse("modoboa.extensions.admin.views.identity.editaccount",
+ args=[user.id]),
+ values, 424
+ )
+ self.assertEqual(resp, 'Not enough resources')
+ self._check_limit('mailboxes', 1, 2)
+ self._check_limit('mailbox_aliases', 0, 2)
diff --git a/modoboa/extensions/postfix_autoreply/views.py b/modoboa/extensions/postfix_autoreply/views.py
index 2766d533e..7b234966b 100644
--- a/modoboa/extensions/postfix_autoreply/views.py
+++ b/modoboa/extensions/postfix_autoreply/views.py
@@ -3,7 +3,7 @@ from datetime import date
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import (
- ajax_simple_response, _render_to_string
+ render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
from modoboa.extensions.admin.models import Mailbox
@@ -29,24 +29,20 @@ def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
arm.untildate = form.cleaned_data["untildate"]
arm.mbox = mb
arm.save()
- return ajax_simple_response(dict(
- status="ok",
- respmsg=_("Auto reply message updated successfully.")
- ))
+ return render_to_json_response(
+ _("Auto reply message updated successfully.")
+ )
- return ajax_simple_response({
- "status": "ko",
- "errors": form.errors,
- "onload_cb": "autoreply_cb"
- })
+ return render_to_json_response(
+ {"form_errors": form.errors}, status=400
+ )
form = ARmessageForm(instance=arm)
if arm is not None:
form.fields['untildate'].initial = arm.untildate
else:
form.fields['untildate'].initial = date.today()
- return ajax_simple_response({
- "status": "ok",
+ return render_to_json_response({
"content": _render_to_string(request, tplname, {"form": form}),
"onload_cb": "autoreply_cb"
})
diff --git a/modoboa/extensions/postfix_relay_domains/__init__.py b/modoboa/extensions/postfix_relay_domains/__init__.py
index 912da41f4..151462236 100644
--- a/modoboa/extensions/postfix_relay_domains/__init__.py
+++ b/modoboa/extensions/postfix_relay_domains/__init__.py
@@ -1,5 +1,7 @@
# coding: utf-8
from django.core.urlresolvers import reverse
+from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
from django.template import Template, Context
from django.conf import settings
from django.utils.translation import ugettext_lazy
@@ -10,6 +12,9 @@ from .models import RelayDomain, RelayDomainAlias, Service
extension_events = [
"RelayDomainCreated",
+ "RelayDomainDeleted",
+ "RelayDomainAliasCreated",
+ "RelayDomainAliasDeleted"
]
@@ -27,15 +32,41 @@ class PostfixRelayDomains(ModoExtension):
"""
for service_name in ['relay', 'smtp']:
Service.objects.get_or_create(name=service_name)
+ if not exts_pool.is_extension_enabled('limits'):
+ return
+
+ 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 load(self):
from .app_settings import AdminParametersForm
- parameters.register(AdminParametersForm, ugettext_lazy("Relay domains"))
+ parameters.register(
+ AdminParametersForm, ugettext_lazy("Relay domains")
+ )
events.declare(extension_events)
+ if not exts_pool.is_extension_enabled('limits'):
+ return
+ import limits_controls
def destroy(self):
events.unregister('GetExtraDomainEntries', extra_domain_entries)
+ events.unregister('GetExtraParameters', extra_parameters)
exts_pool.register_extension(PostfixRelayDomains)
@@ -132,3 +163,41 @@ def check_domain_name():
(RelayDomain, ugettext_lazy('relay domain')),
(RelayDomainAlias, ugettext_lazy('relay domain alias'))
]
+
+
+@events.observe('GetExtraLimitTemplates')
+def extra_limit_templates():
+ return [
+ ('relay_domains_limit', ugettext_lazy('Relay domains'),
+ ugettext_lazy('Maximum number of relay domains this user can create'),
+ 'Resellers'),
+ ("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"),
+ ugettext_lazy('Maximum number of relay domain aliases this user can create'),
+ 'Resellers'),
+ ]
+
+
+@events.observe('GetExtraParameters')
+def extra_parameters(app, level):
+ from django import forms
+
+ if app != 'limits' or level != 'A':
+ return {}
+ return {
+ 'deflt_relay_domains_limit': forms.IntegerField(
+ label=ugettext_lazy("Relay domains"),
+ initial=0,
+ help_text=ugettext_lazy(
+ "Maximum number of allowed relay domains for a new administrator"
+ ),
+ widget=forms.widgets.TextInput(attrs={"class": "span1"})
+ ),
+ 'deflt_relay_domain_aliases_limit': forms.IntegerField(
+ label=ugettext_lazy("Relay domain aliases"),
+ initial=0,
+ help_text=ugettext_lazy(
+ "Maximum number of allowed relay domain aliases for a new administrator"
+ ),
+ widget=forms.widgets.TextInput(attrs={"class": "span1"})
+ )
+ }
diff --git a/modoboa/extensions/postfix_relay_domains/app_settings.py b/modoboa/extensions/postfix_relay_domains/app_settings.py
index d84de1aec..49d17ae0c 100644
--- a/modoboa/extensions/postfix_relay_domains/app_settings.py
+++ b/modoboa/extensions/postfix_relay_domains/app_settings.py
@@ -1,6 +1,5 @@
from django import forms
from django.utils.translation import ugettext_lazy
-from modoboa.lib.formutils import SeparatorField
from modoboa.lib import parameters
diff --git a/modoboa/extensions/postfix_relay_domains/forms.py b/modoboa/extensions/postfix_relay_domains/forms.py
index f3100bd68..767caeb3a 100644
--- a/modoboa/extensions/postfix_relay_domains/forms.py
+++ b/modoboa/extensions/postfix_relay_domains/forms.py
@@ -1,6 +1,7 @@
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
+from modoboa.lib import events
from modoboa.lib.formutils import DynamicForm, DomainNameField
from modoboa.extensions.admin.models import Domain, DomainAlias
from .models import RelayDomain, RelayDomainAlias
@@ -92,23 +93,31 @@ class RelayDomainForm(forms.ModelForm, DynamicForm):
rd = super(RelayDomainForm, self).save(commit=False)
if commit:
rd.save()
+ aliases = []
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
if v in ["", None]:
continue
- try:
- rd.relaydomainalias_set.get(name=v)
- except RelayDomainAlias.DoesNotExist:
- pass
- else:
- continue
- #events.raiseEvent("CanCreate", user, "domain_aliases")
- al = RelayDomainAlias(name=v, target=rd, enabled=rd.enabled)
- al.save(creator=user)
-
+ aliases.append(v)
for rdalias in rd.relaydomainalias_set.all():
- if not filter(lambda name: self.cleaned_data[name] == rdalias.name,
- self.cleaned_data.keys()):
+ if not rdalias.name in aliases:
rdalias.delete()
+ else:
+ aliases.remove(rdalias.name)
+ if aliases:
+ events.raiseEvent(
+ "CanCreate", user, "relay_domain_aliases", len(aliases)
+ )
+ for alias in aliases:
+ try:
+ rd.relaydomainalias_set.get(name=alias)
+ except RelayDomainAlias.DoesNotExist:
+ pass
+ else:
+ continue
+ al = RelayDomainAlias(
+ name=alias, target=rd, enabled=rd.enabled
+ )
+ al.save(creator=user)
return rd
diff --git a/modoboa/extensions/postfix_relay_domains/limits_controls.py b/modoboa/extensions/postfix_relay_domains/limits_controls.py
new file mode 100644
index 000000000..9966945e7
--- /dev/null
+++ b/modoboa/extensions/postfix_relay_domains/limits_controls.py
@@ -0,0 +1,31 @@
+from modoboa.lib import events
+from modoboa.lib.permissions import get_object_owner
+from modoboa.extensions.limits.lib import inc_limit_usage, dec_limit_usage
+from .models import RelayDomainAlias
+
+
+@events.observe('RelayDomainCreated')
+def inc_relaydomains_count(user, rdomain):
+ inc_limit_usage(user, 'relay_domains_limit')
+
+
+@events.observe('RelayDomainDeleted')
+def dec_relaydomains_count(rdomain):
+ owner = get_object_owner(rdomain)
+ dec_limit_usage(owner, 'relay_domains_limit')
+ for rdomalias in rdomain.relaydomainalias_set.all():
+ dec_rdomaliases_count(rdomalias)
+
+
+@events.observe('RelayDomainAliasCreated')
+def inc_rdomaliases_count(user, rdomalias):
+ inc_limit_usage(user, 'relay_domain_aliases_limit')
+
+
+@events.observe('RelayDomainAliasDeleted')
+def dec_rdomaliases_count(rdomainaliases):
+ if isinstance(rdomainaliases, RelayDomainAlias):
+ rdomainaliases = [rdomainaliases]
+ for rdomainalias in rdomainaliases:
+ owner = get_object_owner(rdomainalias)
+ dec_limit_usage(owner, 'relay_domain_aliases_limit')
diff --git a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js
index 830e3e977..f14188ee0 100644
--- a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js
+++ b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js
@@ -16,6 +16,15 @@ RelayDomains.prototype = {
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
admin.register_tag_handler("srv", this.srv_tag_handler);
+ $(document).bind('domain_listing_refresh', this.listing_refresh_cb);
+ },
+
+ listing_refresh_cb: function() {
+ $("a[name=delrelaydomain]").confirm({
+ question: function() { return this.$element.attr('title'); },
+ method: "DELETE",
+ success_cb: $.proxy(admin.reload_listing, admin)
+ });
},
launch_scan: function(e) {
@@ -54,7 +63,7 @@ RelayDomains.prototype = {
$('input:text:visible:first').focus();
$("#id_aliases").dynamic_input();
$(".submit").on('click', $.proxy(function(e) {
- simple_ajax_form_post2(e, {
+ simple_ajax_form_post(e, {
formid: "rdomform",
reload_on_success: false,
success_cb: $.proxy(admin.reload_listing, admin)
diff --git a/modoboa/extensions/postfix_relay_domains/tests.py b/modoboa/extensions/postfix_relay_domains/tests.py
index 501deb776..dab97161c 100644
--- a/modoboa/extensions/postfix_relay_domains/tests.py
+++ b/modoboa/extensions/postfix_relay_domains/tests.py
@@ -1,16 +1,13 @@
-"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
-
-Replace this with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
+from modoboa.lib.tests import ExtTestCase
+from modoboa.extensions.admin import factories
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.assertEqual(1 + 1, 2)
+class RelayDomainsTestCase(ExtTestCase):
+ fixtures = ["initial_users.json"]
+ name = "postfix_relay_domains"
+
+ def setUp(self):
+ super(RelayDomainsTestCase, self).setUp()
+ factories.populate_database()
+
+
diff --git a/modoboa/extensions/postfix_relay_domains/views.py b/modoboa/extensions/postfix_relay_domains/views.py
index 0a4281d0d..5eded6ca3 100644
--- a/modoboa/extensions/postfix_relay_domains/views.py
+++ b/modoboa/extensions/postfix_relay_domains/views.py
@@ -4,24 +4,20 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import (
- login_required, permission_required, user_passes_test
+ login_required, permission_required
)
from modoboa.lib import events
-from modoboa.lib.exceptions import ModoboaException, PermDeniedException
+from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.webutils import render_to_json_response
from .models import RelayDomain, Service
from .forms import RelayDomainForm
@login_required
-@permission_required("admin.add_relaydomain")
+@permission_required("postfix_relay_domains.add_relaydomain")
@transaction.commit_on_success
def create(request, tplname="postfix_relay_domains/relaydomain_form.html"):
- commonctx = {"title": _("New relay domain"),
- "action_label": _("Create"),
- "action_classes": "submit",
- "action": reverse(create),
- "formid": "rdomform"}
+ events.raiseEvent("CanCreate", request.user, "relay_domains")
if request.method == 'POST':
form = RelayDomainForm(request.POST)
if form.is_valid():
@@ -30,13 +26,21 @@ def create(request, tplname="postfix_relay_domains/relaydomain_form.html"):
return render_to_json_response(
{"respmsg": _("Relay domain created")}
)
- return render_to_json_response(form.errors, status=400)
- commonctx['form'] = RelayDomainForm()
- return render(request, tplname, commonctx)
+ return render_to_json_response(
+ {'form_errors': form.errors}, status=400
+ )
+
+ ctx = {"title": _("New relay domain"),
+ "action_label": _("Create"),
+ "action_classes": "submit",
+ "action": reverse(create),
+ "formid": "rdomform",
+ "form": RelayDomainForm()}
+ return render(request, tplname, ctx)
@login_required
-@permission_required("admin.change_relaydomain")
+@permission_required("postfix_relay_domains.change_relaydomain")
def edit(request, rdom_id, tplname='postfix_relay_domains/relaydomain_form.html'):
rdom = RelayDomain.objects.get(pk=rdom_id)
if not request.user.can_access(rdom):
@@ -46,10 +50,11 @@ def edit(request, rdom_id, tplname='postfix_relay_domains/relaydomain_form.html'
if form.is_valid():
form.save(request.user)
events.raiseEvent('RelayDomainModified', rdom)
- return render_to_json_response(
- {'respmsg': _('Relay domain modified')}
- )
- return render_to_json_response(form.errors, status=400)
+ return render_to_json_response(_('Relay domain modified'))
+
+ return render_to_json_response(
+ {'form_errors': form.errors}, status=400
+ )
ctx = {
'action': reverse(edit, args=[rdom.id]),
'formid': 'rdomform',
@@ -62,19 +67,17 @@ def edit(request, rdom_id, tplname='postfix_relay_domains/relaydomain_form.html'
@login_required
-@permission_required("admin.delete_relaydomain")
+@permission_required("postfix_relay_domains.delete_relaydomain")
def delete(request, rdom_id):
rdom = RelayDomain.objects.get(pk=rdom_id)
if not request.user.can_access(rdom):
raise PermDeniedException
rdom.delete()
- return render_to_json_response({
- 'status': 'ok', 'respmsg': _('Relay domain deleted')
- })
+ return render_to_json_response(_('Relay domain deleted'))
@login_required
-@user_passes_test(lambda u: u.is_superuser)
+@permission_required("postfix_relay_domains.add_service")
@require_http_methods(["POST"])
def scan_for_services(request):
try:
diff --git a/modoboa/extensions/sievefilters/lib.py b/modoboa/extensions/sievefilters/lib.py
index 3cd57d338..6d6b7e1ae 100644
--- a/modoboa/extensions/sievefilters/lib.py
+++ b/modoboa/extensions/sievefilters/lib.py
@@ -5,11 +5,12 @@ from sievelib.managesieve import Client, Error
from sievelib.parser import Parser
from sievelib.factory import FiltersSet
from modoboa.lib import parameters
+from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.connections import ConnectionsManager, ConnectionError
-class SieveClientError(Exception):
- pass
+class SieveClientError(ModoboaException):
+ http_code = 424
class SieveClient(object):
diff --git a/modoboa/extensions/sievefilters/static/sievefilters/js/lib.js b/modoboa/extensions/sievefilters/static/sievefilters/js/lib.js
index 7854c0c13..3c8336806 100644
--- a/modoboa/extensions/sievefilters/static/sievefilters/js/lib.js
+++ b/modoboa/extensions/sievefilters/static/sievefilters/js/lib.js
@@ -18,15 +18,13 @@ function removefilter(evt) {
$.ajax({
url: $this.attr("href"),
- dataType: 'json',
- success: function(response) {
- if (response.status == "ok") {
- history.update(true);
- $("body").notify('success', response.respmsg, 2000);
- } else {
- $("body").notify('error', response.respmsg);
- }
- }
+ dataType: 'json'
+ }).done(function(response) {
+ history.update(true);
+ $("body").notify('success', response, 2000);
+ }).fail(function(jqxhr) {
+ var response = $.parseJSON(jqxhr.responseText);
+ $("body").notify('error', response);
});
}
@@ -181,16 +179,14 @@ function toggle_filter_state(event) {
event.preventDefault();
$.ajax({
url: $this.attr("href"),
- dataType: 'json',
- success: function(response) {
- if (response.status == "ok") {
- $this.html(response.label);
- $this.attr("class", "");
- $this.addClass(response.color);
- } else {
- $("body").notify('error', response.respmsg);
- }
- }
+ dataType: 'json'
+ }).done(function(response) {
+ $this.html(response.label);
+ $this.attr("class", "");
+ $this.addClass(response.color);
+ }).fail(function(jqhxr) {
+ var response = $.parseJSON(jqxhr.responseText);
+ $("body").notify('error', response);
});
}
@@ -200,11 +196,13 @@ function move_filter(event) {
event.preventDefault();
$.ajax({
url: $this.attr('href'),
- dataType: 'json',
- success: function(response) {
- $("#set_content").html(response.content);
- init_filters_list();
- }
+ dataType: 'json'
+ }).done(function(response) {
+ $("#set_content").html(response.content);
+ init_filters_list();
+ }).fail(function(jqxhr) {
+ var response = $.parseJSON(jqxhr.responseText);
+ $("body").notify('error', response);
});
}
@@ -234,11 +232,10 @@ function filtersetform_cb() {
$("#newfiltersset").find("input").keypress(function(e) {
if (e.which == 13) e.preventDefault();
});
- $(".submit").one('click', function(e) {
+ $(".submit").on('click', function(e) {
simple_ajax_form_post(e, {
formid: "newfiltersset",
reload_on_success: false,
- error_cb: filtersetform_cb,
success_cb: filterset_created
});
});
diff --git a/modoboa/extensions/sievefilters/templates/sievefilters/index.html b/modoboa/extensions/sievefilters/templates/sievefilters/index.html
index 2430cafdc..817d6fd51 100644
--- a/modoboa/extensions/sievefilters/templates/sievefilters/index.html
+++ b/modoboa/extensions/sievefilters/templates/sievefilters/index.html
@@ -34,11 +34,11 @@ function filterform_cb() {
}
});
- $(".submit").one('click', function(e) {
+ $(".submit").on('click', function(e) {
var counters = "conds=" + $dc.fields_cnt + "&actions=" + $da.fields_cnt;
simple_ajax_form_post(e, {
- error_cb: filterform_cb,
+ formid: 'filterform',
reload_mode: 'history',
extradata: counters
});
diff --git a/modoboa/extensions/sievefilters/views.py b/modoboa/extensions/sievefilters/views.py
index 31fce1b29..0352a23d4 100644
--- a/modoboa/extensions/sievefilters/views.py
+++ b/modoboa/extensions/sievefilters/views.py
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from modoboa.lib import parameters
from modoboa.lib.webutils import _render_error, \
- ajax_response, ajax_simple_response
+ ajax_response, render_to_json_response
from modoboa.lib.connections import ConnectionError
from modoboa.extensions.admin.lib import needs_mailbox
from .lib import SieveClient, SieveClientError
@@ -54,8 +54,12 @@ def index(request, tplname="sievefilters/index.html"):
@needs_mailbox()
def get_templates(request, ftype):
if ftype == "condition":
- return ajax_simple_response(FilterForm([], [], request).cond_templates)
- return ajax_simple_response(FilterForm([], [], request).action_templates)
+ return render_to_json_response(
+ FilterForm([], [], request).cond_templates
+ )
+ return render_to_json_response(
+ FilterForm([], [], request).action_templates
+ )
@login_required
@@ -87,8 +91,8 @@ def getfs(request, name):
menu = '' % \
(_("Actions"), fset_menu(editormode, name))
- resp = dict(status="ok", menu=menu, content=htmlcontent)
- return ajax_simple_response(resp)
+ resp = dict(menu=menu, content=htmlcontent)
+ return render_to_json_response(resp)
def build_filter_ctx(ctx, form):
@@ -119,13 +123,12 @@ def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None
fset.updatefilter(
oldname, fltname, conditions, actions, match_type
)
- try:
- sc.pushscript(fset.name, str(fset))
- except SieveClientError as e:
- return ajax_response(request, "ko", respmsg=str(e))
- return ajax_response(request, respmsg=okmsg, ajaxnav=True)
- tplctx = build_filter_ctx(tplctx, form)
- return ajax_response(request, status="ko", template=tplname, **tplctx)
+ sc.pushscript(fset.name, str(fset))
+ return render_to_json_response(okmsg)
+ #tplctx = build_filter_ctx(tplctx, form)
+ #return ajax_response(request, status="ko", template=tplname, **tplctx)
+
+ return render_to_json_response({'form_errors': form.errors}, status=400)
@login_required
@@ -186,8 +189,8 @@ def removefilter(request, setname, fname):
fset = sc.getscript(setname, format="fset")
if fset.removefilter(fname.encode("utf-8")):
sc.pushscript(fset.name, str(fset))
- return ajax_response(request, respmsg=_("Filter removed"))
- return ajax_response(request, "ko", respmsg=_("Failed to remove filter"))
+ return render_to_json_response(_("Filter removed"))
+ return render_to_json_response(_("Failed to remove filter"), status=500)
@login_required
@@ -208,37 +211,28 @@ def savefs(request, name):
@login_required
@needs_mailbox()
def new_filters_set(request, tplname="common/generic_modal_form.html"):
+ if request.method == "POST":
+ form = FiltersSetForm(request.POST)
+ if form.is_valid():
+ sc = SieveClient(user=request.user.username,
+ password=request.session["password"])
+ sc.pushscript(form.cleaned_data["name"], "# Empty script",
+ form.cleaned_data["active"])
+ return render_to_json_response({
+ "url": form.cleaned_data["name"],
+ "active": form.cleaned_data["active"],
+ "respmsg": _("Filters set created")
+ })
+ return render_to_json_response({'form_errors': form.errors}, status=400)
+
ctx = {"title": _("Create a new filters set"),
"formid": "newfiltersset",
"action_label": _("Create"),
"action_classes": "submit",
"action": reverse(new_filters_set),
"withmenu": False,
- "withunseen": False}
- if request.method == "POST":
- form = FiltersSetForm(request.POST)
- error = None
- if form.is_valid():
- sc = SieveClient(user=request.user.username,
- password=request.session["password"])
- try:
- sc.pushscript(form.cleaned_data["name"], "# Empty script",
- form.cleaned_data["active"])
- except SieveClientError, e:
- error = str(e)
- else:
- return ajax_simple_response({
- "status": "ok",
- "url": form.cleaned_data["name"],
- "active": form.cleaned_data["active"],
- "respmsg": _("Filters set created")
- })
-
- ctx["form"] = form
- ctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **ctx)
-
- ctx["form"] = FiltersSetForm()
+ "withunseen": False,
+ "form": FiltersSetForm()}
return render(request, tplname, ctx)
@@ -247,13 +241,10 @@ def new_filters_set(request, tplname="common/generic_modal_form.html"):
def remove_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
- try:
- sc.deletescript(name)
- except SieveClientError, e:
- return ajax_simple_response(dict(status="ko", respmsg=str(e)))
+ sc.deletescript(name)
acs, scripts = sc.listscripts()
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Filters set deleted"), newfs=acs
+ return render_to_json_response(dict(
+ respmsg=_("Filters set deleted"), newfs=acs
))
@@ -293,24 +284,19 @@ def toggle_filter_state(request, setname, fname):
password=request.session["password"])
if type(fname) is unicode:
fname = fname.encode("utf-8")
- try:
- fset = sc.getscript(setname, format="fset")
- if fset.is_filter_disabled(fname):
- ret = fset.enablefilter(fname)
- newstate = _("yes")
- color = "green"
- else:
- ret = fset.disablefilter(fname)
- newstate = _("no")
- color = "red"
- if not ret:
- pass
- sc.pushscript(setname, str(fset))
- except SieveClientError, e:
- return ajax_response(request, "ko", respmsg=str(e))
-
- return ajax_simple_response({
- "status": "ok",
+ fset = sc.getscript(setname, format="fset")
+ if fset.is_filter_disabled(fname):
+ ret = fset.enablefilter(fname)
+ newstate = _("yes")
+ color = "green"
+ else:
+ ret = fset.disablefilter(fname)
+ newstate = _("no")
+ color = "red"
+ if not ret:
+ pass
+ sc.pushscript(setname, str(fset))
+ return render_to_json_response({
"label": newstate,
"color": color
})
@@ -319,12 +305,9 @@ def toggle_filter_state(request, setname, fname):
def move_filter(request, setname, fname, direction):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
- try:
- fset = sc.getscript(setname, format="fset")
- fset.movefilter(fname.encode("utf-8"), direction)
- sc.pushscript(setname, str(fset))
- except (SieveClientError), e:
- return ajax_response(request, "ko", respmsg=str(e))
+ fset = sc.getscript(setname, format="fset")
+ fset.movefilter(fname.encode("utf-8"), direction)
+ sc.pushscript(setname, str(fset))
return ajax_response(
request, template="sievefilters/guieditor.html", fs=fset
)
diff --git a/modoboa/extensions/webmail/forms.py b/modoboa/extensions/webmail/forms.py
index fb0ba3729..aa6a0bb04 100644
--- a/modoboa/extensions/webmail/forms.py
+++ b/modoboa/extensions/webmail/forms.py
@@ -7,13 +7,20 @@ class ComposeMailForm(forms.Form):
to = forms.CharField(label=_("To"))
cc = forms.CharField(label=_("Cc"), required=False)
cci = forms.CharField(label=_("Cci"), required=False)
- subject = forms.CharField(label=_("Subject"), max_length=255, required=False)
+ subject = forms.CharField(
+ label=_("Subject"), max_length=255, required=False
+ )
+ origmsgid = forms.CharField(
+ label="", widget=forms.HiddenInput(), required=False
+ )
- origmsgid = forms.CharField(label="", widget=forms.HiddenInput(), required=False)
class FolderForm(forms.Form):
- oldname = forms.CharField(label="", widget=forms.HiddenInput(), required=False)
+ oldname = forms.CharField(
+ label="", widget=forms.HiddenInput(), required=False
+ )
name = forms.CharField()
+
class AttachmentForm(forms.Form):
attachment = forms.FileField(label=_("Select a file"))
diff --git a/modoboa/extensions/webmail/static/webmail/js/webmail.js b/modoboa/extensions/webmail/static/webmail/js/webmail.js
index 8ddd4c5b8..829c477ac 100644
--- a/modoboa/extensions/webmail/static/webmail/js/webmail.js
+++ b/modoboa/extensions/webmail/static/webmail/js/webmail.js
@@ -893,13 +893,12 @@ Webmail.prototype = {
$("#mboxform").find("input").keypress(function(e) {
if (e.which == 13) e.preventDefault();
});
- $(".submit").one('click', $.proxy(function(e) {
+ $(".submit").on('click', $.proxy(function(e) {
var $link = $("#folders2 li.active").children("a");
simple_ajax_form_post(e, {
reload_on_success: false,
formid: "mboxform",
- error_cb: this.mboxform_cb,
extradata: ($link.length) ? "parent_folder=" + $link.attr("href") : "",
success_cb: $.proxy(this.mboxform_success, this)
});
@@ -907,6 +906,7 @@ Webmail.prototype = {
},
mboxform_success: function(data) {
+ $("body").notify('success', data.respmsg, 2000);
if (data.oldmb === undefined) {
this.add_mailbox_to_tree(data.parent, data.newmb);
} else if (data.newmb) {
diff --git a/modoboa/extensions/webmail/views.py b/modoboa/extensions/webmail/views.py
index 4c7c29257..5291f8a7a 100644
--- a/modoboa/extensions/webmail/views.py
+++ b/modoboa/extensions/webmail/views.py
@@ -12,7 +12,8 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.gzip import gzip_page
from modoboa.lib import parameters
from modoboa.lib.webutils import (
- _render_to_string, ajax_response, ajax_simple_response
+ _render_to_string, ajax_response, ajax_simple_response,
+ render_to_json_response
)
from modoboa.extensions.admin.lib import needs_mailbox
from .exceptions import WebmailError
@@ -132,6 +133,19 @@ def compact(request, name):
def newfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
+
+ if request.method == "POST":
+ form = FolderForm(request.POST)
+ if form.is_valid():
+ pf = request.POST.get("parent_folder", None)
+ mbc.create_folder(form.cleaned_data["name"], pf)
+ return render_to_json_response({
+ 'respmsg': _("Mailbox created"),
+ 'newmb': form.cleaned_data["name"], 'parent': pf
+ })
+
+ return render_to_json_response({'form_errors': form.errors}, status=400)
+
ctx = {"title": _("Create a new mailbox"),
"formid": "mboxform",
"action": reverse(newfolder),
@@ -140,24 +154,9 @@ def newfolder(request, tplname="webmail/folder.html"):
"withunseen": False,
"selectonly": True,
"mboxes": mbc.getmboxes(request.user),
- "hdelimiter": mbc.hdelimiter}
-
- if request.method == "POST":
- form = FolderForm(request.POST)
- if form.is_valid():
- pf = request.POST.get("parent_folder", None)
- mbc.create_folder(form.cleaned_data["name"], pf)
- return ajax_simple_response(dict(
- status="ok", respmsg=_("Mailbox created"),
- newmb=form.cleaned_data["name"], parent=pf
- ))
-
- ctx["form"] = form
- ctx["selected"] = None
- return ajax_response(request, status="ko", template=tplname, **ctx)
-
- ctx["form"] = FolderForm()
- ctx["selected"] = None
+ "hdelimiter": mbc.hdelimiter,
+ "form": FolderForm(),
+ "selected": None}
return render(request, tplname, ctx)
@@ -179,9 +178,10 @@ def editfolder(request, tplname="webmail/folder.html"):
form = FolderForm(request.POST)
if form.is_valid():
pf = request.POST.get("parent_folder", None)
- ctx["selected"] = pf
- oldname, oldparent = separate_mailbox(request.POST["oldname"], sep=mbc.hdelimiter)
- res = dict(status="ok", respmsg=_("Mailbox updated"))
+ oldname, oldparent = separate_mailbox(
+ request.POST["oldname"], sep=mbc.hdelimiter
+ )
+ res = {'respmsg': _("Mailbox updated")}
if form.cleaned_data["name"] != oldname \
or (pf != oldparent):
newname = form.cleaned_data["name"] if pf is None \
@@ -193,21 +193,27 @@ def editfolder(request, tplname="webmail/folder.html"):
res["newparent"] = pf
if "mbox" in request.session:
del request.session["mbox"]
- return ajax_simple_response(res)
+ return render_to_json_response(res)
- ctx["mboxes"] = mbc.getmboxes(request.user)
- ctx["form"] = form
- return ajax_response(request, status="ko", template=tplname, **ctx)
+ return render_to_json_response({'form_errors': form.errors}, status=400)
name = request.GET.get("name", None)
if name is None:
raise WebmailError(_("Invalid request"))
shortname, parent = separate_mailbox(name, sep=mbc.hdelimiter)
- ctx["mboxes"] = mbc.getmboxes(request.user, until_mailbox=parent)
- ctx["form"] = FolderForm()
+ ctx = {"title": _("Edit mailbox"),
+ "formid": "mboxform",
+ "action": reverse(editfolder),
+ "action_label": _("Update"),
+ "action_classes": "submit",
+ "withunseen": False,
+ "selectonly": True,
+ "hdelimiter": mbc.hdelimiter,
+ "mboxes": mbc.getmboxes(request.user, until_mailbox=parent),
+ "form": FolderForm(),
+ "selected": parent}
ctx["form"].fields["oldname"].initial = name
ctx["form"].fields["name"].initial = shortname
- ctx["selected"] = parent
return render(request, tplname, ctx)
@@ -377,11 +383,11 @@ def render_compose(request, form, posturl, email=None, insert_signature=False):
randid = set_compose_session(request)
attachments = request.session["compose_mail"]["attachments"]
- if len(attachments):
- short_att_list = "(%s)" \
- % ", ".join(map(lambda att: att["fname"],
- attachments[:2] + [{"fname": "..."}] \
- if len(attachments) > 2 else attachments))
+ if attachments:
+ short_att_list = "(%s)" % ", ".join(
+ [att['fname'] for att in (attachments[:2] + [{"fname": "..."}]
+ if len(attachments) > 2 else attachments)]
+ )
else:
short_att_list = ""
content = _render_to_string(request, "webmail/compose.html", {
diff --git a/modoboa/lib/events.py b/modoboa/lib/events.py
index 66aa23270..b2e778bbb 100644
--- a/modoboa/lib/events.py
+++ b/modoboa/lib/events.py
@@ -91,7 +91,7 @@ class observe(object):
return []
else:
if not ext.enabled:
- return []
+ return None
elif not modname in settings.MODOBOA_APPS:
return []
return f(*args, **kwargs)
@@ -144,7 +144,12 @@ def raiseQueryEvent(event, *args, **kwargs):
if not event in events or not event in callbacks.keys():
return result
for callback in callbacks[event].values():
- result += callback(*args, **kwargs)
+ tmp = callback(*args, **kwargs)
+ if tmp is None:
+ # Callback is registered but associated extension is
+ # disabled.
+ continue
+ result += tmp
return result
@@ -158,10 +163,14 @@ def raiseDictEvent(event, *args):
:return: a dictionnary
"""
result = {}
- if not event in events or not event in callbacks.keys():
+ if not event in events or not event in callbacks:
return result
for callback in callbacks[event].values():
tmp = callback(*args)
+ if tmp is None:
+ # Callback is registered but associated extension is
+ # disabled.
+ continue
for k, v in tmp.iteritems():
result[k] = v
return result
diff --git a/modoboa/lib/exceptions.py b/modoboa/lib/exceptions.py
index 026b72bd7..82c1135f0 100644
--- a/modoboa/lib/exceptions.py
+++ b/modoboa/lib/exceptions.py
@@ -8,10 +8,57 @@ from django.utils.translation import ugettext as _
class ModoboaException(Exception):
- pass
+ """
+ Base class for Modoboa custom exceptions.
+ """
+ http_code = None
+
+ def __init__(self, *args, **kwargs):
+ if 'http_code' in kwargs:
+ self.http_code = kwargs['http_code']
+ del kwargs['http_code']
+ super(ModoboaException, self).__init__(*args, **kwargs)
+
+
+class InternalError(ModoboaException):
+ """
+ Use this exception for system errors, missing dependencies, etc.
+ """
+ http_code = 500
+
+
+class BadRequest(ModoboaException):
+ """
+ Use this exception when received data doesn't validate a specific
+ format (example: wrong CSV line) or doesn't respect validation
+ rules.
+ """
+ http_code = 400
+
+
+class NotFound(ModoboaException):
+ """
+ Use this exception to indicate the requested resource could not be
+ found.
+ """
+ http_code = 404
+
+
+class Conflict(ModoboaException):
+ """
+ Use this exception to indicate that the request could not be
+ processed because of conflict in the request.
+ """
+ http_code = 409
class PermDeniedException(ModoboaException):
+ """
+ Use this exception when a user tries to do something he is not
+ allowed to.
+ """
+ http_code = 403
+
def __init__(self, msg=None):
self.msg = msg
diff --git a/modoboa/lib/formutils.py b/modoboa/lib/formutils.py
index fa4e62cb3..f74fbec32 100644
--- a/modoboa/lib/formutils.py
+++ b/modoboa/lib/formutils.py
@@ -10,10 +10,20 @@ from django.utils.html import conditional_escape
class CreationWizard(object):
- def __init__(self, done_cb):
+ def __init__(self, done_cb=None):
self.steps = []
self.done_cb = done_cb
+ @property
+ def errors(self):
+ result = {}
+ for step in self.steps:
+ for name, value in step['form'].errors.items():
+ if name == '__all__':
+ continue
+ result[name] = value
+ return result
+
def add_step(self, cls, title, buttons, formtpl=None, new_args=None):
self.steps += [dict(cls=cls, title=title, buttons=buttons,
formtpl=formtpl, new_args=new_args)]
@@ -33,19 +43,23 @@ class CreationWizard(object):
return -1, _("Invalid request")
stepid = stepid.replace("step", "")
- stepid = int(stepid) - 1
- if stepid < 0 or stepid >= len(self.steps):
+ stepid = int(stepid)
+ if stepid < 0 or stepid > len(self.steps):
return -1, _("Invalid request")
self.create_forms(request.POST)
- if self.steps[stepid]["form"].is_valid():
- if stepid == len(self.steps) - 1:
+ statuses = []
+ for cpt in xrange(0, stepid):
+ statuses.append(self.steps[cpt]["form"].is_valid())
+ if False in statuses:
+ return 0, stepid
+ if stepid == len(self.steps):
+ if self.done_cb is not None:
self.done_cb(self.steps)
- return 2, None
- return 1, stepid
- return 0, stepid
+ return 2, None
+ return 1, stepid
def get_title(self, stepid):
- return "%d. %s" % (stepid + 1, self.steps[stepid]["title"])
+ return "%d. %s" % (stepid, self.steps[stepid - 1]["title"])
class DynamicForm(object):
@@ -109,6 +123,16 @@ class TabForms(object):
def _before_is_valid(self, form):
return True
+ @property
+ def errors(self):
+ result = {}
+ for f in self.forms:
+ for name, value in f['instance'].errors.items():
+ if name == '__all__':
+ continue
+ result[name] = value
+ return result
+
def is_valid(self, mandatory_only=False, optional_only=False):
to_remove = []
for f in self.forms:
diff --git a/modoboa/lib/middleware.py b/modoboa/lib/middleware.py
index 7b7fc1f15..44f08dd07 100644
--- a/modoboa/lib/middleware.py
+++ b/modoboa/lib/middleware.py
@@ -3,7 +3,9 @@ import re
from django.http import Http404, HttpResponseRedirect
from modoboa.core.models import Extension
from modoboa.core.extensions import exts_pool
-from modoboa.lib.webutils import _render_error, ajax_response
+from modoboa.lib.webutils import (
+ _render_error, ajax_response, render_to_json_response
+)
from modoboa.lib.exceptions import ModoboaException
@@ -41,6 +43,10 @@ class CommonExceptionCatcher(object):
return _render_error(
request, user_context=dict(error=str(exception))
)
- return ajax_response(
- request, status="ko", respmsg=unicode(exception), norefresh=True
+ if exception.http_code is None:
+ return ajax_response(
+ request, status="ko", respmsg=unicode(exception), norefresh=True
+ )
+ return render_to_json_response(
+ unicode(exception), status=exception.http_code
)
diff --git a/modoboa/lib/parameters.py b/modoboa/lib/parameters.py
index 97a3ab270..61d3372d7 100644
--- a/modoboa/lib/parameters.py
+++ b/modoboa/lib/parameters.py
@@ -11,9 +11,9 @@ Only super users will be able to access this part of the web interface.
import inspect
import re
from django import forms
+from . import events
from exceptions import ModoboaException
-
_params = {'A': {}, 'U': {}}
@@ -28,7 +28,12 @@ class NotDefined(ModoboaException):
class GenericParametersForm(forms.Form):
+ """Base class for parameter forms.
+
+ Each extension has the possibility to define global parameters.
+ """
app = None
+ visibility_rules = None
def __init__(self, *args, **kwargs):
if self.app is None:
@@ -38,10 +43,12 @@ class GenericParametersForm(forms.Form):
super(GenericParametersForm, self).__init__(*args, **kwargs)
self.visirules = {}
- if hasattr(self, "visibility_rules"):
+ if self.visibility_rules is not None:
for key, rule in self.visibility_rules.items():
field, value = rule.split("=")
- visibility = {"field": "id_%s-%s" % (self.app, field), "value": value}
+ visibility = {
+ "field": "id_%s-%s" % (self.app, field), "value": value
+ }
self.visirules["%s-%s" % (self.app, key)] = visibility
if not args:
@@ -50,6 +57,11 @@ class GenericParametersForm(forms.Form):
def _load_initial_values(self):
raise NotImplementedError
+ def _load_extra_parameters(self, level):
+ params = events.raiseDictEvent('GetExtraParameters', self.app, level)
+ for pname, pdef in params.items():
+ self.fields[pname] = pdef
+
def _save_parameter(self, p, name, value):
if p.value == value:
return
@@ -101,6 +113,7 @@ class AdminParametersForm(GenericParametersForm):
pass
return values
+
class UserParametersForm(GenericParametersForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user") if "user" in kwargs else None
diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py
index ce5dcb9d7..8b93a2d8c 100644
--- a/modoboa/lib/permissions.py
+++ b/modoboa/lib/permissions.py
@@ -18,7 +18,9 @@ def get_account_roles(user):
std_roles = [("SimpleUsers", _("Simple user"))]
if user.is_superuser:
std_roles += [("SuperAdmins", _("Super administrator"))]
- if user.has_perm("admin.add_domain"):
+ filters = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
+ if user.has_perm("admin.add_domain") and \
+ (not filters or True in filters):
std_roles += [("DomainAdmins", _("Domain administrator"))]
std_roles += events.raiseQueryEvent("GetExtraRoles", user)
return sorted(std_roles, key=lambda role: role[1])
diff --git a/modoboa/lib/singleton.py b/modoboa/lib/singleton.py
new file mode 100644
index 000000000..3fd8f0c17
--- /dev/null
+++ b/modoboa/lib/singleton.py
@@ -0,0 +1,15 @@
+
+
+# works in Python 2 & 3
+class _Singleton(type):
+ _instances = {}
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ cls._instances[cls] = \
+ super(_Singleton, cls).__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+class Singleton(_Singleton('SingletonMeta', (object,), {})):
+ pass
diff --git a/modoboa/lib/tests.py b/modoboa/lib/tests.py
index 0a0330294..8601f8682 100644
--- a/modoboa/lib/tests.py
+++ b/modoboa/lib/tests.py
@@ -13,20 +13,17 @@ class ModoTestCase(TestCase):
self.clt = Client()
self.assertEqual(self.clt.login(username=username, password=password), True)
- def check_ajax_request(self, method, url, params, status="ok", **kwargs):
+ def ajax_request(self, method, url, params, status=200):
response = getattr(self.clt, method) \
(url, params, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(response.status_code, 200)
- obj = simplejson.loads(response.content)
- self.assertEqual(obj["status"], status)
- for kw, v in kwargs.items():
- self.assertEqual(obj[kw], v)
+ self.assertEqual(response.status_code, status)
+ return simplejson.loads(response.content)
- def check_ajax_post(self, *args, **kwargs):
- self.check_ajax_request("post", *args, **kwargs)
+ def ajax_post(self, *args, **kwargs):
+ return self.ajax_request('post', *args, **kwargs)
- def check_ajax_get(self, *args, **kwargs):
- self.check_ajax_request("get", *args, **kwargs)
+ def ajax_get(self, *args, **kwargs):
+ return self.ajax_request('get', *args, **kwargs)
class ExtTestCase(ModoTestCase):
diff --git a/modoboa/static/js/confirmation.js b/modoboa/static/js/confirmation.js
index 59bd9314d..3d6296be9 100644
--- a/modoboa/static/js/confirmation.js
+++ b/modoboa/static/js/confirmation.js
@@ -87,29 +87,25 @@
}
params += $(this).attr("name") + "=true";
});
-
$.ajax({
cache: false,
method: this.options.method,
data: params,
- url: this.$element.attr('href'),
- complete: $.proxy(function(XMLHttpRequest, textStatus) {
- var data;
- try {
- data = $.parseJSON(XMLHttpRequest.responseText);
- } catch (e) {
- data = { status: 'ko', respmsg: gettext('Internal Error') };
- }
- if (data.status == "ok") {
- if (this.options.success_cb != undefined) {
- this.options.success_cb(data);
- return;
- }
- window.location.reload();
- } else {
- $("body").notify("error", data.respmsg);
- }
- }, this)
+ url: this.$element.attr('href')
+ }).done($.proxy(function(data) {
+ if (this.options.success_cb != undefined) {
+ this.options.success_cb(data);
+ return;
+ }
+ window.location.reload();
+ }, this)).fail(function(jqxhr) {
+ var data;
+ try {
+ data = $.parseJSON(jqxhr.responseText);
+ } catch (e) {
+ data = gettext('Internal Error');
+ }
+ $("body").notify("error", data);
});
this.box.remove();
}
diff --git a/modoboa/static/js/creation_wizard.js b/modoboa/static/js/creation_wizard.js
index 25c4d2cce..a04e4bb82 100644
--- a/modoboa/static/js/creation_wizard.js
+++ b/modoboa/static/js/creation_wizard.js
@@ -12,6 +12,7 @@
this.$element.carousel({pause: true});
this.$element.carousel("pause");
this.listen();
+ this.titles = {};
$('input:text:visible:first').focus();
},
@@ -21,16 +22,28 @@
$(".submit").on("click", $.proxy(this.submit, this));
},
+ get_current_step_id: function() {
+ return $(".item.active").attr("id");
+ },
+
+ get_current_title: function() {
+ return $(".modal-header").find("small").html();
+ },
+
+ set_current_title: function(title) {
+ $(".modal-header").find("small").html(title);
+ },
+
post: function(last) {
var $form = (this.options.formid) ? $('#' + this.options.formid) : $('form');
- var data = $form.serialize() + "&stepid=" + $(".item.active").attr("id");
+ var data = $form.serialize() + "&stepid=" + this.get_current_step_id();
- $.post($form.attr("action"), data, $.proxy(function(resp) {
- if (resp.status == "ok") {
+ $.ajax({type: 'POST', url: $form.attr("action"), data: data})
+ .done($.proxy(function(resp) {
if (!last) {
var stepid = resp.stepid + 1;
$('input:text:visible:first').focus();
- $(".modal-header").find("small").html(resp.title);
+ this.set_current_title(resp.title);
if (this.options.transition_callbacks[stepid] != undefined) {
this.options.transition_callbacks[stepid]();
}
@@ -43,20 +56,23 @@
window.location.reload();
}
}
- return;
- }
- if (resp.stepid != undefined && resp.form != undefined) {
- var stepid = resp.stepid + 1;
- $("#step" + stepid).html(resp.form);
- if (this.options.error_callbacks[stepid] != undefined) {
- this.options.error_callbacks[stepid]();
+ }, this))
+ .fail($.proxy(function(jqxhr) {
+ var resp = $.parseJSON(jqxhr.responseText);
+ if (resp.stepid != undefined) {
+ var stepid = resp.stepid;
+ display_form_errors("step" + stepid, resp);
+ if (this.options.error_callbacks[stepid] != undefined) {
+ this.options.error_callbacks[stepid]();
+ }
+ $('input:text:visible:first').focus();
+ if (resp.respmsg) {
+ $(".modal-body").prepend(build_error_alert(resp.respmsg));
+ }
+ return;
}
- $('input:text:visible:first').focus();
- }
- if (resp.respmsg) {
- $(".modal-body").prepend(build_error_alert(resp.respmsg));
- }
- }, this));
+ $(".modal-body").prepend(build_error_alert(resp));
+ }, this));
},
update_buttons: function() {
@@ -66,6 +82,8 @@
next: function(evt) {
evt.preventDefault();
+ var step_id = this.get_current_step_id();
+ this.titles[step_id] = this.get_current_title();
this.$element.on('slid', this.update_buttons);
this.post(false);
},
@@ -74,6 +92,7 @@
evt.preventDefault();
this.$element.on('slid', this.update_buttons);
this.$element.carousel('prev');
+ this.set_current_title(this.titles[this.get_current_step_id()]);
},
submit: function(evt) {
@@ -98,7 +117,8 @@
};
$.fn.cwizard.defaults = {
- transition_callbacks: {}
+ transition_callbacks: {},
+ error_callbacks: {}
};
})(jQuery);
\ No newline at end of file
diff --git a/modoboa/static/js/global.js b/modoboa/static/js/global.js
index 1016c0672..5b28f076b 100644
--- a/modoboa/static/js/global.js
+++ b/modoboa/static/js/global.js
@@ -31,8 +31,11 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
$(href).modal('open');
return;
}
- $.get(href, function(data) {
+ $.ajax({type: "GET", url: href}).done(function(data) {
if (typeof data === "object") {
+ /*
+ * Compatibility with the old way...
+ */
if (data.status == "ko" && data.respmsg) {
$("body").notify("error", data.respmsg);
}
@@ -67,6 +70,9 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
if (css != undefined) {
$div.css(css);
}
+ }).fail(function(jqxhr) {
+ var data = $.parseJSON(jqxhr.responseText);
+ $("body").notify("error", data);
});
}
@@ -111,57 +117,46 @@ if (!Object.keys) {
}
/*
- * Simple function that sends a form using an 'ajax' post request.
- *
- * The function is intended to be used in a modal environment.
+ * Clean all errors in a given form.
*/
-function simple_ajax_form_post(e, options) {
- e.preventDefault();
- var $form = (options.formid !== undefined) ? $("#" + options.formid) : $("form");
- var defaults = {reload_on_success: true, reload_mode: 'full', modal: true};
- var opts = $.extend({}, defaults, options);
- var args = $form.serialize();
+function clean_form_errors(formid) {
+ $("#" + formid + " div.error").removeClass("error");
+ $("#" + formid + " span.help-inline").remove();
+}
- if (options.extradata != undefined) {
- args += "&" + options.extradata;
- }
- $.post($form.attr("action"), args, function(data) {
- if (data.status == "ok") {
- $("#modalbox").modal('hide');
- if (opts.success_cb != undefined) {
- opts.success_cb(data);
- return;
- }
- if (opts.reload_on_success) {
- if (opts.reload_mode == 'full') {
- window.location.reload();
- } else {
- history.update(true);
- }
- }
- if (data.respmsg) {
- $("body").notify('success', data.respmsg, 2000);
- }
+/*
+ * Display validation errors for a given form.
+ */
+function display_form_errors(formid, data) {
+ clean_form_errors(formid);
+ $.each(data.form_errors, function(id, value) {
+ var fullid = "id_" + (data.prefix ? data.prefix + "-" : "") + id;
+ var $widget = $("#" + formid + " #" + fullid);
+ var spanid = fullid + "-error";
+ var $span = $("#" + spanid);
+
+ if (!$widget.parents(".control-group").hasClass("error")) {
+ $widget.parents(".control-group").addClass("error");
+ }
+ if (!$span.length) {
+ $span = $("", {
+ "class": "help-inline",
+ "html": value[0],
+ "id": spanid
+ });
+ $widget.parents(".controls").append($span);
} else {
- if (opts.modal) {
- if (data.content != "") {
- $('.modal').html(data.content);
- }
- if (data.respmsg) {
- $('.modal-body').prepend(build_error_alert(data.respmsg));
- }
- }
- if (opts.error_cb != undefined) {
- opts.error_cb(data);
- }
+ $span.html(value[0]);
}
});
}
/*
- * Replacement
+ * Simple function that sends a form using an 'ajax' post request.
+ *
+ * The function is intended to be used in a modal environment.
*/
-function simple_ajax_form_post2(e, options) {
+function simple_ajax_form_post(e, options) {
e.preventDefault();
var $form = (options.formid != undefined) ? $("#" + options.formid) : $("form");
var defaults = {reload_on_success: true, reload_mode: 'full', modal: true};
@@ -176,7 +171,9 @@ function simple_ajax_form_post2(e, options) {
url: $form.attr("action"),
data: args
}).done(function(data) {
- $("#modalbox").modal('hide');
+ if (opts.modal) {
+ $("#modalbox").modal('hide');
+ }
if (opts.success_cb != undefined) {
opts.success_cb(data);
return;
@@ -188,38 +185,22 @@ function simple_ajax_form_post2(e, options) {
history.update(true);
}
}
- if (data.respmsg) {
- $("body").notify('success', data.respmsg, 2000);
+ if (data) {
+ $("body").notify('success', data, 2000);
}
}).fail(function(jqxhr) {
- if (jqxhr.status != 400) {
- return;
- }
- var result = $.parseJSON(jqxhr.responseText);
- $("div.error").removeClass("error");
- $("span.help-inline").remove();
- $.each(result, function(id, value) {
- var fullid = "id_" + id;
- var $widget = $("#" + fullid);
- var spanid = fullid + "-error";
- var $span = $("#" + spanid);
-
- if (!$widget.parents(".control-group").hasClass("error")) {
- $widget.parents(".control-group").addClass("error");
- }
- if (!$span.length) {
- $span = $("", {
- "class": "help-inline",
- "html": value[0],
- "id": spanid
- });
- $widget.parents(".controls").append($span);
+ var data = $.parseJSON(jqxhr.responseText);
+ if (data.form_errors) {
+ display_form_errors(options.formid, data);
+ } else {
+ if (opts.modal) {
+ $('.modal-body').prepend(build_error_alert(data));
} else {
- $span.html(value[0]);
+ $('body').notify('error', data);
}
- });
+ }
if (opts.error_cb) {
- opts.error_cb(result);
+ opts.error_cb(data);
}
});
}
diff --git a/modoboa/static/js/twocols_nav.js b/modoboa/static/js/twocols_nav.js
index d8d3ed337..8c955c2f4 100644
--- a/modoboa/static/js/twocols_nav.js
+++ b/modoboa/static/js/twocols_nav.js
@@ -25,41 +25,11 @@ TwocolsNav.prototype = {
formid: this.options.formid,
modal: false,
reload_on_success: false,
- error_cb: $.proxy(this.display_errors, this),
success_cb: $.proxy(this.save_cb, this)
});
}, this));
},
- display_errors: function(data) {
- if (data.errors === undefined || !data.errors.length) {
- if (data.respmsg) {
- $("body").notify("error", data.respmsg);
- }
- return;
- }
- $.each(data.errors, function(id, value) {
- var fullid = "id_" + (data.prefix ? data.prefix + "-" : "") + id;
- var $widget = $("#" + fullid);
- var spanid = fullid + "-error";
- var $span = $("#" + spanid);
-
- if (!$widget.parents(".control-group").hasClass("error")) {
- $widget.parents(".control-group").addClass("error");
- }
- if (!$span.length) {
- $span = $("", {
- "class": "help-inline",
- "html": value[0],
- "id": spanid
- });
- $widget.parents(".controls").append($span);
- } else {
- $span.html(value[0]);
- }
- });
- },
-
update_content: function(data) {
if (data.content) {
$('#' + this.options.divid).html(data.content);
@@ -134,6 +104,7 @@ TwocolsNav.prototype = {
},
save_cb: function(data) {
+ clean_form_errors(this.options.formid);
if (this.options.reload_exceptions) {
for (var i = 0; i < this.options.reload_exceptions.length; i++) {
if (this.navobj.getbaseurl() == this.options.reload_exceptions[i]) {
@@ -142,7 +113,7 @@ TwocolsNav.prototype = {
}
}
}
- $("body").notify("success", data.respmsg, 2000);
+ $("body").notify("success", data, 2000);
},
load_section: function(e) {