From a295aa1ab66e0e0d8521f7d226d06b05522ad1c6 Mon Sep 17 00:00:00 2001
From: Antoine Nguyen
Date: Mon, 19 Mar 2012 15:13:01 +0100
Subject: [PATCH] wip: Global user interface improvment
* Use of jQuery in place of Mootools
* Most of the custom mootools plugins have been ported to jQuery
* Use of bootstrap from Twitter in place of the old CSS
* Most of the admin templates have been reworked
* The sievefilters extension has been ported
* The postfix autoreply extension is almost done
* more...
see #204
---
admin/forms.py | 604 +++++----
admin/lib.py | 102 +-
admin/migrations/0004_add_permissions.py | 2 +-
...8_auto__add_unique_alias_domain_address.py | 121 ++
admin/models.py | 163 ++-
admin/permissions.py | 133 --
admin/tables.py | 153 +--
admin/templatetags/admin_extras.py | 293 ++---
admin/urls.py | 37 +-
admin/views.py | 738 ++++-------
auth/forms.py | 12 +-
extensions/limits/__init__.py | 43 +-
extensions/limits/controls.py | 108 +-
extensions/limits/forms.py | 43 +-
extensions/limits/permissions.py | 53 +-
extensions/postfix_autoreply/__init__.py | 56 +-
extensions/postfix_autoreply/forms.py | 11 +-
.../postfix_autoreply/autoreply.html | 55 +-
extensions/postfix_autoreply/views.py | 23 +-
extensions/sievefilters/__init__.py | 4 +-
extensions/sievefilters/forms.py | 51 +-
extensions/sievefilters/lib.py | 6 +-
extensions/sievefilters/static/css/style.css | 52 +-
.../static/js/jquery.dynfields.js | 328 +++++
extensions/sievefilters/static/js/lib.js | 227 ++--
.../templates/sievefilters/filter.html | 89 +-
.../templates/sievefilters/filters.html | 28 -
.../templates/sievefilters/guieditor.html | 33 +-
.../templates/sievefilters/index.html | 86 +-
.../templates/sievefilters/newfiltersset.html | 41 -
.../templates/sievefilters/rawfilter.html | 5 -
.../templatetags/sfilters_tags.py | 72 +-
extensions/sievefilters/views.py | 49 +-
extensions/stats/views.py | 2 +-
lib/events.py | 15 +-
lib/formutils.py | 165 +++
lib/permissions.py | 118 ++
lib/tables.py | 15 +-
lib/templatetags/libextras.py | 103 +-
lib/webutils.py | 6 +-
static/css/Autocompleter.css | 50 -
static/css/SqueezeBox.css | 141 ---
static/css/bg_e.png | Bin 990 -> 0 bytes
static/css/bg_n.png | Bin 986 -> 0 bytes
static/css/bg_ne.png | Bin 1367 -> 0 bytes
static/css/bg_nw.png | Bin 1246 -> 0 bytes
static/css/bg_s.png | Bin 985 -> 0 bytes
static/css/bg_se.png | Bin 1260 -> 0 bytes
static/css/bg_sw.png | Bin 1223 -> 0 bytes
static/css/bg_w.png | Bin 981 -> 0 bytes
static/css/bootstrap-responsive.css | 4 +
static/css/bootstrap.css | 632 ++++++++++
static/css/box.css | 207 ----
static/css/calendar.css | 91 --
static/css/closebox.png | Bin 1910 -> 0 bytes
static/css/corner-tl.png | Bin 495 -> 0 bytes
static/css/custom.css | 64 +
static/css/default.css | 10 +-
static/css/login.css | 94 +-
static/img/glyphicons-halflings-white.png | Bin 0 -> 4352 bytes
static/img/glyphicons-halflings.png | Bin 0 -> 4352 bytes
static/js/Autocompleter.Local.js | 31 -
static/js/Autocompleter.Request.js | 109 --
static/js/Autocompleter.js | 442 -------
static/js/Observer.js | 69 --
static/js/ProgressBar.js | 19 -
static/js/SqueezeBox.js | 443 -------
static/js/admin.js | 67 +
static/js/autocompleter.js | 166 +++
static/js/bootstrap.js | 1 +
static/js/calendar.js | 1104 -----------------
static/js/common.js | 74 +-
static/js/confirmation.js | 91 --
static/js/creation_wizard.js | 79 ++
static/js/domains.js | 67 +-
static/js/dynamic_input.js | 106 ++
static/js/dynamic_textinput.js | 94 --
static/js/form.js | 126 --
static/js/{anchor_nav.js => history.js} | 111 +-
static/js/infobox.js | 123 --
static/js/jquery.js | 4 +
static/js/mootools-more.js | 314 -----
static/js/mootools.js | 485 --------
static/js/notify.js | 102 ++
static/js/permissions.js | 67 -
static/js/preferences.js | 15 -
static/js/settings.js | 15 -
static/js/utils.js | 129 +-
templates/admin/add_domain_admin.html | 59 -
templates/admin/add_permission.html | 25 -
templates/admin/adminform.html | 22 -
templates/admin/create_domain_admin.html | 9 -
templates/admin/dlistform.html | 7 +
templates/admin/domain_admin_promotion.html | 10 -
templates/admin/domainform.html | 8 +
templates/admin/domains.html | 16 +-
templates/admin/domains_header.html | 23 -
templates/admin/extensions.html | 8 +-
templates/admin/header.html | 4 +
templates/admin/identities.html | 34 +
templates/admin/import.html | 65 -
templates/admin/import_done.html | 2 +-
templates/admin/importform.html | 12 +
templates/admin/listing.html | 37 +-
templates/admin/mailform.html | 3 +
templates/admin/mbaliasform.html | 37 -
templates/admin/newaccount.html | 33 +
templates/admin/parameters.html | 11 +-
templates/admin/permissions.html | 22 -
templates/admin/permsform.html | 2 +
templates/admin/settings_header.html | 33 +-
templates/admin/view_domain_admins.html | 8 -
templates/common/generic_field.html | 6 +
templates/common/generic_fields_group.html | 8 +
templates/common/generic_modal_form.html | 20 +
templates/common/genericform.html | 12 -
templates/common/menu.html | 2 +-
templates/common/menulist.html | 24 +-
templates/common/pagination_bar.html | 22 +
templates/common/parameters.html | 21 +-
templates/common/tabforms.html | 31 +
templates/common/table_body.html | 2 +-
templates/layout_simple.html | 23 -
templates/nlayout.html | 86 +-
templates/registration/login.html | 51 +-
templates/twocols.html | 9 +
templates/userprefs/chpassword.html | 38 +-
templates/userprefs/preferences.html | 39 +-
userprefs/templatetags/uprefs_extras.py | 96 +-
userprefs/views.py | 9 +-
130 files changed, 4255 insertions(+), 6725 deletions(-)
create mode 100644 admin/migrations/0018_auto__add_unique_alias_domain_address.py
delete mode 100644 admin/permissions.py
create mode 100644 extensions/sievefilters/static/js/jquery.dynfields.js
delete mode 100644 extensions/sievefilters/templates/sievefilters/filters.html
delete mode 100644 extensions/sievefilters/templates/sievefilters/newfiltersset.html
create mode 100644 lib/formutils.py
create mode 100644 lib/permissions.py
delete mode 100644 static/css/Autocompleter.css
delete mode 100644 static/css/SqueezeBox.css
delete mode 100644 static/css/bg_e.png
delete mode 100644 static/css/bg_n.png
delete mode 100644 static/css/bg_ne.png
delete mode 100644 static/css/bg_nw.png
delete mode 100644 static/css/bg_s.png
delete mode 100644 static/css/bg_se.png
delete mode 100644 static/css/bg_sw.png
delete mode 100644 static/css/bg_w.png
create mode 100644 static/css/bootstrap-responsive.css
create mode 100644 static/css/bootstrap.css
delete mode 100644 static/css/box.css
delete mode 100644 static/css/calendar.css
delete mode 100644 static/css/closebox.png
delete mode 100644 static/css/corner-tl.png
create mode 100644 static/css/custom.css
create mode 100644 static/img/glyphicons-halflings-white.png
create mode 100644 static/img/glyphicons-halflings.png
delete mode 100644 static/js/Autocompleter.Local.js
delete mode 100644 static/js/Autocompleter.Request.js
delete mode 100644 static/js/Autocompleter.js
delete mode 100644 static/js/Observer.js
delete mode 100644 static/js/ProgressBar.js
delete mode 100644 static/js/SqueezeBox.js
create mode 100644 static/js/admin.js
create mode 100644 static/js/autocompleter.js
create mode 100644 static/js/bootstrap.js
delete mode 100644 static/js/calendar.js
delete mode 100644 static/js/confirmation.js
create mode 100644 static/js/creation_wizard.js
create mode 100644 static/js/dynamic_input.js
delete mode 100644 static/js/dynamic_textinput.js
delete mode 100644 static/js/form.js
rename static/js/{anchor_nav.js => history.js} (62%)
delete mode 100644 static/js/infobox.js
create mode 100644 static/js/jquery.js
delete mode 100644 static/js/mootools-more.js
delete mode 100644 static/js/mootools.js
create mode 100644 static/js/notify.js
delete mode 100644 static/js/permissions.js
delete mode 100644 static/js/preferences.js
delete mode 100644 static/js/settings.js
delete mode 100644 templates/admin/add_domain_admin.html
delete mode 100644 templates/admin/add_permission.html
delete mode 100644 templates/admin/adminform.html
delete mode 100644 templates/admin/create_domain_admin.html
create mode 100644 templates/admin/dlistform.html
delete mode 100644 templates/admin/domain_admin_promotion.html
create mode 100644 templates/admin/domainform.html
delete mode 100644 templates/admin/domains_header.html
create mode 100644 templates/admin/header.html
create mode 100644 templates/admin/identities.html
delete mode 100644 templates/admin/import.html
create mode 100644 templates/admin/importform.html
create mode 100644 templates/admin/mailform.html
delete mode 100644 templates/admin/mbaliasform.html
create mode 100644 templates/admin/newaccount.html
delete mode 100644 templates/admin/permissions.html
create mode 100644 templates/admin/permsform.html
delete mode 100644 templates/admin/view_domain_admins.html
create mode 100644 templates/common/generic_field.html
create mode 100644 templates/common/generic_fields_group.html
create mode 100644 templates/common/generic_modal_form.html
delete mode 100644 templates/common/genericform.html
create mode 100644 templates/common/pagination_bar.html
create mode 100644 templates/common/tabforms.html
delete mode 100644 templates/layout_simple.html
create mode 100644 templates/twocols.html
diff --git a/admin/forms.py b/admin/forms.py
index f0f1bc7c8..5d9a5cb71 100644
--- a/admin/forms.py
+++ b/admin/forms.py
@@ -1,15 +1,25 @@
# coding: utf-8
from django import forms
+from django.contrib.auth.models import Group
from modoboa.admin.models import *
from django.utils.translation import ugettext as _, ugettext_noop
+from django.http import QueryDict
from modoboa.admin.templatetags.admin_extras import gender
-from modoboa.lib import tables
from modoboa.lib.emailutils import split_mailbox
+from modoboa.lib.permissions import get_account_roles
+from modoboa.lib.formutils import *
+from modoboa.lib.permissions import *
+
+class DomainForm(forms.ModelForm, DynamicForm):
+ aliases = DomainNameField(
+ label=ugettext_noop("Alias(es)"),
+ required=False,
+ help_text=ugettext_noop("Alias(es) of this domain")
+ )
-class DomainForm(forms.ModelForm):
class Meta:
model = Domain
- fields = ("name", "quota", "enabled")
+ fields = ("name", "quota", "aliases", "enabled")
def __init__(self, *args, **kwargs):
self.oldname = None
@@ -17,240 +27,235 @@ class DomainForm(forms.ModelForm):
self.oldname = kwargs["instance"].name
super(DomainForm, self).__init__(*args, **kwargs)
- def save(self, force_insert=False, force_update=False, commit=True):
+ if len(args) and isinstance(args[0], QueryDict):
+ self._load_from_qdict(args[0], "aliases", DomainNameField)
+ elif kwargs.has_key("instance"):
+ d = kwargs["instance"]
+ for pos, dalias in enumerate(d.domainalias_set.all()):
+ name = "aliases_%d" % (pos + 1)
+ self._create_field(forms.CharField, name, dalias.name, 3)
+
+ def clean(self):
+ super(DomainForm, self).clean()
+ if len(self._errors):
+ raise forms.ValidationError(self._errors)
+
+ cleaned_data = self.cleaned_data
+ name = cleaned_data["name"]
+
+ try:
+ DomainAlias.objects.get(name=name)
+ except DomainAlias.DoesNotExist:
+ pass
+ else:
+ self._errors["name"] = self.error_class([_("An alias with this name already exists")])
+ del cleaned_data["name"]
+
+ errors = []
+ for k in cleaned_data.keys():
+ if not k.startswith("aliases"):
+ continue
+ if cleaned_data[k] == "":
+ del cleaned_data[k]
+ continue
+ try:
+ Domain.objects.get(name=cleaned_data[k])
+ except Domain.DoesNotExist:
+ pass
+ else:
+ self._errors[k] = self.error_class([_("A domain with this name already exists")])
+ del cleaned_data[k]
+
+ return cleaned_data
+
+ def save(self, user, commit=True):
d = super(DomainForm, self).save(commit=False)
if commit:
if self.oldname is not None and d.name != self.oldname:
if not d.rename_dir(self.oldname):
raise AdminError(_("Failed to rename domain, check permissions"))
d.save()
+ 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()
+ grant_access_to_object(user, al, is_owner=True)
+ events.raiseEvent("DomainAliasCreated", user, al)
+
+ for dalias in d.domainalias_set.all():
+ if not len(filter(lambda name: self.cleaned_data[name] == dalias.name,
+ self.cleaned_data.keys())):
+ events.raiseEvent("DomainAliasDeleted", dalias)
+ ungrant_access_to_object(dalias)
+ dalias.delete()
return d
-class ProxyForm(forms.ModelForm):
- def __init__(self, user, *args, **kwargs):
- super(ProxyForm, self).__init__(*args, **kwargs)
-
- if not user.is_superuser:
- self._domains = user.get_domains()
- else:
- self._domains = None
-
-class DomainAliasForm(ProxyForm):
- class Meta:
- model = DomainAlias
- fields = ('name', 'target', 'enabled')
-
- def __init__(self, *args, **kwargs):
- super(DomainAliasForm, self).__init__(*args, **kwargs)
- if self._domains is not None:
- #self.fields["target"].queryset = Domain.objects.filter(pk=self._domain.id)
- self.fields["target"].queryset = self._domains
-
-class MailboxForm(ProxyForm):
- name = forms.CharField(
- label=ugettext_noop("Name"), max_length=100,
- help_text=ugettext_noop("First name and last name of mailbox owner")
+class DlistForm(forms.ModelForm, DynamicForm):
+ email = forms.EmailField(
+ label=ugettext_noop("Email address"),
+ help_text=ugettext_noop("")
+ )
+ recipients = forms.EmailField(
+ label=ugettext_noop("Recipients"), required=False,
+ help_text=ugettext_noop("Mailbox(es) this alias will point to")
)
- quota = forms.IntegerField(label=ugettext_noop("Quota"), required=False,
- help_text=ugettext_noop("Mailbox quota in MB (default to domain quota if blank)"))
- enabled = forms.BooleanField(label=gender("Enabled", "f"), required=False,
- initial=True,
- help_text=ugettext_noop("Check to activate this mailbox"))
-
- class Meta:
- model = Mailbox
- fields = ('domain', 'address')
-
- def __init__(self, *args, **kwargs):
- super(MailboxForm, self).__init__(*args, **kwargs)
- for f in ['name', 'address', 'quota']:
- self.fields[f].widget.attrs['size'] = 14
- if self._domains is not None:
- self.fields["domain"].queryset = self._domains
- #self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
- #self.fields["domain"].initial = self._domain
-
- if kwargs.has_key("instance"):
- mb = kwargs["instance"]
- self.fields['name'].initial = unicode(mb.user)
- self.fields['quota'].initial = mb.quota
- self.fields['enabled'].initial = mb.user.is_active
-
- def clean_address(self):
- if self.cleaned_data["address"].find('@') != -1:
- return self.cleaned_data["address"].rsplit("@", 1)[0]
- return self.cleaned_data["address"]
-
- def commit_save(self, mb, **kwargs):
- mb.save(name=self.cleaned_data["name"],
- enabled=self.cleaned_data["enabled"],
- quota=self.cleaned_data["quota"],
- **kwargs)
-
- def save(self, force_insert=False, force_update=False, commit=True):
- m = super(MailboxForm, self).save(commit=False)
- if commit:
- self.commit_save(m)
- return m
-
-class MailboxWithPasswordForm(MailboxForm):
- password1 = forms.CharField(label=ugettext_noop("Password"),
- widget=forms.PasswordInput(render_value=True),
- help_text=ugettext_noop("Password used to log in"))
- password2 = forms.CharField(label=ugettext_noop("Confirmation"),
- widget=forms.PasswordInput(render_value=True),
- help_text=ugettext_noop("Password confirmation"))
-
- def __init__(self, *args, **kwargs):
- super(MailboxWithPasswordForm, self).__init__(*args, **kwargs)
- for f in ['password1', 'password2']:
- self.fields[f].widget.attrs['size'] = 14
- self.fields.keyOrder = ['name', 'domain', 'address', 'quota',
- 'password1', 'password2', 'enabled']
-
- def clean_password2(self):
- if self.cleaned_data["password1"] != self.cleaned_data["password2"]:
- raise forms.ValidationError(_("Passwords mismatch"))
- return self.cleaned_data["password2"]
-
- def commit_save(self, mb):
- super(MailboxWithPasswordForm, self).commit_save(
- mb, password=self.cleaned_data["password1"]
- )
-
-class AliasForm(ProxyForm):
- targets = forms.CharField(label=ugettext_noop("Target(s)"), required=False,
- help_text=ugettext_noop("Mailbox(es) this alias will point to"))
-
- def __init__(self, *args, **kwargs):
- super(AliasForm, self).__init__(*args, **kwargs)
- self.fields['address'].widget.attrs['size'] = 14
-
- # This part is a little bit tricky. As 'domain' is not a
- # member of Alias, we need to fill it manually. if
- # self._domain exists, it means a DomainAdmin is going to
- # create a new alias, we know how to fill the fields.
- # if self._domain is not None:
- # self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
-
- if self._domains is not None:
- self.fields["domain"].queryset = self._domains
-
- # if 'instance' exists, it means we are modifying an existing
- # record, we need to select the right value for 'domain' and
- # to fill the first "targets" input (because it is created by the
- # Form object, not by us).
- #domain = None
- # if self._domain:
- # domain = self._domain
- if kwargs.has_key("instance"):
- # if self._domain is None:
- # domain = kwargs["instance"].domain
- self.fields["targets"].initial = kwargs["instance"].first_target()
-
- # if domain is not None:
- # self.fields["domain"].initial = domain
-
- self.fields.keyOrder = ['address', 'domain', 'enabled', 'targets']
class Meta:
model = Alias
- fields = ('address', 'domain', 'enabled')
+ fields = ("enabled",)
- def clean_address(self):
- if self.cleaned_data["address"].find('@') != -1:
- return self.cleaned_data["address"].rsplit("@", 1)[0]
- return self.cleaned_data["address"]
+ def __init__(self, *args, **kwargs):
+ super(DlistForm, self).__init__(*args, **kwargs)
+ self.fields.keyOrder = ['email', 'recipients', 'enabled']
- def set_targets(self, user, values):
- """Targets dispatching
+ if len(args) and isinstance(args[0], QueryDict):
+ self._load_from_qdict(args[0], "recipients", forms.EmailField)
+ elif kwargs.has_key("instance"):
+ dlist = kwargs["instance"]
+ self.fields["email"].initial = dlist.full_address
+ cpt = 1
+ for mb in dlist.mboxes.all():
+ name = "recipients_%d" % (cpt)
+ self._create_field(forms.EmailField, name, mb.full_address, 2)
+ cpt += 1
+ for addr in dlist.extmboxes.split(','):
+ if addr == "":
+ continue
+ name = "recipients_%d" % (cpt)
+ self._create_field(forms.EmailField, name, addr, 2)
+ cpt += 1
- We make a difference between 'local' targets (the ones hosted
- by Modoboa) and 'external targets.
+ def clean_email(self):
+ localpart, domname = split_mailbox(self.cleaned_data["email"])
+ try:
+ Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ raise forms.ValidationError(_("Domain does not exist"))
+ return self.cleaned_data["email"]
+
+ def set_recipients(self, user):
+ """Recipients dispatching
+
+ We make a difference between 'local' recipients (the ones hosted
+ by Modoboa) and 'external' recipients.
"""
- self.ext_targets = []
- self.int_targets = []
+ self.ext_rcpts = []
+ self.int_rcpts = []
+ total = 0
- for addr in values:
- if addr == "":
+ for k, v in self.cleaned_data.iteritems():
+ if not k.startswith("recipients"):
continue
- local_part, domname = split_mailbox(addr)
+ if v == "":
+ continue
+ local_part, domname = split_mailbox(v)
if domname is None:
- raise AdminError("%s %s" % (_("Invalid mailbox"), addr))
+ raise AdminError("%s %s" % (_("Invalid mailbox"), v))
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
domain = None
if domain:
if not user.is_owner(domain):
- raise PermDeniedException(addr)
+ raise PermDeniedException(v)
try:
mb = Mailbox.objects.get(domain=domain, address=local_part)
except Mailbox.DoesNotExist:
- raise AdminError(_("Mailbox %s does not exist" % addr))
- self.int_targets += [mb]
+ raise AdminError(_("Mailbox %s does not exist" % v))
+ self.int_rcpts += [mb]
+ total += 1
continue
- self.ext_targets += [addr]
+ self.ext_rcpts += [v]
+ total += 1
- def save(self, force_insert=False, force_update=False, commit=True):
- a = super(AliasForm, self).save(commit=False)
+ if total == 0:
+ raise AdminError(_("No recipient defined"))
+
+ if total < 2:
+ raise AdminError(_("A distribution list must contain at least two recipients"))
+
+ def save(self, commit=True):
+ dlist = super(DlistForm, self).save(commit=False)
+ localpart, domname = split_mailbox(self.cleaned_data["email"])
+ dlist.address = localpart
+ dlist.domain = Domain.objects.get(name=domname)
if commit:
- a.save(self.int_targets, self.ext_targets)
+ dlist.save(self.int_rcpts, self.ext_rcpts)
self.save_m2m()
- return a
+ return dlist
-class SuperAdminForm(forms.Form):
- def __init__(self, user, *args, **kwargs):
- super(SuperAdminForm, self).__init__(*args, **kwargs)
+class ImportDataForm(forms.Form):
+ sourcefile = forms.FileField(label=_("Select a file"))
+ sepcar = forms.CharField(label=_("Separator"), max_length=1, required=False)
- self.fields["user"] = \
- forms.ModelChoiceField([], label=_("User"), required=True,
- empty_label=_("Select a user"),
- help_text=_("Select a user in the list"))
- self.fields["user"].queryset = User.objects.exclude(pk__in=[1, user.id])
+ def __init__(self, *args, **kwargs):
+ super(ImportDataForm, self).__init__(*args, **kwargs)
+ self.fields["sepcar"].widget.attrs = {"class" : "span1"}
-class DomainAdminForm(forms.Form):
- createfrom = forms.ChoiceField(
- choices=[("new", ugettext_noop("Create a new account")),
- ("select", ugettext_noop("Promote an existing account"))],
- widget=forms.RadioSelect)
+ def clean_sepcar(self):
+ if self.cleaned_data["sepcar"] == "":
+ return ";"
+ return str(self.cleaned_data["sepcar"])
- domain = forms.ModelChoiceField(
- queryset=Domain.objects.all(),
- label=_("Domain"),
- required=True,
- empty_label=ugettext_noop("Select a domain"),
- help_text=ugettext_noop("Select a domain in the list")
- )
-
- user = forms.ChoiceField(
- label=ugettext_noop("User"), required=True,
- choices=[("", ugettext_noop("Empty"))],
- help_text=ugettext_noop("Select a user in the list"))
-
-class UserForm(forms.ModelForm):
- createmb = forms.BooleanField(
- label=ugettext_noop("Create mailbox"), required=False,
- help_text=ugettext_noop("Create a mailbox for this user using the given address")
+class AccountFormGeneral(forms.ModelForm):
+ role = forms.ChoiceField(
+ choices=[('', ugettext_noop("Choose"))]
)
class Meta:
model = User
- fields = ("username", "first_name", "last_name", "email")
+ fields = ("username", "first_name", "last_name", "role", "is_active")
- def save(self, commit=True, group=None):
- user = super(UserForm, self).save(commit=False)
- if self.cleaned_data.has_key("password1"):
- user.set_password(self.cleaned_data["password1"])
+ def __init__(self, user, *args, **kwargs):
+ super(AccountFormGeneral, self).__init__(*args, **kwargs)
+
+ self.user = user
+ if user.belongs_to_group("DomainAdmins"):
+ del self.fields["role"]
+ return
+
+ self.fields["role"].choices = \
+ [('', ugettext_noop("Choose"))] + get_account_roles(user)
+ if kwargs.has_key("instance"):
+ u = kwargs["instance"]
+ if u.is_superuser:
+ role = "SuperAdmins"
+ else:
+ try:
+ role = u.groups.all()[0].name
+ except IndexError:
+ pass
+ self.fields["role"].initial = role
+
+ def save(self, commit=True):
+ account = super(AccountFormGeneral, self).save(commit=False)
if commit:
- user.save()
- if group:
- user.groups.add(Group.objects.get(name=group))
- user.save()
- return user
+ account.save()
+ if self.cleaned_data.has_key("role"):
+ role = self.cleaned_data["role"]
+ else:
+ role = "SimpleUsers"
+ if role == "SuperAdmins":
+ account.is_superuser = True
+ else:
+ account.is_superuser = False
+ account.groups.clear()
+ account.groups.add(Group.objects.get(name=role))
+ account.save()
+ return account
-class UserWithPasswordForm(UserForm):
+class AccountFormGeneralPwd(AccountFormGeneral):
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(
label=ugettext_noop("Confirmation"),
@@ -258,6 +263,11 @@ class UserWithPasswordForm(UserForm):
help_text=ugettext_noop("Enter the same password as above, for verification.")
)
+ class Meta:
+ model = User
+ fields = ("username", "first_name", "last_name",
+ "password1", "password2", "role", "is_active")
+
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
@@ -265,38 +275,194 @@ class UserWithPasswordForm(UserForm):
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
-class DomainAdminPromotionForm(forms.Form):
- name = forms.CharField(
- label=ugettext_noop("Name"),
- help_text=ugettext_noop("The username/email address/name of the account to promote")
+ def save(self, commit=True, **kwargs):
+ user = super(AccountFormGeneralPwd, self).save(**kwargs)
+ if self.cleaned_data.has_key("password1"):
+ user.set_password(self.cleaned_data["password1"])
+ if commit:
+ user.save()
+ return user
+
+class AccountFormMail(forms.Form, DynamicForm):
+ # FIXME
+ # * ajout du quota
+ # * Renommage de boite ?
+
+ email = forms.EmailField(label=_("E-mail"), required=False)
+ aliases = forms.EmailField(
+ label=ugettext_noop("Alias(es)"),
+ required=False,
+ help_text=ugettext_noop("Alias(es) of this mailbox")
)
- def clean_name(self):
+ def __init__(self, *args, **kwargs):
+ if kwargs.has_key("instance"):
+ self.mb = kwargs["instance"]
+ del kwargs["instance"]
+ super(AccountFormMail, self).__init__(*args, **kwargs)
+ if not hasattr(self, "mb") or self.mb is None:
+ return
+ self.fields["email"].required = True
+ for pos, alias in enumerate(self.mb.alias_set.all()):
+ if len(alias.get_recipients()) >= 2:
+ continue
+ name = "aliases_%d" % (pos + 1)
+ self._create_field(forms.EmailField, name, alias.full_address)
+ self.fields["email"].initial = self.mb.full_address
+ if len(args) and isinstance(args[0], QueryDict):
+ self._load_from_qdict(args[0], "aliases", forms.EmailField)
+
+ def save(self, user, account):
+ if self.cleaned_data["email"] == "":
+ return None
+
+ locpart, domname = split_mailbox(self.cleaned_data["email"])
try:
- u = User.objects.get(username=self.cleaned_data["name"])
- except User.DoesNotExist:
- raise forms.ValidationError(_("Unknown user"))
- return self.cleaned_data["name"]
+ domain = Domain.objects.get(name=domname)
+ except Domain.DoesNotExist:
+ raise AdminError(_("Domain does not exist"))
-class AssignDomainsForm(forms.Form):
- domains = forms.ModelMultipleChoiceField(
- queryset=None,
- label=ugettext_noop("Domains"),
- required=True,
- help_text=ugettext_noop("Select one or more domains in the list")
+ if not user.can_access(domain):
+ raise PermDeniedException(_("You don't have access to this domain"))
+
+ if not hasattr(self, "mb") or self.mb is None:
+ try:
+ self.mb = Mailbox.objects.get(address=locpart, domain=domain)
+ except Mailbox.DoesNotExist:
+ events.raiseEvent("CanCreate", user, "mailboxes")
+ self.mb = Mailbox()
+ self.mb.save_from_user(locpart, domain, account)
+ grant_access_to_object(user, self.mb, is_owner=True)
+ events.raiseEvent("CreateMailbox", user, self.mb)
+ else:
+ if self.cleaned_data["email"] != self.mb.full_address:
+ if self.mb.rename_dir(domname, locpart):
+ self.mb.domain = domain
+ self.mb.address = locpart
+ self.mb.save()
+ else:
+ raise AdminError(_("Failed to rename mailbox, check permissions"))
+
+ account.email = self.cleaned_data["email"]
+ account.save()
+
+ 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([self.mb], [])
+ grant_access_to_object(user, al, is_owner=True)
+ events.raiseEvent("MailboxAliasCreated", user, al)
+
+ 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())):
+ events.raiseEvent("MailboxAliasDeleted", alias)
+ ungrant_access_to_object(alias)
+ alias.delete()
+
+ return self.mb
+
+class AccountPermissionsForm(forms.Form, DynamicForm):
+ domains = DomainNameField(
+ label=ugettext_noop("Domain(s)"),
+ required=False,
+ help_text=ugettext_noop("Domain(s) that user administrates")
)
- def __init__(self, user, domadmin, *args, **kwargs):
- super(AssignDomainsForm, self).__init__(*args, **kwargs)
- self.fields["domains"].queryset = user.get_domains()
- self.fields["domains"].initial = domadmin.get_domains()
+ def __init__(self, *args, **kwargs):
+ if kwargs.has_key("instance"):
+ self.account = kwargs["instance"]
+ del kwargs["instance"]
+
+ super(AccountPermissionsForm, self).__init__(*args, **kwargs)
-class ImportDataForm(forms.Form):
- sourcefile = forms.FileField(label=_("Select a file"))
- sepcar = forms.CharField(label=_("Separator"), max_length=1, required=False)
+ if not hasattr(self, "account") or self.account is None:
+ return
+ for pos, domain in enumerate(self.account.get_domains()):
+ name = "domains_%d" % (pos + 1)
+ self._create_field(DomainNameField, name, domain.name)
+ if len(args) and isinstance(args[0], QueryDict):
+ self._load_from_qdict(args[0], "domains", DomainNameField)
- def clean_sepcar(self):
- if self.cleaned_data["sepcar"] == "":
- return ";"
- return self.cleaned_data["sepcar"]
+ def save(self):
+ current_domains = map(lambda dom: dom.name, self.account.get_domains())
+ for name, value in self.cleaned_data.iteritems():
+ if not name.startswith("domains"):
+ continue
+ if value in ["", None]:
+ continue
+ if not value in current_domains:
+ domain = Domain.objects.get(name=value)
+ grant_access_to_object(self.account, domain)
+
+ for domain in self.account.get_domains():
+ if not len(filter(lambda name: self.cleaned_data[name] == domain.name,
+ self.cleaned_data.keys())):
+ ungrant_access_to_object(domain, self.account)
+
+class AccountForm(TabForms):
+
+ def __init__(self, user, *args, **kwargs):
+ self.user = user
+ self.forms = [
+ dict(id="general", title=_("General"), cls=AccountFormGeneralPwd,
+ new_args=[user], mandatory=True),
+ dict(id="mail", title=_("Mail"), formtpl="admin/mailform.html",
+ cls=AccountFormMail),
+ dict(id="perms", title=_("Permissions"), formtpl="admin/permsform.html",
+ cls=AccountPermissionsForm)
+ ]
+ cbargs = [user]
+ if kwargs.has_key("instances"):
+ cbargs += [kwargs["instances"]["general"]]
+ self.forms += events.raiseQueryEvent("ExtraAccountForm", *cbargs)
+
+ super(AccountForm, self).__init__(*args, **kwargs)
+
+ def check_perms(self, account):
+ if account.is_superuser:
+ return False
+ return account.has_perm("admin.view_domain")
+
+ def _before_is_valid(self, form):
+ if form["id"] == "general":
+ return True
+
+ if hasattr(self, "check_%s" % form["id"]):
+ if not getattr(self, "check_%s" % form["id"])(self.account):
+ return False
+ return True
+
+ if False in events.raiseQueryEvent("CheckExtraAccountForm", self.account, form):
+ return False
+ return True
+
+ def save_general_form(self):
+ self.account = self.forms[0]["instance"].save()
+
+ def save(self):
+ """Custom save method
+
+ As forms interact with each other, it is simpler to make
+ custom code to save them.
+ """
+ self.forms[1]["instance"].save(self.user, self.account)
+ if len(self.forms) <= 2:
+ return
+ for f in self.forms[2:]:
+ f["instance"].save()
diff --git a/admin/lib.py b/admin/lib.py
index 1ad7d6bda..e1e9a2a57 100644
--- a/admin/lib.py
+++ b/admin/lib.py
@@ -1,10 +1,8 @@
# coding: utf-8
-from functools import wraps
-from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-from models import *
-from modoboa.lib.webutils import _render_error, _render
+from modoboa.lib.webutils import _render
+from modoboa.lib import parameters
from modoboa.admin.tables import *
from exceptions import *
@@ -36,98 +34,10 @@ def render_listing(request, objtype, tplname="admin/listing.html",
except ValueError:
page = 1
try:
- kwargs["objects"] = paginator.page(page)
+ kwargs["page"] = paginator.page(page)
except (EmptyPage, PageNotAnInteger):
- kwargs["objects"] = paginator.page(paginator.num_pages)
- kwargs["last_page"] = paginator.num_pages
- kwargs["total"] = paginator.count
- kwargs["table"] = tblclass(request, kwargs["objects"].object_list)
+ kwargs["page"] = paginator.page(paginator.num_pages)
+ kwargs["table"] = tblclass(request, kwargs["page"].object_list)
+ kwargs["selection"] = objtype
return _render(request, tplname, kwargs)
-
-def grant_access_to_object(user, obj, is_owner=False):
- """Grant access to an object for a given user
-
- There are two different cases where we want to grant access to an
- object for a specific user:
-
- * He is the owner (he's just created the object)
- * He is going to administrate the object (but he is not the owner)
-
- :param user: a ``User`` object
- :param obj: an admin. object (Domain, Mailbox, ...)
- :param is_owner: the user is the unique object's owner
- """
- ct = get_content_type(obj)
- try:
- entry = user.objectaccess_set.get(content_type=ct, object_id=obj.id)
- entry.is_owner = is_owner
- entry.save()
- except ObjectAccess.DoesNotExist:
- pass
- else:
- return
-
- try:
- objaccess = ObjectAccess(user=user, content_type=ct,
- object_id=obj.id, is_owner=is_owner)
- objaccess.save()
- except IntegrityError, e:
- raise AdminError(_("Failed to grant access (%s)" % str(e)))
-
-def ungrant_access_to_object(obj, user=None):
- """Ungrant access to an object for a specific user
-
- If no user is provided, all entries referencing this object are
- deleted from the database.
-
- :param obj: an object inheriting from ``models.Model``
- :param user: a ``User`` object
- """
- ct = get_content_type(obj)
- if user:
- try:
- ObjectAccess.objects.get(user=user, content_type=ct, object_id=obj.id).delete()
- except ObjectAccess.DoesNotExist:
- pass
- else:
- ObjectAccess.objects.filter(content_type=ct, object_id=obj.id).delete()
-
-def get_object_owner(obj):
- """Return the unique owner of this object
-
- :param obj: an object inheriting from ``model.Model``
- :return: a ``User`` object
- """
- ct = get_content_type(obj)
- try:
- entry = ObjectAccess.objects.get(content_type=ct, object_id=obj.id, is_owner=True)
- except ObjectAccess.DoesNotExist:
- return None
- return entry.user
-
-def check_domain_access(f):
- """Decorator to check if the current user can access a domain
-
- This decorator only applies to url that contain a 'domid' or
- 'mbid' parameter. (for a direct access)
-
- Raise a ``PermDeniedError`` if the current user can't view the
- requested object.
- """
- @wraps(f)
- def dec(request, **kwargs):
- if not request.user.is_superuser:
- domain = None
- domid = request.GET.get("domid", None)
- if domid:
- domain = Domain.objects.get(pk=domid)
- else:
- mbid = request.GET.get("mbid", None)
- if mbid:
- domain = Mailbox.objects.get(pk=mbid).domain
- if domain and not request.user.can_access(domain):
- raise PermDeniedError(_("You can't access this domain"))
-
- return f(request, **kwargs)
- return dec
diff --git a/admin/migrations/0004_add_permissions.py b/admin/migrations/0004_add_permissions.py
index f1c0ec735..8b3416ce7 100644
--- a/admin/migrations/0004_add_permissions.py
+++ b/admin/migrations/0004_add_permissions.py
@@ -93,4 +93,4 @@ class Migration(DataMigration):
}
}
- complete_apps = ['admin']
+ complete_apps = ['admin', 'contenttypes']
diff --git a/admin/migrations/0018_auto__add_unique_alias_domain_address.py b/admin/migrations/0018_auto__add_unique_alias_domain_address.py
new file mode 100644
index 000000000..41af5c4b1
--- /dev/null
+++ b/admin/migrations/0018_auto__add_unique_alias_domain_address.py
@@ -0,0 +1,121 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding unique constraint on 'Alias', fields ['domain', 'address']
+ db.create_unique('admin_alias', ['domain_id', 'address'])
+
+
+ def backwards(self, orm):
+
+ # Removing unique constraint on 'Alias', fields ['domain', 'address']
+ db.delete_unique('admin_alias', ['domain_id', 'address'])
+
+
+ models = {
+ 'admin.alias': {
+ 'Meta': {'unique_together': "(('address', 'domain'),)", 'object_name': 'Alias'},
+ 'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
+ 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
+ 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'})
+ },
+ 'admin.domain': {
+ 'Meta': {'object_name': 'Domain'},
+ 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+ 'quota': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'admin.domainalias': {
+ 'Meta': {'object_name': 'DomainAlias'},
+ 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"})
+ },
+ 'admin.extension': {
+ 'Meta': {'object_name': 'Extension'},
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'admin.mailbox': {
+ 'Meta': {'object_name': 'Mailbox'},
+ 'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
+ 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
+ 'gid': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'quota': ('django.db.models.fields.IntegerField', [], {}),
+ 'uid': ('django.db.models.fields.IntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'admin.objectaccess': {
+ 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'admin.objectdates': {
+ 'Meta': {'object_name': 'ObjectDates'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ 'admin.user': {
+ 'Meta': {'object_name': 'User', 'db_table': "'auth_user'", '_ormbases': ['auth.User'], 'proxy': 'True'}
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['admin']
diff --git a/admin/models.py b/admin/models.py
index b53a2228b..1e771ac3b 100644
--- a/admin/models.py
+++ b/admin/models.py
@@ -24,15 +24,6 @@ try:
except ImportError:
ldap_available = False
-def get_content_type(obj):
- """Simple function that use the right method to retrieve a content type
-
- :param obj: a django model
- :return: a ``ContentType`` object
- """
- return obj.get_content_type() if hasattr(obj, "get_content_type") \
- else ContentType.objects.get_for_model(obj)
-
class User(DUser):
"""Proxy for the ``User`` model.
@@ -50,6 +41,37 @@ class User(DUser):
def __unicode__(self):
return u"%s %s" % (self.first_name, self.last_name)
+ def delete(self, fromuser, keep_mb_dir, *args, **kwargs):
+ """Custom delete method
+ """
+ from modoboa.lib.permissions import \
+ get_object_owner, grant_access_to_object, ungrant_access_to_object
+
+ if not fromuser.can_access(self):
+ raise PermDeniedException(_("You don't have access to this account"))
+ if fromuser == self:
+ raise AdminError(_("You can't delete your own account"))
+ if self.has_mailbox:
+ mb = self.mailbox_set.all()[0]
+ if not fromuser.can_access(mb.domain):
+ raise PermDeniedException(_("You don't have access to this domain"))
+ events.raiseEvent("DeleteMailbox", mb)
+ ungrant_access_to_object(mb)
+ mb.delete(keepdir=keep_mb_dir)
+
+ owner = get_object_owner(self)
+ for ooentry in self.objectaccess_set.all():
+ if ooentry.is_owner:
+ if ooentry.content_object is not None:
+ grant_access_to_object(owner, ooentry.content_object, True)
+ else:
+ print "ORPHELIN!!!"
+ ooentry.delete()
+
+ events.raiseEvent("AccountDeleted", self)
+ ungrant_access_to_object(self)
+ super(User, self).delete(*args, **kwargs)
+
@staticmethod
def get_content_type():
"""An uggly hack to retrieve the appropriate content_type!
@@ -130,6 +152,27 @@ class User(DUser):
return "%s %s" % (self.first_name, self.last_name)
return self.username
+ @property
+ def identity(self):
+ if self.email != "":
+ return self.email
+ return self.username
+
+ @property
+ def name_or_rcpt(self):
+ if self.first_name != "":
+ return "%s %s" % (self.first_name, self.last_name)
+ return "----"
+
+ @property
+ def group(self):
+ if self.is_superuser:
+ return "SuperAdmin"
+ try:
+ return self.groups.all()[0].name
+ except IndexError:
+ return "?"
+
def belongs_to_group(self, name):
"""Simple shortcut to check if this user is a member of a
specific group.
@@ -216,6 +259,8 @@ class User(DUser):
:param obj: an object inheriting from ``models.Model``
:return: a boolean
"""
+ from modoboa.lib.permissions import get_content_type
+
ct = get_content_type(obj)
try:
ooentry = self.objectaccess_set.get(content_type=ct, object_id=obj.id)
@@ -233,6 +278,8 @@ class User(DUser):
:param obj: a admin object
:return: a boolean
"""
+ from modoboa.lib.permissions import get_content_type
+
if self.is_superuser:
return True
@@ -360,8 +407,8 @@ class Domain(DatesAware):
# Not very optimized but currently, this is the simple way
# I've found to delete all related objects (mailboxes, users
# and aliases)!
- for mb in self.mailbox_set.all():
- mb.delete(keepdir=keepdir)
+ # for mb in self.mailbox_set.all():
+ # mb.delete(keepdir=keepdir)
super(Domain, self).delete(*args, **kwargs)
if not keepdir:
self.delete_dir()
@@ -379,14 +426,19 @@ class DomainAlias(DatesAware):
help_text=ugettext_noop("The alias name"))
target = models.ForeignKey(Domain, verbose_name=ugettext_noop('target'),
help_text=ugettext_noop("The domain this alias points to"))
- enabled = models.BooleanField(ugettext_noop('enabled'),
- help_text=ugettext_noop("Check to activate this alias"))
+ enabled = models.BooleanField(
+ ugettext_noop('enabled'),
+ help_text=ugettext_noop("Check to activate this alias")
+ )
class Meta:
permissions = (
("view_domaliases", "View domain aliases"),
)
+ def __unicode__(self):
+ return self.name
+
class Mailbox(DatesAware):
address = models.CharField(
ugettext_noop('address'), max_length=100,
@@ -456,13 +508,14 @@ class Mailbox(DatesAware):
% (abspath, self.mdirroot, dir, sdir))
return True
- def rename_dir(self, domain, newaddress):
+ def rename_dir(self, newdomain, newaddress):
if parameters.get_admin("CREATE_DIRECTORIES") == "yes":
if self.domain.name == domain and newaddress == self.address:
return True
- path = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), domain)
+ oldpath = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), self.domain.name)
+ newpath = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), newdomain)
code = exec_as_vuser("mv %s/%s %s/%s" \
- % (path, self.address, path, newaddress))
+ % (oldpath, self.address, newpath, newaddress))
if code:
self.path = "%s/" % newaddress
return code
@@ -498,8 +551,8 @@ class Mailbox(DatesAware):
user.set_password(kwargs["password"])
try:
user.save()
- except IntegrityError:
- raise AdminError(_("Mailbox with this address already exists"))
+ except IntegrityError, e:
+ raise AdminError(_("Account '%s' already exists" % user.username))
self.user = user
if len(user.groups.all()) == 0:
@@ -538,7 +591,6 @@ class Mailbox(DatesAware):
:param domain: the associated Domain object
:param user: the associated User object
"""
- self.name = "%s %s" % (user.first_name, user.last_name)
self.address = localpart
self.domain = domain
self.user = user
@@ -556,7 +608,7 @@ class Mailbox(DatesAware):
self.gid = v_gid
else:
self.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid
- self.quota = self.domain.quota
+ self.quota = self.domain.quota
super(Mailbox, self).save()
def create_from_csv(self, user, line):
@@ -591,18 +643,21 @@ class Mailbox(DatesAware):
alias.mboxes.remove(self)
if len(alias.mboxes.all()) == 0:
alias.delete()
- self.user.delete()
super(Mailbox, self).delete(*args, **kwargs)
if not keepdir:
self.delete_dir()
class Alias(DatesAware):
- address = models.CharField(ugettext_noop('address'), max_length=254,
- help_text=ugettext_noop("The alias address (without the domain part). For a 'catch-all' address, just enter an * character."))
+ address = models.CharField(
+ ugettext_noop('address'), max_length=254,
+ help_text=ugettext_noop("The alias address (without the domain part). For a 'catch-all' address, just enter an * character.")
+ )
domain = models.ForeignKey(Domain)
- mboxes = models.ManyToManyField(Mailbox, verbose_name=ugettext_noop('mailboxes'),
- help_text=ugettext_noop("The mailboxes this alias points to"))
+ mboxes = models.ManyToManyField(
+ Mailbox, verbose_name=ugettext_noop('mailboxes'),
+ help_text=ugettext_noop("The mailboxes this alias points to")
+ )
extmboxes = models.TextField(blank=True)
enabled = models.BooleanField(ugettext_noop('enabled'),
help_text=ugettext_noop("Check to activate this alias"))
@@ -611,64 +666,43 @@ class Alias(DatesAware):
permissions = (
("view_aliases", "View aliases"),
)
+ unique_together = (("address", "domain"),)
@property
def full_address(self):
return "%s@%s" % (self.address, self.domain.name)
@property
- def targets(self):
- ret = "
".join(map(lambda m: str(m), self.mboxes.all()))
- ret += "
" + self.repr_extmboxes()
- return ret
+ def identity(self):
+ return self.full_address
- def save(self, int_targets, ext_targets, *args, **kwargs):
- if len(ext_targets):
- self.extmboxes = ",".join(ext_targets)
+ @property
+ def name_or_rcpt(self):
+ return "%s, ..." % self.get_recipients()[0]
+
+ def save(self, int_rcpts, ext_rcpts, *args, **kwargs):
+ if len(ext_rcpts):
+ self.extmboxes = ",".join(ext_rcpts)
else:
self.extmboxes = ""
super(Alias, self).save(*args, **kwargs)
curmboxes = self.mboxes.all()
- for t in int_targets:
+ for t in int_rcpts:
if not t in curmboxes:
self.mboxes.add(t)
for t in curmboxes:
- if not t in int_targets:
+ if not t in int_rcpts:
self.mboxes.remove(t)
- def first_target(self):
- """Return the first target of this alias
-
- It is taken from internal addresses, or external addresses.
- """
- if len(self.mboxes.all()):
- return self.mboxes.all()[0]
- if len(self.extmboxes):
- return self.extmboxes.split(",")[0]
- return None
-
- def get_targets(self):
- """Return a list containing targeted mailboxes
+ def get_recipients(self):
+ """Return a the recipients list
Internal and external addresses are mixed into a single list.
"""
- result = []
- if len(self.mboxes.all()):
- for mb in self.mboxes.all()[1:]:
- result += [mb.full_address]
- if len(self.extmboxes):
- result += self.extmboxes.split(",")
- else:
- result = self.extmboxes.split(",")[1:]
+ result = map(lambda mb: mb.full_address, self.mboxes.all())
+ result += self.extmboxes.split(',')
return result
- def repr_extmboxes(self):
- """Return external targets as a string
-
- A ready to print representation of external targets.
- """
- return "
".join(self.extmboxes.split(","))
-
def ui_disabled(self, user):
if user.is_superuser:
return False
@@ -699,11 +733,12 @@ class Extension(models.Model):
module.destroy()
def on(self):
- self.load()
- self.init()
self.enabled = True
self.save()
+ self.load()
+ self.init()
+
extdir = "%s/extensions/%s" % (settings.MODOBOA_DIR, self.name)
scriptdir = "%s/%s" % (extdir, "scripts")
if os.path.exists(scriptdir):
diff --git a/admin/permissions.py b/admin/permissions.py
deleted file mode 100644
index 31b50317d..000000000
--- a/admin/permissions.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# coding: utf-8
-
-import datetime
-from django.utils.translation import ugettext as _, ugettext_noop
-from django.contrib.auth.models import Group
-from modoboa.lib.webutils import _render, getctx, _render_to_string
-from modoboa.lib import events
-from forms import *
-from models import User
-from tables import SuperAdminsTable, DomainAdminsTable
-from lib import grant_access_to_object, ungrant_access_to_object
-
-class Permissions(object):
- submit_label = ugettext_noop("Add")
- formtpl = 'admin/add_permission.html'
-
- @staticmethod
- def is_authorized(user):
- return user.is_superuser
-
- def get_add_form(self, request):
- raise NotImplementedError
-
- def add(self, request):
- raise NotImplementedError
-
- def delete(self, request, selection):
- raise NotImplementedError
-
- def get(self, request):
- raise NotImplementedError
-
- def _render_form(self, request, form, instring=False):
- ctx = dict(form=form, title=self.title, submit_label=self.submit_label,
- role=self.role)
- if instring:
- return _render_to_string(request, self.formtpl, ctx)
- return _render(request, self.formtpl, ctx)
-
-class SuperAdminsPerms(Permissions):
- title = ugettext_noop("Add super administrator")
- role = "super_admins"
-
- def get_add_form(self, request):
- form = SuperAdminForm(request.user)
- return self._render_form(request, form)
-
- def add(self, request):
- form = SuperAdminForm(request.user, request.POST)
- if form.is_valid():
- user = User.objects.get(pk=request.POST["user"])
- user.is_superuser = True
- user.date_joined = datetime.datetime.now()
- events.raiseEvent("SuperAdminPromotion", user)
- user.groups.clear()
- user.save()
- return True, None
-
- content = self._render_form(request, form, True)
- return False, getctx("ko", content=content)
-
- def delete(self, selection):
- for s in selection:
- user = User.objects.get(pk=int(s))
- if user.username == "admin":
- continue
- user.is_superuser = False
- user.save()
-
- def get(self, request):
- admins = User.objects.filter(is_superuser=True)
- return SuperAdminsTable(request, admins).render()
-
-
-class DomainAdminsPerms(Permissions):
- title = ugettext_noop("Add domain administrator")
- role = "domain_admins"
- formtpl = 'admin/add_domain_admin.html'
-
- @staticmethod
- def is_authorized(user):
- return user.has_perm("admin.view_domains")
-
- def get_add_form(self, request):
- form = DomainAdminForm()
- return self._render_form(request, form)
-
- def delete(self, selection):
- dagrp = Group.objects.get(name="DomainAdmins")
- sugrp = Group.objects.get(name="SimpleUsers")
- for uid in selection:
- u = User.objects.get(pk=uid)
- u.groups.remove(dagrp)
- u.save()
- events.raiseEvent("DomainAdminDeleted", u)
- if not len(u.mailbox_set.all()):
- ungrant_access_to_object(u)
- u.delete()
- else:
- u.groups.add(sugrp)
-
- def get(self, request):
- domainadmins = User.objects.filter(groups__name="DomainAdmins")
- domainadmins = filter(lambda da: request.user.is_owner(da), domainadmins)
- return DomainAdminsTable(request, domainadmins).render()
-
-def get_perms_class(user, role):
- """Retrieves the class associated to a given role
-
- The provided user must have the required permission to retrieve
- this class.
-
- If "role" is one of those provided by the admin component, its
- class is directly returned. Otherwise, a "PermsGetClass" event is
- raised to check if one of the active plugins provide the desired
- class.
-
- :param user: a User object
- :param role: the role to look for
- """
- parts = role.split("_")
- cname = ""
- for p in parts:
- cname += p.capitalize()
- cname += "Perms"
- if globals().has_key(cname):
- if globals()[cname].is_authorized(user):
- return globals()[cname]
- return None
- res = events.raiseQueryEvent("PermsGetClass", role)
- if not len(res):
- return None
- return res[0]
diff --git a/admin/tables.py b/admin/tables.py
index 4e6f7f182..b8c9cc304 100644
--- a/admin/tables.py
+++ b/admin/tables.py
@@ -26,7 +26,7 @@ class DomainsTable(tables.Table):
cols_order = ["name", "creation", "modified",
"domaliases", "mboxes", "mbaliases",
- "quota", "enabled", "actions"]
+ "quota", "actions"]
def __init__(self, request, doms):
super(DomainsTable, self).__init__(request)
@@ -38,91 +38,10 @@ class DomainsTable(tables.Table):
def parse_enabled(self, value):
return _("yes") if value else _("no")
-class DomaliasesTable(tables.Table):
- tableid = "objects_table"
- idkey = "id"
-
- name = tables.Column("name", label=ugettext_noop("Name"))
- target = tables.Column("target", label=ugettext_noop("Targeted domain"))
- creation = tables.Column("creation", label=ugettext_noop("Created"), width="170px")
- modified = tables.Column("last_modification", label=ugettext_noop("Last modified"),
- width="170px")
- enabled = tables.Column("enabled", label=gender("Enabled", "m"), width="50px",
- align="center")
- actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"),
- width="50px", align="center",
- defvalue=domalias_actions)
-
- cols_order = ["name", "target", "creation",
- "modified", "enabled", "actions"]
-
- def __init__(self, request, domaliases):
- super(DomaliasesTable, self).__init__(request)
- self.populate(self._rows_from_model(domaliases))
-
- def parse_enabled(self, value):
- return _("yes") if value else _("no")
-
-class MailboxesTable(tables.Table):
- tableid = "objects_table"
- idkey = "id"
-
- address = tables.Column("full_address", label=ugettext_noop("Address"))
- name = tables.Column("user", label=ugettext_noop("Name"))
- creation = tables.Column("creation", label=ugettext_noop("Created"),
- width="160px")
- modified = tables.Column("last_modification", label=ugettext_noop("Last modified"),
- width="160px")
- aliases = tables.Column("alias_count", label=ugettext_noop("Aliases"), width="70px",
- align="center")
- quota = tables.Column("quota", label=ugettext_noop("Quota"),
- width="50px", align="center")
- enabled = tables.Column("enabled", label=gender("Enabled", "f"), width="50px",
- align="center")
- actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"),
- width="70px", align="center", defvalue=mailbox_actions)
-
- cols_order = ["address", "name", "creation", "modified", "aliases",
- "quota", "enabled", "actions"]
-
- def __init__(self, request, mailboxes):
- super(MailboxesTable, self).__init__(request)
- self.populate(self._rows_from_model(mailboxes))
-
- def parse_quota(self, value):
- return "%s %s" % (value, _("MB"))
-
- def parse_enabled(self, value):
- return _("yes") if value else _("no")
-
- def parse_name(self, user):
- return "%s %s" % (user.first_name, user.last_name)
-
-class MbaliasesTable(tables.Table):
- tableid = "objects_table"
- idkey = "id"
-
- address = tables.Column("full_address", label=ugettext_noop("Address"))
- targets = tables.Column("targets", label=ugettext_noop("Target(s)"), safe=True)
- creation = tables.Column("creation", label=ugettext_noop("Created"), width="170px")
- modified = tables.Column("last_modification", label=ugettext_noop("Last modified"),
- width="170px")
- enabled = tables.Column("enabled", label=gender("Enabled", "m"), width="50px",
- align="center")
- actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"), width="50px",
- align="center", defvalue=mbalias_actions)
-
- cols_order = ["address", "targets", "creation", "modified", "enabled", "actions"]
-
- def __init__(self, request, aliases):
- super(MbaliasesTable, self).__init__(request)
- self.populate(self._rows_from_model(aliases))
-
- def rowoptions(self, request, alias):
- return "{ui_disabled : '%s'}" % alias.ui_disabled(request.user)
-
- def parse_enabled(self, value):
- return _("yes") if value else _("no")
+ def row_class(self, request, domain):
+ if not domain.enabled:
+ return "muted"
+ return ""
class ExtensionsTable(tables.Table):
idkey = "id"
@@ -133,43 +52,55 @@ class ExtensionsTable(tables.Table):
cols_order = ["selection", "name", "version", "descr"]
-class SuperAdminsTable(tables.Table):
+class AccountsTable(tables.Table):
idkey = "id"
- selection = tables.SelectionColumn("selection", width="4%")
- username = tables.Column("username", label=ugettext_noop("User name"))
- first_name = tables.Column("first_name", label=ugettext_noop("First name"))
- last_name = tables.Column("last_name", label=ugettext_noop("Last name"))
- date_joined = tables.Column("date_joined", label=ugettext_noop("Defined"))
- enabled = tables.Column("enabled", label=gender("Enabled", "m"), width="10%")
-
- cols_order = ["selection", "username", "first_name", "last_name",
- "date_joined", "enabled"]
-
- def __init__(self, request, users):
- super(SuperAdminsTable, self).__init__(request)
- self.populate(self._rows_from_model(users))
-
- def parse_enabled(self, value):
- return _("yes") if value else _("no")
-
-class DomainAdminsTable(tables.Table):
- idkey = "id"
- selection = tables.SelectionColumn("selection", width="4%")
username = tables.Column("username", label=ugettext_noop("User name"))
first_name = tables.Column("first_name", label=ugettext_noop("First name"))
last_name = tables.Column("last_name", label=ugettext_noop("Last name"))
date_joined = tables.Column("date_joined", label=ugettext_noop("Defined"))
enabled = tables.Column("is_active", label=gender("Enabled", "m"), width="10%")
+ role = tables.Column("group", label=ugettext_noop("Role"))
actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"),
width="70px", align="center",
- defvalue=domain_admin_actions)
+ defvalue=account_actions)
- cols_order = ["selection", "username", "first_name",
- "last_name", "date_joined", "enabled", "actions"]
+ cols_order = ["username", "first_name", "last_name", "date_joined",
+ "role", "actions"]
def __init__(self, request, users):
- super(DomainAdminsTable, self).__init__(request)
+ super(AccountsTable, self).__init__(request)
self.populate(self._rows_from_model(users))
def parse_is_active(self, value):
return _("yes") if value else _("no")
+
+ def parse_group(self, value):
+ if value == "SimpleUsers":
+ return "U"
+ return value[:1]
+
+ def row_class(self, request, obj):
+ if not obj.is_active:
+ return "muted"
+ return ""
+
+class IdentitiesTable(tables.Table):
+ idkey = "id"
+ identity = tables.Column("identity", label=ugettext_noop("Email/Username"))
+ name_or_rcpt = tables.Column("name_or_rcpt", label=ugettext_noop("Fullname/Recipient"))
+ actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"),
+ width="70px", align="center",
+ defvalue=identity_actions)
+
+ cols_order = ["identity", "name_or_rcpt", "actions"]
+
+ def __init__(self, request, identities):
+ super(IdentitiesTable, self).__init__(request)
+ self.populate(self._rows_from_model(identities, True))
+
+ def row_class(self, request, obj):
+ if obj.__class__.__name__ == "User" and not obj.is_active:
+ return "muted"
+ if not obj.enabled:
+ return "muted"
+ return ""
diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py
index e71e665cf..c13daa964 100644
--- a/admin/templatetags/admin_extras.py
+++ b/admin/templatetags/admin_extras.py
@@ -13,15 +13,7 @@ genders = {
}
@register.simple_tag
-def admin_menu(user):
- # entries = [
- # {"name" : "admin",
- # "img" : static_url('pics/admin.png'),
- # "label" : _("Admin"),
- # "class" : "topdropdown",
- # "menu" : []
- # }
- # ]
+def admin_menu(selection, user):
entries = []
if user.has_perm("admin.view_domains"):
entries += [
@@ -30,21 +22,14 @@ def admin_menu(user):
"label" : _("Domains"),
"img" : static_url("pics/domains.png")}
]
- elif user.has_perm("admin.view_mailboxes"):
- entries += [
- {"name" : "mailboxes",
- "url" : reverse(admin.views.domains),
- "label" : _("Mailboxes"),
- "img" : static_url("pics/mailbox.png")}
- ]
entries += \
events.raiseQueryEvent("AdminMenuDisplay", "top_menu", user)
- if user.has_perm("auth.view_permissions"):
+ if user.has_perm("auth.add_user") or user.has_perm("admin.add_mailbox"):
entries += [
- {"name" : "permissions",
- "url" : reverse(admin.views.permissions),
- "label" : _("Accounts"),
- "img" : static_url("pics/permissions.png")}
+ {"name" : "identities",
+ "url" : reverse(admin.views.identities),
+ "label" : _("Identities")},
+
]
if user.is_superuser:
entries += [
@@ -57,193 +42,127 @@ def admin_menu(user):
if not len(entries):
return ""
return render_to_string("common/menulist.html",
- {"entries" : entries, "user" : user})
+ {"entries" : entries, "selection" : selection, "user" : user})
@register.simple_tag
def settings_menu(selection, user):
entries = [
- {"name" : "parameters",
- "url" : reverse(admin.views.viewparameters),
- "img" : static_url("pics/domains.png"),
- "label" : _("Parameters")},
{"name" : "extensions",
"url" : reverse(admin.views.viewextensions),
"label" : _("Extensions"),
"img" : static_url("pics/extensions.png")},
+ {"name" : "parameters",
+ "url" : reverse(admin.views.viewparameters),
+ "img" : static_url("pics/domains.png"),
+ "label" : _("Parameters")},
]
- return render_to_string('common/menu.html',
- {"selection" : selection, "entries" : entries,
- "user" : user})
-
-def dommenu_entry(name, label, img, listurl, newurl):
- result = {"name" : name,
- "label" : label,
- "img" : img,
- "url" : listurl,
- "class" : "menubardropdown",
- }
- return result
+ return render_to_string('common/menu.html', {
+ "entries" : entries,
+ "css" : "nav nav-list",
+ "selection" : selection,
+ "user" : user
+ })
@register.simple_tag
-def domains_menu(selection, user, domid):
- args = domid != "" and ("?domid=%s" % domid) or ""
- entries = []
- if selection == "stats":
- disabled = True
- else:
- disabled = False
-
- entries += [
- {"name" : "actions",
- "label" : _("Actions"),
- "img" : static_url("pics/tools.png"),
- "class" : "menubardropdown",
- "menu" : [
- {"name" : "new",
- "label" : _("New"),
- "img" : static_url("pics/add.png"),
- "disabled" : disabled},
- {"name" : "remove",
- "label" : _("Remove"),
- "img" : static_url("pics/remove.png"),
- "disabled" : disabled
- },
- {"name" : "import",
- "label" : _("Import data"),
- "img" : static_url("pics/import.png"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:370,y:250}}",
- "url" : reverse(admin.views.importdata)}
- ]
- },
- ]
-
- entries += [{"separator" : True}]
- if user.has_perm("admin.view_domains"):
- entries += dommenu_entry("domains", _("Domains"),
- static_url("pics/domains.png"),
- reverse(admin.views.domains),
- reverse(admin.views.newdomain)),
- entries += [
- dommenu_entry("domaliases", _("Domain aliases"), static_url("pics/alias.png"),
- reverse(admin.views.domaliases) + args,
- reverse(admin.views.newdomalias) + args),
- dommenu_entry("mailboxes", _("Mailboxes"), static_url("pics/mailbox.png"),
- reverse(admin.views.mailboxes) + args,
- reverse(admin.views.newmailbox) + args),
- dommenu_entry("mbaliases", _("Mailbox aliases"), static_url("pics/alias.png"),
- reverse(admin.views.mbaliases) + args,
- reverse(admin.views.newmbalias) + args),
- ]
- entries += events.raiseQueryEvent("AdminMenuDisplay", "admin_menu_box", user)
- return render_to_string('common/menu.html',
- {"entries" : entries,
- "selection" : selection,
- "user" : user})
-
-@register.simple_tag
-def permissions_menu(user):
+def domains_menu(selection, user):
entries = [
- {"name" : "addperm",
- "label" : _("Add permission"),
- "img" : static_url("pics/add.png"),
- "url" : reverse(admin.views.add_permission)},
- {"name" : "delperms",
- "label" : _("Remove permissions"),
- "img" : static_url("pics/remove.png"),
- "url" : reverse(admin.views.delete_permissions)},
+ {"name" : "newdomain",
+ "label" : _("New domain"),
+ "img" : "icon-plus",
+ "modal" : True,
+ "modalcb" : "domainform_cb",
+ "url" : reverse("modoboa.admin.views.newdomain")},
+ {"name" : "import",
+ "label" : _("Import"),
+ "img" : "icon-folder-open",
+ "url" : reverse(admin.views.import_domains),
+ "modal" : True,
+ "modalcb" : "importform_cb"}
]
- return render_to_string('common/menu.html',
- {"entries" : entries, "user" : user})
+ return render_to_string('common/menu.html', {
+ "entries" : entries,
+ "css" : "nav nav-list",
+ "selection" : selection,
+ "user" : user
+ })
+
+@register.simple_tag
+def identities_menu(user):
+ entries = [
+ {"name" : "newaccount",
+ "label" : _("New account"),
+ "img" : "icon-plus",
+ "modal" : True,
+ "modalcb" : "newaccount_cb",
+ "url" : reverse(admin.views.newaccount)},
+ {"name" : "newdlist",
+ "label" : _("New distribution list"),
+ "img" : "icon-plus",
+ "modal" : True,
+ "modalcb" : "dlistform_cb",
+ "url" : reverse(admin.views.newdlist)},
+ {"name" : "import",
+ "label" : _("Import"),
+ "img" : "icon-folder-open",
+ "url" : reverse(admin.views.import_identities),
+ "modal" : True,
+ "modalcb" : "importform_cb"}
+ ]
+
+ return render_to_string('common/menu.html', {
+ "entries" : entries,
+ "css" : "nav nav-list",
+ "user" : user
+ })
@register.simple_tag
def domain_actions(user, domid):
actions = [
{"name" : "editdomain",
"url" : reverse(admin.views.editdomain, args=[domid]),
- "img" : static_url("pics/edit.png"),
+ "img" : "icon-edit",
"title" : _("Edit domain"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:310,y:190}}"},
- {"name" : "domaliases",
- "url" : reverse(admin.views.domaliases) + "?domid=%s" % domid,
- "img" : static_url("pics/alias.png"),
- "title" : _("View aliases of this domain")}
+ "modal" : True,
+ "modalcb" : "domainform_cb"},
+ {"name" : "deldomain",
+ "url" : reverse(admin.views.deldomain) + "?selection=%s" % domid,
+ "img" : "icon-remove"},
]
- if user.has_perm('admin.view_domains'):
- actions += [
- {"name" : "viewadmins",
- "url" : reverse(admin.views.view_domain_admins, args=[domid]),
- "img" : static_url("pics/administrators.png"),
- "title" : _("View administrators of this domain"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:310,y:190}}"},
- ]
+
return render_actions(actions)
@register.simple_tag
-def domalias_actions(user, aid):
+def account_actions(user, accountid):
actions = [
- {"name" : "editalias",
- "url" : reverse(admin.views.editdomalias, args=[aid]),
- "img" : static_url("pics/edit.png"),
- "title" : _("Edit alias"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:310,y:190}}"}
+ {"name" : "editaccount",
+ "url" : reverse(admin.views.editaccount, args=[accountid]),
+ "img" : "icon-edit",
+ "modal" : True,
+ "modalcb" : "editaccount_cb",
+ "title" : _("Edit account")},
+ {"name" : "delaccount",
+ "url" : reverse(admin.views.delaccount) + "?selection=%s" % accountid,
+ "img" : "icon-remove",
+ "title" : _("Delete this account")},
]
return render_actions(actions)
@register.simple_tag
-def mailbox_actions(user, mboxid):
+def identity_actions(user, iid):
+ name, objid = iid.split(':')
+ if name == "User":
+ return account_actions(user, objid)
actions = [
- {"name" : "editmailbox",
- "url" : reverse(admin.views.editmailbox, args=[mboxid]),
- "img" : static_url("pics/edit.png"),
- "title" : _("Edit mailbox"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:310,y:320}}"},
- {"name" : "aliases",
- "url" : reverse(admin.views.mbaliases) + "?mbid=%d" % mboxid,
- "img" : static_url("pics/alias.png"),
- "title" : _("View this mailbox aliases")},
- ]
- return render_actions(actions)
-
-@register.simple_tag
-def domain_admin_actions(user, daid):
- from modoboa.admin.models import User
-
- actions = [
- {"name" : "editdomainadmin",
- "url" : reverse(admin.views.edit_domain_admin, args=[daid]),
- "img" : static_url("pics/edit.png"),
- "title" : _("Edit domain admin"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:350,y:320}}"},
- {"name" : "assigndomains",
- "url" : reverse(admin.views.assign_domains_to_admin, args=[daid]),
- "img" : static_url("pics/domains.png"),
- "title" : _("Assign domain(s) to this administrator"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:350,y:320}}"},
- ]
- actions += events.raiseQueryEvent("DomainAdminActions", User.objects.get(pk=daid))
- return render_actions(actions)
-
-@register.simple_tag
-def mbalias_actions(user, aliasid):
- from modoboa.admin.models import Alias
-
- alias = Alias.objects.get(pk=aliasid)
- if alias.ui_disabled(user):
- return "--"
- actions = [
- {"name" : "editmbalias",
- "url" : reverse(admin.views.editmbalias, args=[aliasid]),
- "img" : static_url("pics/edit.png"),
- "title" : _("Edit mailbox alias"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:320,y:300}}"}
+ {"name" : "editdlist",
+ "url" : reverse(admin.views.editdlist, args=[objid]),
+ "img" : "icon-edit",
+ "modal" : True,
+ "modalcb" : "dlistform_cb",
+ "title" : _("Edit distribution list")},
+ {"name" : "deldlist",
+ "url" : reverse(admin.views.deldlist) + "?selection=%s" % objid,
+ "img" : "icon-remove",
+ "title" : _("Delete this distribution list")},
]
return render_actions(actions)
@@ -255,9 +174,11 @@ def loadadminextmenu(user):
@register.simple_tag
def param(app, definition):
- result = """
-
""" % (definition.has_key("label") \
- and _(definition["label"]) or definition["name"])
+ result = """
+
+
""" \
+ % (definition.has_key("label") and _(definition["label"])
+ or definition["name"])
name = "%s.%s" % (app, definition["name"])
value = definition.has_key("value") \
and definition["value"] or definition["deflt"]
@@ -290,10 +211,14 @@ def param(app, definition):
if definition.has_key("help"):
result += """
-
-
- """ % (_(definition["help"]), _("Help"), static_url("pics/info.png"))
+
%s
+""" % definition["help"]
+ # result += """
+ #
+ #
+ # """ % (_(definition["help"]), _("Help"), static_url("pics/info.png"))
result += """
+
"""
return result
diff --git a/admin/urls.py b/admin/urls.py
index a47f8a591..5586301a2 100644
--- a/admin/urls.py
+++ b/admin/urls.py
@@ -4,40 +4,31 @@ urlpatterns = patterns(
'modoboa.admin.views',
url(r'^$', 'domains', name="index"),
url(r'^domains/$', 'domains', name="domains"),
+ (r'^domains/list/$', 'domains_list'),
(r'^domains/new/', 'newdomain'),
(r'^domains/(?P
\d+)/edit/$', 'editdomain'),
(r'^domains/delete/$', 'deldomain'),
- (r'^domains/import/$', 'importdata'),
+ (r'^domains/import/$', 'import_domains'),
- (r'^domaliases/$', "domaliases"),
- (r'^domaliases/new/', 'newdomalias'),
- (r'^domaliases/edit/(?P\d+)/', 'editdomalias'),
- (r'^domaliases/delete/$', 'deldomalias'),
-
- (r'^mailboxes/$', 'mailboxes'),
- (r'^mailboxes/new/$', 'newmailbox'),
- (r'^mailboxes/edit/(?P\d+)/$', 'editmailbox'),
- (r'^mailboxes/delete/$', 'delmailbox'),
(r'^mailboxes/search/$', 'mailboxes_search'),
- (r'^mbaliases/$', 'mbaliases'),
- (r'^mbaliases/new/$', 'newmbalias'),
- (r'^mbaliases/edit/(?P\d+)/$', 'editmbalias'),
- (r'^mbaliases/delete/$', 'delmbalias'),
-
(r'^settings/$', 'viewparameters'),
(r'^settings/parameters/$', 'viewparameters'),
(r'^settings/parameters/save/$', 'saveparameters'),
(r'^settings/extensions/$', 'viewextensions'),
(r'^settings/extensions/save/$', 'saveextensions'),
- (r'^permissions/$', 'permissions'),
- (r'^permissions/add/$', 'add_permission'),
- (r'^permissions/domainadmin/new/$', 'create_domain_admin'),
- (r'^permissions/domainadmin/edit/(?P\d+)/$', 'edit_domain_admin'),
- (r'^permissions/domainadmin/promote/$', 'domain_admin_promotion'),
- (r'^permissions/domainadmin/assign/(?P\d+)/$', 'assign_domains_to_admin'),
- (r'^permissions/domainadmins/(?P\d+)/$', 'view_domain_admins'),
(r'^permissions/searchaccount/$', 'search_account'),
- (r'^permissions/delete/$', 'delete_permissions'),
+
+ (r'^identities/$', 'identities'),
+ (r'^identities/import/$', 'import_identities'),
+
+ (r'^accounts/list/$', 'accounts_list'),
+ (r'^accounts/new/$', 'newaccount'),
+ (r'^accounts/edit/(?P\d+)/$', 'editaccount'),
+ (r'^accounts/delete/$', 'delaccount'),
+
+ (r'^distriblists/new/$', 'newdlist'),
+ (r'^distriblists/edit/(?P\d+)/$', 'editdlist'),
+ (r'^distriblists/delete/$', 'deldlist'),
)
diff --git a/admin/views.py b/admin/views.py
index 925d0e681..942e22059 100644
--- a/admin/views.py
+++ b/admin/views.py
@@ -1,61 +1,61 @@
-# -*- coding: utf-8 -*-
+# coding: utf-8
from django.http import HttpResponse, HttpResponseRedirect
-from django.utils import simplejson
from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators \
import login_required, permission_required, user_passes_test
from django.db.models import Q
-from django.contrib.auth.models import Group
from django.contrib import messages
-from django.db import transaction, IntegrityError, transaction
+from django.db import transaction, IntegrityError
-from lib import *
+from lib import render_listing
+from forms import *
from modoboa import admin, userprefs
-from models import *
-from modoboa.admin.permissions import *
+from models import *
from modoboa.admin.tables import *
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import *
from modoboa.lib.webutils \
- import _render, ajax_response, ajax_simple_response, getctx
+ import _render, ajax_response, ajax_simple_response
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.models import Parameter
-import copy
+from modoboa.lib.permissions import *
@login_required
def domains(request):
if not request.user.has_perm("admin.view_domains"):
if request.user.has_perm("admin.view_mailboxes"):
- return HttpResponseRedirect(reverse(mailboxes))
+ return HttpResponseRedirect(reverse(identities))
return HttpResponseRedirect(reverse(userprefs.views.preferences))
domains = request.user.get_domains()
for dom in domains:
dom.mbalias_counter = len(Alias.objects.filter(domain=dom.id))
- deloptions = {"keepdir" : _("Do not delete domain directory")}
return render_listing(request, "domains",
"admin/domains.html",
- title=_("Available domains"),
- objects=domains,
- rel="310 190",
- deloptions=deloptions)
+ objects=domains)
-def _validate_domain(request, form, successmsg, commonctx,
- callback=None, tplname="admin/adminform.html"):
- """Domain validation
+@login_required
+@permission_required("admin.view_domains")
+def domains_list(request):
+ doms = map(lambda dom: dom.name, request.user.get_domains())
+ return ajax_simple_response(doms)
+
+def _validate_domain(request, form, successmsg, commonctx, tplname,
+ callback=None):
+ """Domain form validation
Common function shared between creation and modification actions.
"""
error = None
if form.is_valid():
try:
- domain = form.save()
+ domain = form.save(request.user)
except AdminError, e:
error = str(e)
except IntegrityError:
- error = _("Domain with this name already defined")
+ error = _("An alias with this name already exists")
else:
if callback is not None:
callback(request.user, domain)
@@ -69,7 +69,7 @@ def _validate_domain(request, form, successmsg, commonctx,
@login_required
@permission_required("admin.add_domain")
@transaction.commit_on_success
-def newdomain(request, tplname="admin/adminform.html"):
+def newdomain(request):
events.raiseEvent("CanCreate", request.user, "domains")
def newdomain_cb(user, domain):
grant_access_to_object(user, domain, is_owner=True)
@@ -82,15 +82,15 @@ def newdomain(request, tplname="admin/adminform.html"):
if request.method == "POST":
form = DomainForm(request.POST)
return _validate_domain(request, form, _("Domain created"), commonctx,
- newdomain_cb)
-
+ "admin/domainform.html",
+ callback=newdomain_cb)
form = DomainForm()
commonctx["form"] = form
- return _render(request, tplname, commonctx)
+ return _render(request, "common/generic_modal_form.html", commonctx)
@login_required
@permission_required("admin.change_domain")
-def editdomain(request, dom_id, tplname="admin/adminform.html"):
+def editdomain(request, dom_id, tplname="admin/domainform.html"):
domain = Domain.objects.get(pk=dom_id)
if not request.user.is_owner(domain):
raise PermDeniedException(_("You can't edit this domain"))
@@ -101,7 +101,7 @@ def editdomain(request, dom_id, tplname="admin/adminform.html"):
"formid" : "domform"}
if request.method == "POST":
form = DomainForm(request.POST, instance=domain)
- return _validate_domain(request, form, _("Domain modified"), commonctx)
+ return _validate_domain(request, form, _("Domain modified"), commonctx, tplname)
form = DomainForm(instance=domain)
commonctx["form"] = form
@@ -120,11 +120,13 @@ def deldomain(request):
mb = None
for dom in Domain.objects.filter(id__in=selection):
- if not request.user.is_owner(dom):
+ if not request.user.can_access(dom):
raise PermDeniedException(_("You can't delete this domain"))
if mb and mb.domain == dom:
raise AdminError(_("You can't delete your own domain"))
+ events.raiseEvent("DomainAliasDeleted", dom.domainalias_set.all())
+ events.raiseEvent("DeleteMailbox", dom.mailbox_set.all())
events.raiseEvent("DeleteDomain", dom)
ungrant_access_to_object(dom)
dom.delete(keepdir=keepdir)
@@ -133,126 +135,6 @@ def deldomain(request):
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
-@login_required
-@permission_required("admin.view_domaliases")
-@check_domain_access
-def domaliases(request):
- if request.GET.has_key("domid"):
- domain = Domain.objects.get(pk=request.GET["domid"])
- domaliases = DomainAlias.objects.filter(target=request.GET["domid"])
- else:
- domain = None
- domaliases = request.user.get_domaliases()
- return render_listing(request, "domaliases",
- title=_("Domain aliases"),
- rel="310 190",
- objects=domaliases)
-
-@login_required
-@permission_required("admin.add_domainalias")
-@transaction.commit_on_success
-def newdomalias(request, tplname="admin/adminform.html"):
- events.raiseEvent("CanCreate", request.user, "domain_aliases")
- commonctx = {"title" : _("New domain alias"),
- "submit_label" : _("Create"),
- "action" : reverse(newdomalias),
- "formid" : "domaliasform"}
- if request.method == "POST":
- form = DomainAliasForm(request.user, request.POST)
- error = None
- if form.is_valid():
- domalias = form.save(commit=False)
- if Domain.objects.filter(name=domalias.name):
- error = _("A domain with this name already exists")
- else:
- try:
- domalias.save()
- except IntegrityError:
- error = _("Alias with this name already exists")
- else:
- grant_access_to_object(request.user, domalias, is_owner=True)
- events.raiseEvent("DomainAliasCreated", request.user, domalias)
- messages.info(request, _("Domain alias created"), fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.domaliases))
-
- commonctx["form"] = form
- commonctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **commonctx)
-
- form = DomainAliasForm(request.user)
- if request.GET.has_key("domid"):
- form.fields["target"].initial = request.GET["domid"]
- commonctx["form"] = form
- return _render(request, tplname, commonctx)
-
-@login_required
-@permission_required("admin.change_domainalias")
-def editdomalias(request, alias_id, tplname="admin/adminform.html"):
- domalias = DomainAlias.objects.get(pk=alias_id)
- if not request.user.is_owner(domalias.target):
- raise PermDeniedException(_("You do not have access to this domain"))
-
- commonctx = {"title" : _("Domain alias editing"),
- "submit_label" : _("Update"),
- "action" : reverse(editdomalias, args=[alias_id]),
- "formid" : "domaliasform"}
- if request.method == "POST":
- form = DomainAliasForm(request.user, request.POST, instance=domalias)
- error = None
- if form.is_valid():
- ndomalias = form.save(commit=False)
- if Domain.objects.filter(name=ndomalias.name):
- error = _("A domain with this name already exists")
- else:
- try:
- ndomalias.save()
- except IntegrityError:
- error = _("Alias with this name already exists")
- else:
- messages.info(request, _("Domain alias updated"), fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.domaliases))
- commonctx["form"] = form
- commonctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **commonctx)
-
- form = DomainAliasForm(request.user, instance=domalias)
- commonctx["form"] = form
- return _render(request, tplname, commonctx)
-
-@login_required
-@permission_required("admin.delete_domainalias")
-@transaction.commit_on_success
-def deldomalias(request):
- selection = request.GET["selection"].split(",")
- for daid in selection:
- domalias = DomainAlias.objects.get(pk=daid)
- if not request.user.is_owner(domalias.target):
- raise PermDeniedException(_("You don't have acces to this domain"))
- events.raiseEvent("DomainAliasDeleted", domalias)
- ungrant_access_to_object(domalias)
- domalias.delete()
- msg = ungettext("Domain alias deleted", "Domain aliases deleted", len(selection))
- messages.info(request, msg, fail_silently=True)
- return ajax_response(request)
-
-@login_required
-@permission_required("admin.view_mailboxes")
-@check_domain_access
-def mailboxes(request):
- title = _("Available mailboxes")
- if request.GET.has_key("domid"):
- domain = Domain.objects.get(pk=request.GET["domid"])
- mboxes = Mailbox.objects.filter(domain=domain.id)
- title += " (%s)" % domain.name
- else:
- mboxes = request.user.get_mailboxes()
- domain = None
- deloptions = {"keepdir" : _("Do not delete mailbox directory")}
- return render_listing(request, "mailboxes",
- title=title,
- rel="310 320",
- objects=mboxes, domain=domain, deloptions=deloptions)
-
@login_required
@permission_required("admin.view_mailboxes")
@check_domain_access
@@ -271,135 +153,14 @@ def mailboxes_search(request):
result = map(lambda mb: mb.full_address, mboxes)
return ajax_simple_response(result)
-@login_required
-@permission_required("admin.add_mailbox")
-@transaction.commit_on_success
-def newmailbox(request, tplname="admin/adminform.html"):
- events.raiseEvent("CanCreate", request.user, "mailboxes")
- commonctx = {"title" : _("New mailbox"),
- "submit_label" : _("Create"),
- "action" : reverse(newmailbox),
- "formid" : "mboxform"}
- if request.method == "POST":
- form = MailboxWithPasswordForm(request.user, request.POST)
- error = None
- if form.is_valid():
- if not request.user.is_superuser:
- if not request.user.can_access(form.cleaned_data["domain"]):
- raise PermDeniedException(_("You do not have access to this domain"))
-
- try:
- mb = form.save()
- except AdminError as e:
- error = str(e)
- else:
- grant_access_to_object(request.user, mb, is_owner=True)
- grant_access_to_object(request.user, mb.user, is_owner=True)
- events.raiseEvent("CreateMailbox", request.user, mb)
- messages.info(request, _("Mailbox created"),
- fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.mailboxes) + "?domid=%d" % mb.domain.id)
-
- commonctx["form"] = form
- commonctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **commonctx)
-
- form = MailboxWithPasswordForm(request.user)
- commonctx["form"] = form
- return _render(request, tplname, commonctx)
-
-@login_required
-@permission_required("admin.change_mailbox")
-def editmailbox(request, mbox_id=None, tplname="admin/adminform.html"):
- mb = Mailbox.objects.get(pk=mbox_id)
- if not request.user.can_access(mb.domain):
- raise PermDeniedException(_("You do not have access to this domain"))
-
- commonctx = {"title" : _("Mailbox editing"),
- "submit_label" : _("Create"),
- "action" : reverse(editmailbox, args=[mbox_id]),
- "formid" : "mboxform"}
- if request.method == "POST":
- if request.POST.get("password1", "") != "" or \
- request.POST.get("password2", "") != "":
- formclass = MailboxWithPasswordForm
- else:
- formclass = MailboxForm
- oldmb = copy.deepcopy(mb)
- form = formclass(request.user, request.POST, instance=mb)
- error = None
- if form.is_valid():
- try:
- mb = form.save(commit=False)
- except AdminError, inst:
- error = str(inst)
- else:
- if oldmb.rename_dir(mb.domain.name, mb.address):
- form.commit_save(mb)
- events.raiseEvent("ModifyMailbox", mb, oldmb)
- messages.info(request, _("Mailbox modified"),
- fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.mailboxes) + "?domid=%d" % mb.domain.id)
- error = _("Failed to rename mailbox, check permissions")
-
- commonctx["form"] = form
- commonctx["error"] = error
- return ajax_response(request, status="ko", template=tplname, **commonctx)
-
- form = MailboxWithPasswordForm(request.user, instance=mb)
- commonctx["form"] = form
- return _render(request, tplname, commonctx)
-
-@login_required
-@permission_required("admin.delete_mailbox")
-@transaction.commit_on_success
-def delmailbox(request):
- selection = request.GET["selection"].split(",")
- error = None
- keepdir = True if request.GET.get("keepdir", "false") == "true" else False
- usermb = request.user.mailbox_set.all()[0] \
- if len(request.user.mailbox_set.all()) else None
-
- for mb in Mailbox.objects.filter(id__in=selection):
- if not request.user.can_access(mb.domain):
- raise PermDeniedException(_("You don't have access to this domain"))
- if usermb and usermb == mb:
- raise AdminError(_("You can't delete your own mailbox"))
- events.raiseEvent("DeleteMailbox", mb)
- ungrant_access_to_object(mb)
- ungrant_access_to_object(mb.user)
- mb.delete(keepdir=keepdir)
-
- msg = ungettext("Mailbox deleted", "Mailboxes deleted", len(selection))
- messages.info(request, msg, fail_silently=True)
- return ajax_response(request)
-
-@login_required
-@permission_required("admin.view_aliases")
-@check_domain_access
-def mbaliases(request):
- if request.GET.has_key("domid"):
- aliases = Alias.objects.filter(domain=request.GET["domid"])
- elif request.GET.has_key("mbid"):
- aliases = Alias.objects.filter(mboxes__id=request.GET["mbid"]).distinct()
- else:
- aliases = request.user.get_mbaliases()
-
- return render_listing(request, "mbaliases",
- title=_("Mailbox aliases"),
- rel="320 300",
- objects=aliases)
-
-def _validate_mbalias(request, form, successmsg, tplname, commonctx, callback=None):
- """Mailbox alias validation
+def _validate_dlist(request, form, successmsg, tplname, commonctx, callback=None):
+ """Distribution list validation
Common function shared between creation and modification actions.
"""
error = None
if form.is_valid():
- if not request.POST.has_key("targets"):
- raise AdminError(_("No target defined"))
- form.set_targets(request.user, request.POST.getlist("targets"))
+ form.set_recipients(request.user)
try:
alias = form.save()
except IntegrityError:
@@ -408,8 +169,8 @@ def _validate_mbalias(request, form, successmsg, tplname, commonctx, callback=No
if callback:
callback(request.user, alias)
- messages.info(request, successmsg, fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.mbaliases))
+ messages.info(request, successmsg)
+ return ajax_response(request, url=reverse(admin.views.identities))
if request.POST.has_key("targets"):
targets = request.POST.getlist("targets")
@@ -422,218 +183,184 @@ def _validate_mbalias(request, form, successmsg, tplname, commonctx, callback=No
@login_required
@permission_required("admin.add_alias")
@transaction.commit_on_success
-def newmbalias(request, tplname="admin/mbaliasform.html"):
+def newdlist(request, tplname="common/generic_modal_form.html"):
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
- commonctx = {"title" : _("New mailbox alias"),
+ commonctx = {"title" : _("New distribution list"),
"submit_label" : _("Create"),
- "action" : reverse(newmbalias),
- "formid" : "mbaliasform"}
+ "action" : reverse(newdlist),
+ "formid" : "dlistform"}
+
if request.method == "POST":
- def callback(user, mbalias):
- grant_access_to_object(user, mbalias, is_owner=True)
- events.raiseEvent("MailboxAliasCreated", user, mbalias)
+ def callback(user, dlist):
+ grant_access_to_object(user, dlist, is_owner=True)
+ events.raiseEvent("MailboxAliasCreated", user, dlist)
- form = AliasForm(request.user, request.POST)
- return _validate_mbalias(request, form, _("Mailbox alias created"),
- tplname, commonctx, callback)
+ form = DlistForm(request.POST)
+ return _validate_dlist(request, form, _("Distribution list created"),
+ tplname, commonctx, callback)
- form = AliasForm(request.user)
+ form = DlistForm()
commonctx["form"] = form
- commonctx["noerrors"] = True
- return _render(request, tplname, commonctx)
+ return _render(request, "common/generic_modal_form.html", commonctx)
@login_required
@permission_required("admin.change_alias")
-def editmbalias(request, alias_id, tplname="admin/mbaliasform.html"):
- alias = Alias.objects.get(pk=alias_id)
- if not request.user.is_owner(alias.domain):
+def editdlist(request, dlist_id, tplname="admin/dlistform.html"):
+ dlist = Alias.objects.get(pk=dlist_id)
+ if not request.user.is_owner(dlist.domain):
raise PermDeniedException(_("You do not have access to this domain"))
- commonctx = {"title" : _("Mailbox alias editing"),
+ commonctx = {"title" : dlist.full_address,
"submit_label" : _("Update"),
- "action" : reverse(editmbalias, args=[alias.id]),
- "formid" : "mbaliasform"}
+ "action" : reverse(editdlist, args=[dlist.id]),
+ "formid" : "dlistform"}
if request.method == "POST":
- form = AliasForm(request.user, request.POST, instance=alias)
- return _validate_mbalias(request, form, _("Mailbox alias modified"),
- tplname, commonctx)
+ form = DlistForm(request.POST, instance=dlist)
+ return _validate_dlist(request, form, _("Distribution list modified"),
+ tplname, commonctx)
- form = AliasForm(request.user, instance=alias)
+ form = DlistForm(instance=dlist)
commonctx["form"] = form
- commonctx["targets"] = alias.get_targets()
return _render(request, tplname, commonctx)
@login_required
@permission_required("admin.delete_alias")
@transaction.commit_on_success
-def delmbalias(request):
+def deldlist(request):
selection = request.GET["selection"].split(",")
- for mbaid in selection:
- mbalias = Alias.objects.get(pk=mbaid)
- if not request.user.is_owner(mbalias.domain):
+ for dlist_id in selection:
+ dlist = Alias.objects.get(pk=dlist_id)
+ if not request.user.is_owner(dlist.domain):
raise PermDeniedException(_("You do not have access to this domain"))
- events.raiseEvent("MailboxAliasDeleted", mbalias)
- ungrant_access_to_object(mbalias)
- mbalias.delete()
+ events.raiseEvent("MailboxAliasDeleted", dlist)
+ ungrant_access_to_object(dlist)
+ dlist.delete()
- msg = ungettext("Alias deleted", "Aliases deleted", len(selection))
- messages.info(request, msg, fail_silently=True)
+ msg = ungettext("Distribution list deleted",
+ "Distribution lists deleted", len(selection))
+ messages.info(request, msg)
return ajax_response(request)
@login_required
-@permission_required("auth.view_permissions")
-def permissions(request, tplname='admin/permissions.html'):
- permtables = []
- if request.user.is_superuser:
- permtables += [
- {"id" : "super_admins",
- "title" : _("Super administrators"),
- "rel" : "300 140",
- "content" : SuperAdminsPerms().get(request)}
- ]
-
- permtables += events.raiseQueryEvent("PermsGetTables", request)
-
- permtables += [
- {"id" : "domain_admins",
- "title" : _("Domain administrators"),
- "rel" : "350 100",
- "content" : DomainAdminsPerms().get(request)}
- ]
-
- return _render(request, tplname, {
- "permtables" : permtables
- })
+@user_passes_test(lambda u: u.has_perm("auth.add_user") or u.has_perm("admin.add_mailbox"))
+def identities(request, tplname='admin/identities.html'):
+ accounts_list = []
+ for account in User.objects.all():
+ if request.user.can_access(account) or \
+ account.has_mailbox and request.user.can_access(account.mailbox_set.all()[0].domain):
+ accounts_list += [account]
+ for mbalias in Alias.objects.all():
+ if len(mbalias.get_recipients()) > 2:
+ accounts_list += [mbalias]
+ return render_listing(request, "identities", tplname, objects=accounts_list)
@login_required
-@permission_required("auth.view_permissions")
-def add_permission(request):
- role = request.method == "GET" and request.GET["role"] or request.POST["role"]
- pclass = get_perms_class(request.user, role)
- if pclass is None:
- messages.error(request, _("Permission denied!"),
- fail_silently=True)
- else:
- events.raiseEvent("CanCreate", request.user, role)
- pobj = pclass()
- if request.method == "GET":
- return pobj.get_add_form(request)
- status, data = pobj.add(request)
- if not status:
- return HttpResponse(simplejson.dumps(data), mimetype="application/json")
- try:
- msg = pobj.add_success_msg
- except AttributeError:
- msg = _("Permission added")
- messages.info(request, msg, fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.permissions))
+def accounts_list(request):
+ accs = User.objects.filter(is_superuser=False).exclude(groups__name='SimpleUsers')
+ res = map(lambda a: a.username, accs.all())
+ return ajax_simple_response(res)
@login_required
-@permission_required("auth.view_permissions")
-def create_domain_admin(request, tplname="admin/create_domain_admin.html"):
- events.raiseEvent("CanCreate", request.user, "domain_admins")
- if request.method == "POST":
- form = UserWithPasswordForm(request.POST)
- if form.is_valid():
- user = form.save(group="DomainAdmins")
- grant_access_to_object(request.user, user, is_owner=True)
- events.raiseEvent("DomainAdminCreated", user)
- messages.info(request, _("Domain admin created"), fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.permissions))
-
- return ajax_response(request, status="ko", template=tplname, form=form)
-
- form = UserWithPasswordForm()
- return ajax_response(request, template=tplname, form=form)
-
-@login_required
-@permission_required("auth.view_permissions")
-def edit_domain_admin(request, da_id, tplname="admin/adminform.html"):
- ctx = dict(title=_("Edit domain admin"),
- action=reverse(edit_domain_admin, args=[da_id]),
- formid="eda_form",
- submit_label=_("Update"))
- u = User.objects.get(pk=da_id, groups__name="DomainAdmins")
- if request.method == "POST":
- if request.POST.get("password1", "") != "" and \
- request.POST.get("password2", "") != "":
- form = UserWithPasswordForm(request.POST, instance=u)
- else:
- form = UserForm(request.POST, instance=u)
- if form.is_valid():
- form.save()
- if form.cleaned_data["createmb"]:
- pass
- messages.info(request, _("Domain admin updated"), fail_silently=True)
- return ajax_response(request,
- url=reverse("modoboa.admin.views.permissions"))
- ctx.update(form=form)
- return ajax_response(request, status="ko", template=tplname, **ctx)
-
- form = UserWithPasswordForm(instance=u)
- ctx.update(form=form)
- return _render(request, tplname, ctx)
-
-@login_required
-@permission_required("auth.view_permissions")
-def domain_admin_promotion(request, tplname="admin/domain_admin_promotion.html"):
- if request.method == "POST":
- form = DomainAdminPromotionForm(request.POST)
- if form.is_valid():
- u = User.objects.get(username=form.cleaned_data["name"])
- u.groups.add(Group.objects.get(name="DomainAdmins"))
- u.save()
- if not request.user.can_access(u):
- # The only case where we need to grant access to the
- # object is when ``u`` has been create by a super
- # admin
- grant_access_to_object(request.user, u, is_owner=True)
- events.raiseEvent("DomainAdminCreated", u)
- messages.info(request, _("Domain admin added"), fail_silently=True)
- return ajax_response(request, url=reverse(admin.views.permissions))
-
- return ajax_response(request, status="ko", template=tplname, form=form)
-
- form = DomainAdminPromotionForm()
- return ajax_response(request, template=tplname, form=form)
-
-@login_required
-@permission_required("auth.view_permissions")
@transaction.commit_on_success
-def assign_domains_to_admin(request, da_id, tplname="admin/adminform.html"):
- da = User.objects.get(pk=da_id)
- ctx = dict(title=_("Assign domains to %s" % da.username),
- action=reverse(assign_domains_to_admin, args=[da_id]),
- formid="assignform",
- submit_label=_("Assign"))
+def newaccount(request, tplname='admin/newaccount.html'):
+ from modoboa.lib.formutils import CreationWizard
+
+ def create_account(steps):
+ """Account creation callback
+
+ Called when all the creation steps have been validated.
+
+ :param steps: the steps data
+ """
+ genform = steps[0]["form"]
+ genform.is_valid()
+ account = genform.save()
+ grant_access_to_object(request.user, account, is_owner=True)
+ events.raiseEvent("AccountCreated", account)
+
+ mailform = steps[1]["form"]
+ mailform.save(request.user, account)
+
+ ctx = dict(
+ title=_("New account"),
+ action=reverse(newaccount),
+ formid="newaccount_form",
+ submit_label=_("Create")
+ )
+ cwizard = CreationWizard(create_account)
+ cwizard.add_step(AccountFormGeneralPwd,
+ buttons=[dict(classes="btn-inverse next", label=_("Next"))],
+ new_args=[request.user])
+ cwizard.add_step(AccountFormMail,
+ buttons=[dict(classes="btn-primary submit", label=_("Create")),
+ dict(classes="btn-inverse prev", label=_("Previous"))])
+
if request.method == "POST":
- form = AssignDomainsForm(request.user, da, request.POST)
- if form.is_valid():
- current_domains = da.get_domains()
- for domain in form.cleaned_data["domains"]:
- if not domain in current_domains:
- grant_access_to_object(da, domain)
- else:
- current_domains.remove(domain)
- for domain in current_domains:
- ungrant_access_to_object(domain, da)
- msg = ungettext("Domain assigned", "Domains assigned",
- len(form.cleaned_data["domains"]))
- messages.info(request, msg)
- return ajax_response(request, url=reverse(admin.views.permissions))
- ctx.update(form=form)
+ retcode, data = cwizard.validate_step(request)
+ if retcode == -1:
+ raise AdminError(data)
+ if retcode == 1:
+ return ajax_simple_response(dict(status="ok"))
+ if retcode == 2:
+ messages.info(request, _("Account created"))
+ return ajax_simple_response(dict(status="ok"))
+ ctx.update(steps=cwizard.steps)
return ajax_response(request, status="ko", template=tplname, **ctx)
- form = AssignDomainsForm(request.user, da)
- ctx.update(form=form)
+ cwizard.create_forms()
+ ctx.update(steps=cwizard.steps)
return _render(request, tplname, ctx)
@login_required
-@permission_required("auth.view_permissions")
-def view_domain_admins(request, dom_id, tplname="admin/view_domain_admins.html"):
- domain = Domain.objects.get(pk=dom_id)
- return _render(request, tplname, {
- "domain" : domain
- })
+@transaction.commit_on_success
+def editaccount(request, accountid, tplname="common/tabforms.html"):
+ account = User.objects.get(pk=accountid)
+ mb = account.mailbox_set.all()[0] if account.has_mailbox else None
+ instances = dict(general=account, mail=mb, perms=account)
+ events.raiseEvent("FillAccountInstances", request.user, account, instances)
+
+ ctx = dict(
+ title=account.username,
+ action=reverse(editaccount, args=[accountid]),
+ submit_label=_("Update")
+ )
+
+ if request.method == "POST":
+ classes = {}
+ if request.POST.get("password1", "") == "" \
+ and request.POST.get("password2", "") == "":
+ classes["general"] = AccountFormGeneral
+ form = AccountForm(request.user, request.POST,
+ instances=instances, classes=classes)
+ account.oldgroup = account.group
+ if form.is_valid(mandatory_only=True):
+ form.save_general_form()
+ if form.is_valid(optional_only=True):
+ events.raiseEvent("AccountModified", account, form.account)
+ form.save()
+ messages.info(request, _("Account updated"))
+ return ajax_simple_response(dict(status="ok"))
+ transaction.rollback()
+
+ ctx["tabs"] = form
+ return ajax_response(request, status="ko", template=tplname, **ctx)
+
+ ctx["tabs"] = AccountForm(request.user, instances=instances)
+ return _render(request, tplname, ctx)
+
+@login_required
+@user_passes_test(lambda u: u.has_perm("auth.delete_user") or \
+ u.has_perm("admin.delete_mailbox"))
+@transaction.commit_on_success
+def delaccount(request):
+ selection = request.GET["selection"].split(",")
+ keepdir = True if request.GET.get("keepdir", "false") == "true" else False
+
+ for account in User.objects.filter(id__in=selection):
+ account.delete(request.user, keepdir)
+
+ msg = ungettext("Account deleted", "Accounts deleted", len(selection))
+ messages.info(request, msg, fail_silently=True)
+ return ajax_response(request)
@login_required
@permission_required("auth.view_permissions")
@@ -654,19 +381,6 @@ def search_account(request):
result = map(lambda u: u.username, users.all())
return ajax_simple_response(result)
-@login_required
-def delete_permissions(request):
- pclass = get_perms_class(request.user, request.GET["role"])
- if pclass is None:
- ctx = getctx("ko", message=_("Permission denied!"))
- else:
- selection = request.GET["selection"].split(",")
- pobj = pclass()
- pobj.delete(selection)
- ctx = getctx("ok", content=pobj.get(request),
- message=_("Permissions removed"))
- return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
-
@login_required
@user_passes_test(lambda u: u.is_superuser)
def viewparameters(request, tplname='admin/parameters.html'):
@@ -688,6 +402,7 @@ def viewparameters(request, tplname='admin/parameters.html'):
gparams += [tmp]
return _render(request, tplname, {
+ "selection" : "parameters",
"gparams" : gparams
})
@@ -699,7 +414,7 @@ def saveparameters(request):
continue
app, name = pname.split('.')
parameters.save_admin(name, v, app=app)
- return ajax_response(request)
+ return ajax_simple_response(dict(status="ok", message=_("Parameters saved")))
@login_required
@user_passes_test(lambda u: u.is_superuser)
@@ -720,6 +435,7 @@ def viewextensions(request, tplname='admin/extensions.html'):
tbl = ExtensionsTable(request, exts)
return _render(request, tplname, {
+ "selection" : "extensions",
"extensions" : tbl
})
@@ -741,40 +457,92 @@ def saveextensions(request):
for ext in actived_exts:
if not ext in found:
ext.off()
- messages.info(request, _("Modifications applied."), fail_silently=True)
- return HttpResponseRedirect(reverse(admin.views.viewextensions))
-@login_required
-@permission_required("admin.add_mailbox")
-@transaction.commit_on_success
-def importdata(request, tplname="admin/import.html"):
- if request.method == "POST":
- error = None
- form = ImportDataForm(request.POST, request.FILES)
- if form.is_valid():
- import csv
+ return ajax_simple_response(dict(
+ status="ok", message=_("Modifications applied."), reload=True
+ ))
+
+def import_domain(user, row):
+ pass
+
+def import_account(user, row):
+ """Specific code for accounts import"""
+ mb = Mailbox()
+ mb.create_from_csv(user, row)
+ events.raiseEvent("AccountCreated", user, mb.user)
+ events.raiseEvent("CreateMailbox", user, mb)
+ grant_access_to_object(user, mb.user)
+ grant_access_to_object(user, mb)
+
+def importdata(request, typ):
+ """Generic import function
+
+ As the process of importing data from a CSV file is the same
+ whatever the type, we do a maximum of the work here.
+
+ :param request: a ``Request`` instance
+ :param typ: a string indicating the object type being imported
+ :return: a ``Response`` instance
+ """
+ error = None
+ form = ImportDataForm(request.POST, request.FILES)
+ if form.is_valid():
+ import csv
+
+ try:
reader = csv.reader(request.FILES['sourcefile'],
delimiter=form.cleaned_data['sepcar'])
+ except Exception, e:
+ error = str(e)
+
+ if error is None:
+ fct = globals()["import_%s" % typ]
try:
cpt = 0
for row in reader:
- mb = Mailbox()
- mb.create_from_csv(request.user, row)
- events.raiseEvent("CreateMailbox", request.user, mb)
+ fct(request.user, row)
cpt += 1
- messages.info(request, _("%d mailboxes imported successfully" % cpt))
+ messages.info(request, _("%d %s imported successfully" % (cpt, typ)))
return _render(request, "admin/import_done.html", {
"status" : "ok", "msg" : ""
})
except ModoboaException, e:
error = str(e)
- return _render(request, "admin/import_done.html", {
- "status" : "ko", "msg" : error
- })
+ return _render(request, "admin/import_done.html", {
+ "status" : "ko", "msg" : error
+ })
- form = ImportDataForm()
- ctx = dict(title=_("Import data"), action="", formid="importform",
- enctype="multipart/form-data", form=form, submit_label=_("Import"))
- return _render(request, tplname, ctx)
+@login_required
+@transaction.commit_on_success
+def import_identities(request):
+ if request.method == "POST":
+ return importdata(request, "account")
+ ctx = dict(
+ title=_("Import identities"),
+ submit_label=_("Import"),
+ action=reverse(import_identities),
+ formid="importform",
+ enctype="multipart/form-data",
+ target="import_target",
+ form=ImportDataForm()
+ )
+ return _render(request, "admin/importform.html", ctx)
+
+@login_required
+@transaction.commit_on_success
+def import_domains(request):
+ if request.method == "POST":
+ return importdata(request, "domain")
+
+ ctx = dict(
+ title=_("Import domains"),
+ submit_label=_("Import"),
+ action=reverse(import_domains),
+ formid="importform",
+ enctype="multipart/form-data",
+ target="import_target",
+ form=ImportDataForm()
+ )
+ return _render(request, "admin/importform.html", ctx)
diff --git a/auth/forms.py b/auth/forms.py
index 909d7d3a6..ddb148482 100644
--- a/auth/forms.py
+++ b/auth/forms.py
@@ -2,6 +2,12 @@ from django import forms
from django.utils.translation import ugettext as _
class LoginForm(forms.Form):
- username = forms.CharField(label=_("Username"))
- password = forms.CharField(label=_("Password"), widget=forms.PasswordInput,
- help_text=_("Password used to authenticate"))
+ username = forms.CharField(
+ label=_("Username"),
+ widget=forms.TextInput()
+ )
+ password = forms.CharField(
+ label=_("Password"),
+ widget=forms.PasswordInput(),
+ help_text=_("Password used to authenticate")
+ )
diff --git a/extensions/limits/__init__.py b/extensions/limits/__init__.py
index ffea693a2..76ee8ac81 100644
--- a/extensions/limits/__init__.py
+++ b/extensions/limits/__init__.py
@@ -9,10 +9,10 @@ from django.db import IntegrityError
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_noop as _, ugettext
from django.core.urlresolvers import reverse
-from modoboa.lib import events, parameters
+from modoboa.lib import events
from modoboa.lib.webutils import static_url
-from modoboa.admin.models import User
from models import *
+from forms import ResourcePoolForm
import permissions
import controls
import views
@@ -20,6 +20,8 @@ import views
baseurl = "limits"
def init():
+ from modoboa.admin.models import User
+
ct = ContentType.objects.get(app_label="admin", model="domain")
dagrp = Group.objects.get(name="DomainAdmins")
for pname in ["view_domains", "add_domain", "change_domain", "delete_domain"]:
@@ -59,9 +61,9 @@ def infos():
"url" : baseurl
}
-@events.observe('AdminFooterDisplay')
+#@events.observe('AdminFooterDisplay')
def display_pool_usage(user, objtype):
- if user.id == 1:
+ if user.is_superuser:
return []
if objtype == "domaliases":
objtype = "domain_aliases"
@@ -89,3 +91,36 @@ def get_da_actions(user):
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:330,y:280}}"},
]
+
+@events.observe("ExtraAccountForm")
+def extra_account_form(user, account=None):
+ if not user.is_superuser and not user.belongs_to_group("Resellers"):
+ return []
+ if account:
+ if not account.belongs_to_group("Resellers") and \
+ not account.belongs_to_group("DomainAdmins"):
+ return []
+
+ return [
+ dict(
+ id="resources", title=_("Resources"), cls=ResourcePoolForm
+ )
+ ]
+
+@events.observe("CheckExtraAccountForm")
+def check_form_access(account, form):
+ if form["id"] != "resources":
+ return [True]
+ if not account.belongs_to_group("Resellers") and \
+ not account.belongs_to_group("DomainAdmins"):
+ return [False]
+ return [True]
+
+@events.observe("FillAccountInstances")
+def fill_account_instances(user, account, instances):
+ if not user.is_superuser and not user.belongs_to_group("Resellers"):
+ return
+ if not account.belongs_to_group("Resellers") and \
+ not account.belongs_to_group("DomainAdmins"):
+ return
+ instances["resources"] = account
diff --git a/extensions/limits/controls.py b/extensions/limits/controls.py
index 12ef82b78..17d0346c4 100644
--- a/extensions/limits/controls.py
+++ b/extensions/limits/controls.py
@@ -4,13 +4,10 @@
----------------------------------------------------------------------------
"""
-from modoboa.admin.models import Mailbox
-from modoboa.admin.lib import \
- get_object_owner, grant_access_to_object, \
- ungrant_access_to_object
from modoboa.lib import events
from lib import *
from models import *
+from modoboa.lib.permissions import get_object_owner
def check_limit(user, lname):
"""Check if a user has reached a defined limit
@@ -40,6 +37,22 @@ def dec_limit(user, lname):
except LimitsPool.DoesNotExist:
pass
+def move_pool_resource(owner, user):
+ try:
+ pool = user.limitspool
+ except LimitsPool.DoesNotExist:
+ return
+ if not owner.is_superuser:
+ for lname in reseller_limits_tpl:
+ l = user.limitspool.get_limit(lname)
+ if l.maxvalue < 0:
+ continue
+ ol = owner.limitspool.get_limit(lname)
+ ol.curvalue += l.curvalue
+ ol.maxvalue += l.maxvalue
+ ol.save()
+
+ user.limitspool.delete()
@events.observe('CreateDomain')
def inc_nb_domains(user, domain):
@@ -61,18 +74,29 @@ def inc_nb_domaliases(user, domalias):
inc_limit(user, 'domain_aliases_limit')
@events.observe('DomainAliasDeleted')
-def dec_nb_domaliases(domainalias):
- owner = get_object_owner(domainalias)
- dec_limit(owner, 'domain_aliases_limit')
+def dec_nb_domaliases(domainaliases):
+ from modoboa.admin.models import DomainAlias
+
+ if isinstance(domainaliases, DomainAlias):
+ domainaliases = [domainaliases]
+ for domainalias in domainaliases:
+ owner = get_object_owner(domainalias)
+ dec_limit(owner, 'domain_aliases_limit')
@events.observe('CreateMailbox')
def inc_nb_mailboxes(user, mailbox):
inc_limit(user, 'mailboxes_limit')
@events.observe('DeleteMailbox')
-def dec_nb_mailboxes(mailbox):
- owner = get_object_owner(mailbox)
- dec_limit(owner, 'mailboxes_limit')
+def dec_nb_mailboxes(mailboxes):
+ from modoboa.admin.models import Mailbox
+
+ if isinstance(mailboxes, Mailbox):
+ mailboxes = [mailboxes]
+ for mailbox in mailboxes:
+ owner = get_object_owner(mailbox)
+ dec_limit(owner, 'mailboxes_limit')
+ # FIXME: Gestion des alias ici
@events.observe('MailboxAliasCreated')
def inc_nb_mbaliases(user, mailboxalias):
@@ -87,30 +111,48 @@ def dec_nb_mbaliases(mailboxalias):
def can_create_new_object(user, objtype):
check_limit(user, '%s_limit' % objtype)
-@events.observe("DomainAdminCreated")
+@events.observe("AccountCreated")
def create_pool(user):
owner = get_object_owner(user)
- inc_limit(owner, 'domain_admins_limit')
- p = LimitsPool(user=user)
- p.save()
- p.create_limits()
+ if not owner.is_superuser and \
+ not owner.belongs_to_group("Resellers"):
+ return
-@events.observe("DomainAdminDeleted", "SuperAdminPromotion")
-def move_pool_resource(user):
- owner = get_object_owner(user)
- if not owner.is_superuser:
+ if user.belongs_to_group("DomainAdmins"):
+ check_limit(owner, 'domain_admins_limit')
+ inc_limit(owner, 'domain_admins_limit')
+
+ if user.belongs_to_group("DomainAdmins") or \
+ user.belongs_to_group("Resellers"):
+ p = LimitsPool(user=user)
+ p.save()
+ p.create_limits()
+
+@events.observe("AccountModified")
+def on_account_modified(old, new):
+ owner = get_object_owner(old)
+ if owner.group not in ["SuperAdmins", "Resellers"]:
+ return
+
+ 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')
+ return
+
+ if new.group == "DomainAdmins":
+ check_limit(owner, 'domain_admins_limit')
+ inc_limit(owner, 'domain_admins_limit')
+
+@events.observe("AccountDeleted")
+def on_account_deleted(account):
+ owner = get_object_owner(account)
+ if not owner.group in ["SuperAdmins", "Resellers"]:
+ return
+
+ move_pool_resource(owner, account)
+
+ if account.group == "DomainAdmins":
dec_limit(owner, 'domain_admins_limit')
-
- for ooentry in user.objectaccess_set.all():
- if ooentry.is_owner:
- grant_access_to_object(owner, ooentry.content_object, True)
- ooentry.delete()
-
- for lname in reseller_limits_tpl:
- l = user.limitspool.get_limit(lname)
- ol = owner.limitspool.get_limit(lname)
- ol.curvalue += l.curvalue
- ol.maxvalue += l.maxvalue
- ol.save()
-
- user.limitspool.delete()
diff --git a/extensions/limits/forms.py b/extensions/limits/forms.py
index 55814b710..0324271a3 100644
--- a/extensions/limits/forms.py
+++ b/extensions/limits/forms.py
@@ -3,33 +3,10 @@
from django import forms
from django.utils.translation import ugettext_noop as _
from django.contrib.auth.models import User, Group
-from modoboa.admin.forms import UserForm, UserWithPasswordForm
-from modoboa.admin.lib import get_object_owner
from models import *
from lib import *
-class ResellerForm(UserForm):
- def __init__(self, *args, **kwargs):
- super(ResellerForm, self).__init__(*args, **kwargs)
- del self.fields["createmb"]
-
- def save(self, commit=True, group=None):
- user = super(ResellerForm, self).save(commit, group)
- if commit:
- try:
- pool = user.limitspool
- except LimitsPool.DoesNotExist:
- pool = LimitsPool()
- pool.user = user
- pool.save()
- pool.create_limits()
-
- return user
-
-class ResellerWithPasswordForm(ResellerForm, UserWithPasswordForm):
- pass
-
-class ResellerPoolForm(forms.Form):
+class ResourcePoolForm(forms.Form):
domain_admins_limit = forms.IntegerField(
ugettext_noop("Max domain admins"),
help_text=ugettext_noop("Maximum number of domain administrators that can be created by this user")
@@ -43,6 +20,16 @@ class ResellerPoolForm(forms.Form):
mailbox_aliases_limit = forms.IntegerField(ugettext_noop("Max mailbox aliases"),
help_text=ugettext_noop("Maximum number of mailbox aliases that can be created by this user"))
+ def __init__(self, *args, **kwargs):
+ if kwargs.has_key("instance"):
+ self.account = kwargs["instance"]
+ del kwargs["instance"]
+ super(ResourcePoolForm, self).__init__(*args, **kwargs)
+ if hasattr(self, "account"):
+ if not self.account.belongs_to_group("Resellers"):
+ del self.fields["domain_admins_limit"]
+ self.load_from_user(self.account)
+
def check_limit_value(self, lname):
if self.cleaned_data[lname] < -1:
raise forms.ValidationError(_("Invalid limit"))
@@ -84,12 +71,14 @@ class ResellerPoolForm(forms.Form):
ol.maxvalue -= newvalue
ol.save()
- def save_new_limits(self, pool):
- owner = get_object_owner(pool.user)
+ def save(self):
+ from modoboa.lib.permissions import get_object_owner
+
+ owner = get_object_owner(self.account)
for lname in reseller_limits_tpl:
if not self.cleaned_data.has_key(lname):
continue
- l = pool.limit_set.get(name=lname)
+ l = self.account.limitspool.limit_set.get(name=lname)
if not owner.is_superuser:
self.allocate_from_pool(l, owner.limitspool)
l.maxvalue = self.cleaned_data[lname]
diff --git a/extensions/limits/permissions.py b/extensions/limits/permissions.py
index fd4bf1f91..2f5b316b8 100644
--- a/extensions/limits/permissions.py
+++ b/extensions/limits/permissions.py
@@ -1,53 +1,10 @@
# coding: utf-8
from django.utils.translation import ugettext as _, ugettext_noop
-from modoboa.admin.permissions import Permissions
-from modoboa.admin.lib import grant_access_to_object, ungrant_access_to_object
-from modoboa.admin.models import User, ObjectAccess
from modoboa.lib import events
-from modoboa.lib.webutils import _render, _render_to_string
-from tables import *
-from forms import *
-class ResellersPerms(Permissions):
- title = ugettext_noop("New reseller")
- role = "resellers"
- add_success_msg = ugettext_noop("Reseller added")
-
- def get_add_form(self, request):
- form = ResellerWithPasswordForm()
- return self._render_form(request, form)
-
- def add(self, request):
- form = ResellerWithPasswordForm(request.POST)
- error = None
- if form.is_valid():
- r = form.save(group="Resellers")
- grant_access_to_object(request.user, r, True)
- return True, None
- content = self._render_form(request, form, True)
- return False, dict(status="ko", content=content)
-
- def delete(self, selection):
- for u in User.objects.filter(id__in=selection):
- ungrant_access_to_object(u)
- u.delete()
-
- def get(self, request):
- resellers = User.objects.filter(groups__name="Resellers")
- return ResellersTable(request, resellers).render()
-
-@events.observe('PermsGetTables')
-def get_resellers_table(request):
- if not request.user.is_superuser:
- return []
- return [{"id" : "resellers",
- "title" : _("Resellers"),
- "rel" : "350 300",
- "content" : ResellersPerms().get(request)}]
-
-@events.observe("PermsGetClass")
-def get_perms_class(role):
- if role != "resellers":
- return []
- return [ResellersPerms]
+@events.observe("GetExtraRoles")
+def get_extra_roles(user):
+ if user.is_superuser:
+ return [("Resellers", _("Reseller")),]
+ return []
diff --git a/extensions/postfix_autoreply/__init__.py b/extensions/postfix_autoreply/__init__.py
index 6d99c025b..e38c9c8ca 100644
--- a/extensions/postfix_autoreply/__init__.py
+++ b/extensions/postfix_autoreply/__init__.py
@@ -21,6 +21,23 @@ def infos():
"url" : "postfix_autoreply"
}
+def init():
+ from modoboa.admin.models import Domain
+
+ for dom in Domain.objects.all():
+ try:
+ trans = Transport.objects.get(domain="autoreply.%s" % dom.name)
+ except Transport.DoesNotExist:
+ onCreateDomain(None, dom)
+ else:
+ continue
+
+ for mb in dom.mailbox_set.all():
+ try:
+ alias = Alias.objects.get(full_address=mb.full_address)
+ except Alias.DoesNotExist:
+ onCreateMailbox(None, mb)
+
def load():
parameters.register_admin(
"AUTOREPLIES_TIMEOUT",
@@ -48,10 +65,9 @@ def menu(target, user):
return [
{"name" : "autoreply",
"url" : reverse(views.autoreply),
- "img" : static_url("pics/auto-reply.png"),
- "class" : "boxed",
- "rel" : "{handler:'iframe',size:{x:440,y:400}}",
- "label" : ugettext("Auto-reply message")}
+ "label" : ugettext("Auto-reply message"),
+ "modal": True,
+ "modalcb" : "arform_cb"}
]
@events.observe("CreateDomain")
@@ -75,12 +91,18 @@ def onCreateMailbox(user, mailbox):
alias.save()
@events.observe("DeleteMailbox")
-def onDeleteMailbox(mailbox):
- try:
- alias = Alias.objects.get(full_address=mailbox.full_address)
- alias.delete()
- except Alias.DoesNotExist:
- pass
+def onDeleteMailbox(mailboxes):
+ from modoboa.admin.models import Mailbox
+
+ if isinstance(mailboxes, Mailbox):
+ mailboxes = [mailboxes]
+ for mailbox in mailboxes:
+ try:
+ alias = Alias.objects.get(full_address=mailbox.full_address)
+ except Alias.DoesNotExist:
+ pass
+ else:
+ alias.delete()
@events.observe("ModifyMailbox")
def onModifyMailbox(mailbox, oldmailbox):
@@ -91,3 +113,17 @@ def onModifyMailbox(mailbox, oldmailbox):
alias.autoreply_address = \
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
alias.save()
+
+@events.observe("GetStaticContent")
+def get_static_content(target):
+ if target != "userprefs":
+ return []
+ return """
+"""
diff --git a/extensions/postfix_autoreply/forms.py b/extensions/postfix_autoreply/forms.py
index 0c46c5e5c..5ce746e2a 100644
--- a/extensions/postfix_autoreply/forms.py
+++ b/extensions/postfix_autoreply/forms.py
@@ -4,18 +4,15 @@ from django.utils.translation import ugettext as _
import datetime
class ARmessageForm(forms.ModelForm):
- untildate = forms.DateField(label=_('Until'), required=False,
- help_text=_("Activate your auto reply until this date"))
+ untildate = forms.DateField(
+ label=_('Until'), required=False,
+ help_text=_("Activate your auto reply until this date")
+ )
class Meta:
model = ARmessage
fields = ('subject', 'content', 'enabled')
- def __init__(self, *args, **kwargs):
- super(ARmessageForm, self).__init__(*args, **kwargs)
- self.fields['subject'].widget.attrs['size'] = 40
- self.fields['content'].widget.attrs['cols'] = 50
-
def clean_untildate(self):
if self.cleaned_data["untildate"] is not None:
if self.cleaned_data["untildate"] < datetime.date.today():
diff --git a/extensions/postfix_autoreply/templates/postfix_autoreply/autoreply.html b/extensions/postfix_autoreply/templates/postfix_autoreply/autoreply.html
index c7a9a6fe2..9a909fe31 100644
--- a/extensions/postfix_autoreply/templates/postfix_autoreply/autoreply.html
+++ b/extensions/postfix_autoreply/templates/postfix_autoreply/autoreply.html
@@ -1,42 +1,15 @@
-{% extends "layout_simple.html" %}
{% load i18n %}
-{% block html_head %}
-
-
-
-
-
-{% endblock %}
-
-{% block content %}
-{% trans "Auto-reply message" %}
-
-
-{% endblock %}
+
+
+ {% if error %}
{{ error }}
{% endif %}
+
+
+
diff --git a/extensions/postfix_autoreply/views.py b/extensions/postfix_autoreply/views.py
index 907a4ba40..ff924e1ac 100644
--- a/extensions/postfix_autoreply/views.py
+++ b/extensions/postfix_autoreply/views.py
@@ -1,5 +1,4 @@
-# -*- coding: utf-8 -*-
-
+# coding: utf-8
from django.http import HttpResponse
from django.utils import simplejson
from django.contrib.auth.decorators \
@@ -14,7 +13,13 @@ from models import *
@login_required
@needs_mailbox()
-def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
+def autoreply(request, tplname="common/generic_modal_form.html"):
+ ctx = dict(
+ title=_("Auto-reply message"),
+ action=reverse(autoreply),
+ formid="arform",
+ submit_label=_("Update")
+ )
mb = Mailbox.objects.get(user=request.user.id)
try:
arm = ARmessage.objects.get(mbox=mb.id)
@@ -33,15 +38,13 @@ def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
arm.untildate = form.cleaned_data["untildate"]
arm.mbox = mb
arm.save()
- messages.info(request, _("Auto reply message updated successfully."),
- fail_silently=True)
+ messages.info(request, _("Auto reply message updated successfully."))
return ajax_response(request, url=reverse(userprefs.views.index))
- return ajax_response(request, status="ko", template=tplname,
- form=form, error=error)
+ ctx.update(form=form, error=error)
+ return ajax_response(request, status="ko", template=tplname, **ctx)
form = ARmessageForm(instance=arm)
if arm is not None:
form.fields['untildate'].initial = arm.untildate
- return _render(request, tplname, {
- "form" : form
- })
+ ctx.update(form=form)
+ return _render(request, tplname, ctx)
diff --git a/extensions/sievefilters/__init__.py b/extensions/sievefilters/__init__.py
index ed6043711..865877bed 100644
--- a/extensions/sievefilters/__init__.py
+++ b/extensions/sievefilters/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# coding: utf-8
from django.utils.translation import ugettext_noop as _, ugettext
from django.core.urlresolvers import reverse
from modoboa.lib import events, parameters
@@ -55,7 +55,7 @@ def menu(target, user):
{"name" : "sievefilters",
"label" : ugettext("Message filters"),
"url" : reverse(views.index),
- "img" : static_url("pics/filters.png")}
+ "img" : "icon-check"}
]
@events.observe("Userlogout")
diff --git a/extensions/sievefilters/forms.py b/extensions/sievefilters/forms.py
index c07ac1a36..549c9934d 100644
--- a/extensions/sievefilters/forms.py
+++ b/extensions/sievefilters/forms.py
@@ -1,6 +1,10 @@
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_noop
+from django.forms.widgets import RadioFieldRenderer, RadioSelect, RadioInput
+from django.utils.safestring import mark_safe
+from django.utils.html import conditional_escape
+from django.utils.encoding import force_unicode
from modoboa.admin.templatetags.admin_extras import gender
class FiltersSetForm(forms.Form):
@@ -9,17 +13,52 @@ class FiltersSetForm(forms.Form):
initial=False,
help_text=ugettext_noop("Check to activate this filters set"))
+class CustomRadioInput(RadioInput):
+ def __unicode__(self):
+ return self.render()
+
+ def render(self, name=None, value=None, attrs=None, choices=()):
+ name = name or self.name
+ value = value or self.value
+ attrs = attrs or self.attrs
+ if 'id' in self.attrs:
+ label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
+ else:
+ label_for = ''
+ choice_label = conditional_escape(force_unicode(self.choice_label))
+ return mark_safe(
+ u'' \
+ % (label_for, self.tag(), choice_label)
+ )
+
+class CustomRadioFieldRenderer(RadioFieldRenderer):
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+
+ def __getitem__(self, idx):
+ choice = self.choices[idx]
+ return CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
+
+ def render(self):
+ return mark_safe(u'\n'.join([force_unicode(w) for w in self]))
+
+class CustomRadioSelect(RadioSelect):
+ renderer = CustomRadioFieldRenderer
+
class FilterForm(forms.Form):
def __init__(self, conditions, actions, request, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
self.fields["name"] = forms.CharField(label=_("Name"))
- self.fields["match_type"] = forms.ChoiceField(choices=[("allof", _("All of the following")),
- ("anyof", _("Any of the following")),
- ("all", _("All messages"))],
- initial="anyof",
- widget=forms.RadioSelect)
-
+ self.fields["match_type"] = forms.ChoiceField(
+ choices=[("allof", _("All of the following")),
+ ("anyof", _("Any of the following")),
+ ("all", _("All messages"))],
+ initial="anyof",
+ widget=CustomRadioSelect(attrs={"class" : "radio inline"})
+ )
+
self.header_operators = [("contains", _("contains"), "string"),
("notcontains", _("does not contain"), "string"),
("is", _("is"), "string"),
diff --git a/extensions/sievefilters/lib.py b/extensions/sievefilters/lib.py
index 41f321017..4d58addd5 100644
--- a/extensions/sievefilters/lib.py
+++ b/extensions/sievefilters/lib.py
@@ -42,6 +42,8 @@ class SieveClient(object):
self.msc = None
def refresh(self, user, password):
+ import ssl
+
if self.msc is not None:
try:
self.msc.capability()
@@ -51,7 +53,7 @@ class SieveClient(object):
return
try:
ret, msg = self.login(user, password)
- except Error, e:
+ except (Error, ssl.SSLError), e:
raise ConnectionError(e)
if not ret:
raise ConnectionError(msg)
@@ -74,6 +76,8 @@ class SieveClient(object):
return fs
def pushscript(self, name, content, active=False):
+ if type(content) is unicode:
+ content = str(content)
if not self.msc.havespace(name, len(content)):
error = "%s (%s)" % (_("Not enough space on server"), self.msc.errmsg)
raise SieveClientError(error)
diff --git a/extensions/sievefilters/static/css/style.css b/extensions/sievefilters/static/css/style.css
index cf487b3f2..123766e87 100644
--- a/extensions/sievefilters/static/css/style.css
+++ b/extensions/sievefilters/static/css/style.css
@@ -1,43 +1,6 @@
-#fsets_list {
- margin-left: 5px;
- float: left;
- display: inline;
-}
-
-#fset_menu {
- position: absolute;
- top: 0px;
- left: 10px;
-}
-
-#set_content {
- position: absolute;
- left: 10px;
- right: 10px;
- top: 0px;
- bottom: 10px;
- padding: 0;
-}
-
-#set_content h1 {
- padding: 2px 5px;
- margin: 0;
- font-size: 1.2em;
- background: #CEDBEC;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
-}
-
-#fsets_bar {
- margin-left: 10px;
- width: 250px;
-}
-
#set_content textarea {
- margin: 0;
width: 100%;
- height: 100%;
- padding: 2px;
+ height: 500px;
}
#set_content label {
@@ -54,14 +17,6 @@
bottom: 0px;
}
-#feditor {
- position: absolute;
- left: 190px;
- right: 10px;
- top: 0px;
- bottom: 0px;
-}
-
#id_content {
width: 95% !important;
height: 270px;
@@ -143,11 +98,6 @@ div.item input, div.item select {
margin: 0 2px;
}
-#condlist label {
- margin: 0 !important;
- padding: 0;
-}
-
ul.errors {
list-style: none;
display: inline;
diff --git a/extensions/sievefilters/static/js/jquery.dynfields.js b/extensions/sievefilters/static/js/jquery.dynfields.js
new file mode 100644
index 000000000..eb3c61264
--- /dev/null
+++ b/extensions/sievefilters/static/js/jquery.dynfields.js
@@ -0,0 +1,328 @@
+(function($) {
+ var DynamicCondition = function(element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, $.fn.dyncondition.defaults, options);
+ this.fields_cnt = 1;
+ this.nextid = 1;
+
+ var $addlink = $('', {
+ href: '#',
+ id: this.$element.attr("id") + '_rmbtn',
+ html: ''
+ });
+ $("select[name*=cond_target_]").change($.proxy(this.target_change, this));
+ $addlink.click($.proxy(this.addfields, this));
+ this.$element.append($addlink);
+
+ for (var cpt = 1; true; cpt++) {
+ var $div = $("#condition_" + cpt);
+
+ if (!$div.length) {
+ break;
+ }
+ $div.append(this.newrmlink());
+ this.nextid++;
+ this.fields_cnt++;
+ }
+ };
+
+ DynamicCondition.prototype = {
+ constructor: DynamicCondition,
+
+ loadoperators: function($container, tpl) {
+ $container.empty();
+ for (var ocpt = 0; ocpt < tpl.operators.length; ocpt++) {
+ var $opt = $("", {
+ value: tpl["operators"][ocpt][0],
+ html: tpl["operators"][ocpt][1]
+ });
+
+ $container.append($opt);
+ }
+ },
+
+ removefields: function(e) {
+ var $link = $(e.target).parent();
+ var id = $link.attr("id");
+
+ e.preventDefault();
+ $("#" + id.replace("_rmbtn", "")).remove();
+ this.fields_cnt--;
+ },
+
+ newrmlink: function() {
+ return $('', {
+ href: '#',
+ id: 'condition_' + this.nextid + '_rmbtn',
+ html: ''
+ }).click($.proxy(this.removefields, this));
+ },
+
+ addfields: function(e) {
+ e.preventDefault();
+ var $div = $("", {
+ id: "condition_" + this.nextid, 'class': "item"
+ });
+ var $condtarget = $("", {
+ id: "id_cond_target_" + this.nextid,
+ name: "cond_target_" + this.nextid
+ }).change($.proxy(this.target_change, this));
+ var $condoperator = $("", {
+ id: "id_cond_operator_" + this.nextid,
+ name: "cond_operator_" + this.nextid
+ });
+ var $condvalue = $("", {
+ id: "id_cond_value_" + this.nextid,
+ name: "cond_value_" + this.nextid,
+ type: "text"
+ });
+ var $rmlink = this.newrmlink();
+
+ for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
+ var tpl = this.options.templates[cpt];
+ var $opt = $("", {
+ value: tpl.name,
+ html: tpl.label
+ });
+
+ $condtarget.append($opt);
+ if (tpl.name != this.options.defaultcond) {
+ continue;
+ }
+ this.loadoperators($condoperator, tpl);
+ }
+
+ $div.append($condtarget, $condoperator, $condvalue, $rmlink);
+ this.$element.after($div);
+ this.nextid++;
+ this.fields_cnt++;
+ },
+
+ target_change: function(e) {
+ var $target = $(e.target);
+ var parts = $target.attr("name").split("_");
+ var $container = $("#id_cond_operator_" + parts[2]);
+
+ for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
+ var tpl = this.options.templates[cpt];
+
+ if (tpl.name != $target.attr("value")) {
+ continue;
+ }
+ this.loadoperators($container, tpl);
+ break;
+ }
+ },
+
+ counter: function() {
+ return this.fields_cnt;
+ }
+ };
+
+ $.fn.dyncondition = function(method) {
+ return this.each(function() {
+ var $this = $(this),
+ data = $this.data('dyncondition'),
+ options = typeof method === "object" && method;
+
+ if (!data) {
+ $this.data('dyncondition', new DynamicCondition(this, options));
+ }
+ if (typeof method === "string") {
+ data[method]();
+ }
+ });
+ };
+
+ $.fn.dyncondition.defaults = {
+ defaultcond: "Subject",
+ templates: []
+ };
+
+})(jQuery);
+
+/*
+ * Dynamic actions class
+ */
+(function($) {
+ var DynamicAction = function(element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, $.fn.dynaction.defaults, options);
+ this.nextid = 1;
+ this.fields_cnt = 1;
+
+ var $addlink = $('', {
+ href: '#',
+ id: this.$element.attr("id") + '_rmbtn',
+ html: ''
+ });
+ $("select[name*=action_name_]").change($.proxy(this.name_change, this));
+ $addlink.click($.proxy(this.addfields, this));
+ this.$element.append($addlink);
+
+ for (var cpt = 1; true; cpt++) {
+ var $div = $("#action_" + cpt);
+
+ if (!$div.length) {
+ break;
+ }
+ $div.append(this.newrmlink());
+ this.nextid++;
+ this.fields_cnt++;
+ }
+
+ $("#id_action_arg_0_0").find("option").each(function() {
+ var $this = $(this),
+ cpt = 1,
+ start = 0,
+ value = $this.attr("value");
+
+ while (true) {
+ var id = value.indexOf(".", start);
+ if (id == -1) {
+ break;
+ }
+ $this.css("margin-left", (cpt * 20) + "px");
+ cpt++;
+ start = id + 1;
+ }
+ });
+
+ this.$fdoptions = $("#id_action_arg_0_0").find("option");
+ };
+
+ DynamicAction.prototype = {
+ constructor: DynamicAction,
+
+ loadargs: function(id, tpl) {
+ var res = [];
+
+ for (var acpt = 0; acpt < tpl.args.length; acpt++) {
+ var argname = "action_arg_" + id + "_" + acpt;
+ var $child = null;
+
+ if (tpl.args[acpt].type == "list") {
+ $child = $("", {id: "id_" + argname, name: argname});
+ if (tpl.args[acpt].vloader != undefined) {
+ this[tpl.args[acpt].vloader]($child);
+ }
+ } else if (tpl.args[acpt].type == "string") {
+ $child = $("", {
+ type: "text",
+ id: "id_" + argname,
+ name: argname
+ });
+ }
+ res.push($child);
+ }
+ return res;
+ },
+
+ deletePrevArgs: function(id) {
+ for (var cpt = 0; true; cpt++) {
+ var $arg = $("#id_action_arg_" + id + "_" + cpt);
+ if (!$arg.length) {
+ break;
+ }
+ $arg.remove();
+ }
+ },
+
+ name_change: function(e) {
+ var $target = $(e.target);
+ var parts = $target.attr("name").split("_");
+ var $pos = $("#action_" + parts[2] + "_rmbtn");
+
+ e.preventDefault();
+ this.deletePrevArgs(parts[2]);
+ for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
+ var tpl = this.options.templates[cpt];
+ if (tpl.name != $target.attr("value")) {
+ continue;
+ }
+ $.each(this.loadargs(parts[2], tpl), function(idx, arg) {
+ $pos.before(arg);
+ });
+ }
+ },
+
+ newrmlink: function() {
+ return $('', {
+ href: '#',
+ id: 'action_' + this.nextid + '_rmbtn',
+ html: ''
+ }).click($.proxy(this.removefields, this));
+ },
+
+ addfields: function(e) {
+ e.preventDefault();
+ var $div = $("", {id: "action_" + this.nextid, "class": "item"});
+ var $actioname = $("", {
+ id: "id_action_name_" + this.nextid,
+ name: "action_name_" + this.nextid
+ });
+ var $rmlink = this.newrmlink();
+ var args = null;
+
+ for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
+ var tpl = this.options.templates[cpt];
+ var $opt = $("", {
+ value: tpl.name,
+ html: tpl.label
+ });
+
+ $actioname.append($opt);
+ if (tpl.name != this.options.defaultaction) {
+ continue;
+ }
+ args = this.loadargs(this.nextid, tpl);
+ }
+ $div.append($actioname);
+ $actioname.change($.proxy(this.name_change, this));
+ $.each(args, function(idx, element) {
+ $div.append(element);
+ });
+ $div.append($rmlink);
+ this.$element.after($div);
+ this.nextid++;
+ this.fields_cnt++;
+ },
+
+ removefields: function(e) {
+ var $link = $(e.target).parent();
+ var id = $link.attr("id");
+
+ e.preventDefault();
+ $("#" + id.replace("_rmbtn", "")).remove();
+ this.fields_cnt--;
+ },
+
+ userfolders: function($container) {
+ this.$fdoptions.each(function() {
+ $container.append($(this).clone());
+ });
+ }
+
+ };
+
+ $.fn.dynaction = function(method) {
+ return this.each(function() {
+ var $this = $(this),
+ data = $this.data('dynaction'),
+ options = typeof method === "object" && method;
+
+ if (!data) {
+ $this.data('dynaction', new DynamicAction(this, options));
+ }
+ if (typeof method === "string") {
+ data[method]();
+ }
+ });
+ };
+
+ $.fn.dynaction.defaults = {
+ defaultaction: "fileinto",
+ templates: [],
+ ufolders_url: null
+ };
+})(jQuery);
\ No newline at end of file
diff --git a/extensions/sievefilters/static/js/lib.js b/extensions/sievefilters/static/js/lib.js
index 39add7588..32c301713 100644
--- a/extensions/sievefilters/static/js/lib.js
+++ b/extensions/sievefilters/static/js/lib.js
@@ -10,41 +10,40 @@ var curscript = null;
*
*/
function removefilter(evt) {
- evt.stop();
+ evt.preventDefault();
if (!confirm(gettext("Remove this filter?"))) {
return;
}
- new Request.JSON.mdb({
- url: this.get("href"),
- onSuccess: function(response) {
+ var $this = $(this);
+
+ $.ajax({
+ url: $this.attr("href"),
+ dataType: 'json',
+ success: function(response) {
if (response.status == "ok") {
- current_anchor.update(true);
- infobox.info(response.respmsg);
- infobox.hide(1);
+ history.update(true);
+ $("body").notify('success', response.respmsg, 2000);
} else {
- infobox.error(response.respmsg);
+ $("body").notify('error', response.respmsg);
}
}
- }).get();
+ });
}
/*
* Handler listening for the "change" event on the filters sets list.
*/
function changefs(evt) {
- evt.stop();
- current_anchor.baseurl(this.get("value")).update();
+ evt.preventDefault();
+ history.baseurl($(this).attr("value")).update();
}
/*
* Initializes buttons located inside the filters list
*/
function init_filters_list() {
- SqueezeBox.assign($("filters_list").getElements('a[class=boxed]'), {
- parse: 'rel'
- });
- $$("a[name=togglestate]").addEvent("click", toggle_filter_state);
- $$("a[name*=movefilter_]").addEvent("click", move_filter);
+ $("a[name=togglestate]").click(toggle_filter_state);
+ $("a[name*=movefilter_]").click(move_filter);
}
/*
@@ -53,17 +52,21 @@ function init_filters_list() {
* It updates the middle DIV and initializes the user interface.
*/
function loadfs(response) {
- curscript = current_anchor.getbaseurl();
- $("curfset").set("value", curscript);
- $("set_content").set("html", response.content);
- parse_menubar("fset_menu");
- if ($defined($("filters_list"))) {
+ curscript = history.getbaseurl();
+ $("#curfset").attr("value", curscript);
+ $("#set_content").html(response.content);
+
+ if ($("#filters_list") != undefined) {
init_filters_list();
}
- $$("a[name=removefilter]").addEvent("click", removefilter);
- $$("a[name=activatefs]").addEvent("click", activatefs);
- $$("a[name=savefs]").addEvent("click", savefs);
- $$("a[name=removefs]").addEvent("click", removefs);
+ if ($("#fsetmenu").length) {
+ $("#fsetmenu").remove();
+ }
+ $("#menu").after($(response.menu));
+ $("a[name=removefilter]").click(removefilter);
+ $("a[name=activatefs]").click(activatefs);
+ $("a[name=savefs]").click(savefs);
+ $("a[name=removefs]").click(removefs);
}
/*
@@ -72,25 +75,23 @@ function loadfs(response) {
* (remove, activate)
*/
function send_command(cmd, extracb) {
- new Request.JSON.mdb({
+ $.ajax({
url: cmd,
- onSuccess: function(response) {
+ dataType: 'json',
+ success: function(response) {
if (response.status == "ko") {
- infobox.calcsize(300);
- infobox.error(response.respmsg);
+ $("body").notify('error', response.respmsg);
} else {
- if ($defined(extracb)) {
- extracb();
+ if (extracb != undefined) {
+ extracb(response);
}
if (!response.norefresh) {
- current_anchor.update(true);
+ history.update(true);
}
- infobox.calcsize();
- infobox.info(response.respmsg);
- infobox.hide(2);
+ $("body").notify('success', response.respmsg, 2000);
}
}
- }).get();
+ });
}
/*
@@ -98,11 +99,11 @@ function send_command(cmd, extracb) {
* validate any new asked action. (activate, save, remove)
*/
function check_prereqs(evt, msg) {
- evt.stop();
+ evt.preventDefault();
if (!curscript) {
return false;
}
- if ($defined(msg) && !confirm(msg)) {
+ if (msg != undefined && !confirm(msg)) {
return false;
}
return true;
@@ -116,21 +117,21 @@ function savefs(evt) {
if (!check_prereqs(evt, gettext("Save changes?"))) {
return;
}
- new Request.JSON.mdb({
- url: $("feditor").get("action"),
- method: $("feditor").get("method"),
- data: $("feditor").toQueryString(),
- onSuccess: function(response) {
+ var $editor = $("#feditor");
+
+ $.ajax({
+ url: $editor.attr("action"),
+ type: $editor.attr("method"),
+ dataType: 'json',
+ data: $editor.serialize(),
+ success: function(response) {
if (response.status == "ko") {
- infobox.calcsize(300);
- infobox.error(response.respmsg);
+ $("body").notify('error', response.respmsg);
} else {
- infobox.calcsize();
- infobox.info(response.respmsg);
- infobox.hide(2);
+ $("body").notify('success', response.respmsg, 2000);
}
}
- }).send();
+ });
}
/*
@@ -143,14 +144,11 @@ function removefs(evt) {
if (!check_prereqs(evt, gettext("Remove filters set?"))) {
return;
}
- send_command(this.get("href"), function() {
- $("curfset").getElements("option[value=" + curscript + "]").destroy();
- if (!$("curfset").getElements("option[value=" + curscript + "]").length) {
- current_anchor.reset().update();
- location.reload();
- } else {
- current_anchor.reset();
- }
+ send_command($(this).attr("href"), function(response) {
+ var $curfset = $("#curfset");
+
+ $curfset.find("option[value=" + curscript + "]").remove();
+ history.baseurl(response.newfs, 1);
});
}
@@ -158,46 +156,105 @@ function removefs(evt) {
* Activate the current filters set.
*/
function activatefs(evt) {
+ var $this = $(this);
+
if (!check_prereqs(evt, gettext("Activate this filters set?"))) {
return;
}
- send_command(
- this.get("href"),
- function() {
- $("curfset").getElements("option").each(function(item) {
- if (item.get("value") == curscript) {
- item.set("html", curscript + " (" + gettext("active") + ")");
- } else {
- item.set("html", item.get("value"));
- }
- });
- }
- );
+ send_command($this.attr("href"), function(response) {
+ $("#curfset").find("option").each(function(index, element) {
+ var $element = $(element);
+
+ ($element.attr("value") == curscript)
+ ? $element.html(curscript + " (" + gettext("active") + ")")
+ : $element.html($element.attr("value"));
+ });
+ });
}
+/*
+ * Enable/Disable a specific filter
+ */
function toggle_filter_state(event) {
- event.stop();
- new Request.JSON.mdb({
- url: this.get("href"),
- onSuccess: function(response) {
+ var $this = $(this);
+
+ event.preventDefault();
+ $.ajax({
+ url: $this.attr("href"),
+ dataType: 'json',
+ success: function(response) {
if (response.status == "ok") {
- this.set("html", response.label);
- this.set("class", "");
- this.addClass(response.color);
+ $this.html(response.label);
+ $this.attr("class", "");
+ $this.addClass(response.color);
} else {
- infobox.error(respmsg.respmsg);
+ $("body").notify('error', response.respmsg);
}
- }.bind(this)
- }).get();
+ }
+ });
}
function move_filter(event) {
- event.stop();
- new Request.JSON.mdb({
- url: this.get("href"),
- onSuccess: function(response) {
- $("filters_list").set("html", response.content);
+ var $this = $(this);
+
+ event.preventDefault();
+ $.ajax({
+ url: $this.attr('href'),
+ dataType: 'json',
+ success: function(response) {
+ $("#set_content").html(response.content);
init_filters_list();
}
- }).get();
+ });
+}
+
+function filterset_created(data) {
+ var name = data.url;
+ var option = $('');
+ var curfset = $(parent.document).find("#curfset");
+
+ curfset.append(option);
+ curfset.attr("value", name);
+
+ if ($("#id_active").checked) {
+ curfset.find("option").each(function(index, element) {
+ var $element = $(element);
+
+ if ($element.attr("value") == name) {
+ $element.html(name + " (" + gettext("active") + ")");
+ } else {
+ $element.html($element.attr("value"));
+ }
+ });
+ }
+ history.baseurl(data.url, 1).update(1);
+}
+
+function filtersetform_cb() {
+ $(".submit").one('click', function(e) {
+ simple_ajax_form_post(e, {
+ reload_on_success: false,
+ error_cb: filtersetform_cb,
+ success_cb: filterset_created
+ });
+ });
+}
+
+/*
+ * From here: specific functions used by the user-friendly form to
+ * create/edit filters.
+ */
+function fetch_templates(url, container) {
+ if (container) {
+ return container;
+ }
+ var result;
+
+ $.ajax({
+ url: url,
+ async: false,
+ dataType: 'json',
+ success: function(data) { result = data; }
+ });
+ return result;
}
\ No newline at end of file
diff --git a/extensions/sievefilters/templates/sievefilters/filter.html b/extensions/sievefilters/templates/sievefilters/filter.html
index 0ff1c30c6..a438c1c35 100644
--- a/extensions/sievefilters/templates/sievefilters/filter.html
+++ b/extensions/sievefilters/templates/sievefilters/filter.html
@@ -1,96 +1,23 @@
-{% extends "layout_simple.html" %}
-{% load i18n sfilters_tags %}
-{% block html_head %}
-{{ block.super }}
-
-
-
-
-
-
-
-{% endblock %}
-{% block content %}
-{{ title }}
-
+
{% endblock %}
diff --git a/extensions/sievefilters/templates/sievefilters/filters.html b/extensions/sievefilters/templates/sievefilters/filters.html
deleted file mode 100644
index 42f561aaa..000000000
--- a/extensions/sievefilters/templates/sievefilters/filters.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% load i18n sfilters_tags %}
-{% trans "Filters" %}
-
- {% if fs.filters %}
-
-
- | {% trans "Actions" %} |
- {% trans "Name" %} |
- {% trans "Active" %} |
-
- {% for filter in fs.filters %}
-
- | {% filter_actions fs.name filter forloop.counter forloop.last %} |
- {{ filter.name }} |
-
-
- {{ filter.enabled|yesno }}
-
- |
-
- {% endfor %}
-
- {% else %}
- {% trans "No filter defined yet" %}
- {% endif %}
-
diff --git a/extensions/sievefilters/templates/sievefilters/guieditor.html b/extensions/sievefilters/templates/sievefilters/guieditor.html
index 8923141c0..8e3150185 100644
--- a/extensions/sievefilters/templates/sievefilters/guieditor.html
+++ b/extensions/sievefilters/templates/sievefilters/guieditor.html
@@ -1,8 +1,25 @@
-{% load sfilters_tags i18n %}
-
-
- {% include "sievefilters/filters.html" %}
-
+{% load i18n sfilters_tags %}
+{% if fs.filters %}
+
+
+ | {% trans "Actions" %} |
+ {% trans "Name" %} |
+ {% trans "Active" %} |
+
+ {% for filter in fs.filters %}
+
+ | {% filter_actions fs.name filter forloop.counter forloop.last %} |
+ {{ filter.name }} |
+
+
+ {{ filter.enabled|yesno }}
+
+ |
+
+ {% endfor %}
+
+{% else %}
+{% trans "No filter defined yet" %}
+{% endif %}
diff --git a/extensions/sievefilters/templates/sievefilters/index.html b/extensions/sievefilters/templates/sievefilters/index.html
index 3a85e91b6..03dcc4eb5 100644
--- a/extensions/sievefilters/templates/sievefilters/index.html
+++ b/extensions/sievefilters/templates/sievefilters/index.html
@@ -1,41 +1,77 @@
-{% extends "nlayout.html" %}
-{% load i18n libextras sfilters_tags %}
+{% extends "twocols.html" %}
+{% load i18n sfilters_tags %}
{% block html_head %}
-
-
+
+
+
+
{% endblock %}
-{% block middletitle %}{% trans "Message filters" %}{% endblock %}
-{% block menu %}
-
-
-
+{% block leftcol %}
+
-
{% endblock %}
-{% block content %}
-{% if messages %}
- {% display_messages messages %}
-{% endif %}
+{% block apparea %}
{% if not active_script and not scripts %}
-
{% trans "No filters set defined yet. You must create one in order to define filters." %}
+
{% trans "No filters set defined yet. You must create one in order to define filters." %}
{% endif %}
{% endblock %}
diff --git a/extensions/sievefilters/templates/sievefilters/newfiltersset.html b/extensions/sievefilters/templates/sievefilters/newfiltersset.html
deleted file mode 100644
index 134c60d77..000000000
--- a/extensions/sievefilters/templates/sievefilters/newfiltersset.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "layout_simple.html" %}
-{% load i18n %}
-{% block html_head %}
-
-
-
-{% endblock %}
-{% block content %}
-
{{ title }}
-{% if error %}
{{ error }}
{% endif %}
-
-{% endblock %}
diff --git a/extensions/sievefilters/templates/sievefilters/rawfilter.html b/extensions/sievefilters/templates/sievefilters/rawfilter.html
index 895609303..20fbf55d3 100644
--- a/extensions/sievefilters/templates/sievefilters/rawfilter.html
+++ b/extensions/sievefilters/templates/sievefilters/rawfilter.html
@@ -1,8 +1,3 @@
-{% load i18n sfilters_tags %}
-