mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
wip: relay domains support
* Unit tests added (limits application) * Added support for limits * Big changes were required * Using standard HTTP codes to identify errors (json) * Custom exceptions have been reworked (not finished yet) see #491
This commit is contained in:
@@ -15,6 +15,7 @@ base_events = [
|
||||
"RoleChanged",
|
||||
"GetExtraRoles",
|
||||
"PasswordChange",
|
||||
"UserCanSetRole",
|
||||
|
||||
"UserMenuDisplay",
|
||||
"AdminMenuDisplay",
|
||||
@@ -32,7 +33,9 @@ base_events = [
|
||||
"ExtraAdminContent",
|
||||
|
||||
"ExtraUprefsRoutes",
|
||||
"ExtraUprefsJS"
|
||||
"ExtraUprefsJS",
|
||||
|
||||
"GetExtraParameters"
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
+13
-10
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
+10
-17
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.")
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
+1
-2
@@ -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" %}
|
||||
@@ -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 %}<div class="alert alert-warning">{% trans "You can't create a new domain administrator" %}</div>{% endif %}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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]),
|
||||
{}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
})
|
||||
|
||||
@@ -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"})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
+10
-1
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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 = '<ul id="fsetmenu" class="nav nav-list"><li class="nav-header">%s</li>%s</ul>' % \
|
||||
(_("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
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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", {
|
||||
|
||||
+12
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
+7
-10
@@ -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):
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
+52
-71
@@ -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 = $("<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 = $("<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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 = $("<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) {
|
||||
|
||||
Reference in New Issue
Block a user