mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Remove admin extension from this repo.
This commit is contained in:
@@ -57,7 +57,6 @@ MODOBOA_APPS = (
|
||||
'modoboa.core',
|
||||
'modoboa.lib',
|
||||
# Modoboa extensions here.
|
||||
'modoboa.extensions.admin',
|
||||
'modoboa.extensions.postfix_relay_domains',
|
||||
'modoboa.extensions.limits',
|
||||
'modoboa.extensions.postfix_autoreply',
|
||||
|
||||
@@ -41,7 +41,8 @@ def dologin(request):
|
||||
if user.group == "SimpleUsers":
|
||||
nextlocation = reverse("topredirection")
|
||||
else:
|
||||
nextlocation = reverse("admin:domain_list")
|
||||
# FIXME
|
||||
nextlocation = reverse("modoboa_admin:domain_list")
|
||||
return HttpResponseRedirect(nextlocation)
|
||||
error = _(
|
||||
"Your username and password didn't match. Please try again.")
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib.form_utils import YesNoField, SeparatorField
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.lib import parameters
|
||||
import os
|
||||
|
||||
|
||||
class AdminParametersForm(parameters.AdminParametersForm):
|
||||
app = "admin"
|
||||
|
||||
mbsep = SeparatorField(label=ugettext_lazy("Mailboxes"))
|
||||
|
||||
handle_mailboxes = YesNoField(
|
||||
label=ugettext_lazy("Handle mailboxes on filesystem"),
|
||||
initial="no",
|
||||
help_text=ugettext_lazy(
|
||||
"Rename or remove mailboxes on the filesystem when they get"
|
||||
" renamed or removed within Modoboa"
|
||||
)
|
||||
)
|
||||
|
||||
mailboxes_owner = forms.CharField(
|
||||
label=ugettext_lazy("Mailboxes owner"),
|
||||
initial="vmail",
|
||||
help_text=ugettext_lazy(
|
||||
"The UNIX account who owns mailboxes on the filesystem"
|
||||
)
|
||||
)
|
||||
|
||||
default_domain_quota = forms.IntegerField(
|
||||
label=ugettext_lazy("Default domain quota"),
|
||||
initial=0,
|
||||
help_text=ugettext_lazy(
|
||||
"Default quota (in MB) applied to freshly created domains with no "
|
||||
"value specified. A value of 0 means no quota."
|
||||
)
|
||||
)
|
||||
|
||||
auto_account_removal = YesNoField(
|
||||
label=ugettext_lazy("Automatic account removal"),
|
||||
initial="no",
|
||||
help_text=ugettext_lazy(
|
||||
"When a mailbox is removed, also remove the associated account")
|
||||
)
|
||||
|
||||
auto_create_domain_and_mailbox = YesNoField(
|
||||
label=ugettext_lazy("Automatic domain/mailbox creation"),
|
||||
initial="yes",
|
||||
help_text=ugettext_lazy(
|
||||
"Create a domain and a mailbox when an account is automatically "
|
||||
"created."
|
||||
)
|
||||
)
|
||||
|
||||
# Visibility rules
|
||||
visibility_rules = {
|
||||
"mailboxes_owner": "handle_mailboxes=yes",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AdminParametersForm, self).__init__(*args, **kwargs)
|
||||
self.field_widths = {
|
||||
"default_domain_quota": 2
|
||||
}
|
||||
hide_fields = False
|
||||
dpath = None
|
||||
code, output = exec_cmd("which dovecot")
|
||||
if not code:
|
||||
dpath = output.strip()
|
||||
else:
|
||||
known_paths = getattr(
|
||||
settings, "DOVECOT_LOOKUP_PATH",
|
||||
("/usr/sbin/dovecot", "/usr/local/sbin/dovecot")
|
||||
)
|
||||
for fpath in known_paths:
|
||||
if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
|
||||
dpath = fpath
|
||||
if dpath:
|
||||
try:
|
||||
code, version = exec_cmd("%s --version" % dpath)
|
||||
except OSError:
|
||||
hide_fields = True
|
||||
else:
|
||||
if code or not version.strip().startswith("2"):
|
||||
hide_fields = True
|
||||
else:
|
||||
hide_fields = True
|
||||
if hide_fields:
|
||||
del self.fields["handle_mailboxes"]
|
||||
del self.fields["mailboxes_owner"]
|
||||
|
||||
def clean_default_domain_quota(self):
|
||||
"""Ensure quota is a positive integer."""
|
||||
if self.cleaned_data['default_domain_quota'] < 0:
|
||||
raise forms.ValidationError(
|
||||
ugettext_lazy('Must be a positive integer')
|
||||
)
|
||||
return self.cleaned_data['default_domain_quota']
|
||||
@@ -1,79 +0,0 @@
|
||||
"""
|
||||
Fixtures factories.
|
||||
"""
|
||||
import factory
|
||||
|
||||
from modoboa.core.factories import PermissionFactory, UserFactory
|
||||
from . import models
|
||||
|
||||
|
||||
class DomainFactory(PermissionFactory):
|
||||
|
||||
"""Factory to create Domains."""
|
||||
|
||||
class Meta:
|
||||
model = models.Domain
|
||||
django_get_or_create = ("name", )
|
||||
|
||||
quota = 10
|
||||
enabled = True
|
||||
|
||||
|
||||
class MailboxFactory(PermissionFactory):
|
||||
|
||||
class Meta:
|
||||
model = models.Mailbox
|
||||
|
||||
domain = factory.SubFactory(DomainFactory)
|
||||
user = factory.SubFactory(UserFactory)
|
||||
quota = 10
|
||||
enabled = True
|
||||
|
||||
|
||||
class AliasFactory(PermissionFactory):
|
||||
|
||||
class Meta:
|
||||
model = models.Alias
|
||||
|
||||
enabled = True
|
||||
|
||||
|
||||
def populate_database():
|
||||
"""Create test data.
|
||||
|
||||
2 domains, 1 domain admin, 2 users
|
||||
"""
|
||||
dom = DomainFactory(name="test.com")
|
||||
admin = UserFactory(
|
||||
username="admin@test.com", groups=('DomainAdmins', ),
|
||||
password='{PLAIN}toto'
|
||||
)
|
||||
MailboxFactory(address='admin', domain=dom, user=admin)
|
||||
account = UserFactory.create(
|
||||
username="user@test.com", groups=('SimpleUsers',),
|
||||
)
|
||||
MailboxFactory.create(address='user', domain=dom, user=account)
|
||||
|
||||
fwd = AliasFactory.create(
|
||||
address='forward', domain=dom, extmboxes='user@external.com'
|
||||
)
|
||||
al = AliasFactory.create(
|
||||
address='alias', domain=dom
|
||||
)
|
||||
al.mboxes.add(account.mailbox_set.all()[0])
|
||||
AliasFactory.create(
|
||||
address='postmaster', domain=dom, extmboxes='toto@titi.com,test@truc.fr'
|
||||
)
|
||||
dom.add_admin(admin)
|
||||
|
||||
dom2 = DomainFactory.create(name='test2.com', quota=0)
|
||||
admin = UserFactory.create(
|
||||
username='admin@test2.com', groups=('DomainAdmins',),
|
||||
password='{PLAIN}toto'
|
||||
)
|
||||
MailboxFactory.create(address='admin', domain=dom2, user=admin)
|
||||
u = UserFactory.create(
|
||||
username='user@test2.com', groups=('SimpleUsers',)
|
||||
)
|
||||
MailboxFactory.create(address='user', domain=dom2, user=u)
|
||||
dom2.add_admin(admin)
|
||||
@@ -1,310 +0,0 @@
|
||||
[
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "core.user",
|
||||
"fields": {
|
||||
"username": "admin",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_staff": false,
|
||||
"last_login": "2012-05-03T16:58:51+01:00",
|
||||
"groups": [],
|
||||
"user_permissions": [],
|
||||
"password": "{CRYPT}dTTsGDkA5ZHKg",
|
||||
"email": "",
|
||||
"date_joined": "2012-01-26T15:19:25+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "core.user",
|
||||
"fields": {
|
||||
"username": "admin@demo.local",
|
||||
"first_name": "Demo",
|
||||
"last_name": "Administrator",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": false,
|
||||
"last_login": "2012-05-24T05:00:12+01:00",
|
||||
"groups": [1],
|
||||
"user_permissions": [],
|
||||
"password": "{CRYPT}SJ2bQWwabfH/6",
|
||||
"email": "admin@demo.local",
|
||||
"date_joined": "2012-05-03T16:50:15+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "core.user",
|
||||
"fields": {
|
||||
"username": "user@demo.local",
|
||||
"first_name": "Demo", "last_name": "User",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": false,
|
||||
"last_login": "2012-05-03T16:56:50+01:00",
|
||||
"groups": [2],
|
||||
"user_permissions": [],
|
||||
"password": "{CRYPT}2iAFXtxUA6.7Y",
|
||||
"email": "user@demo.local",
|
||||
"date_joined": "2012-05-03T16:56:50+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["admin", "domain"],
|
||||
"object_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["core", "user"],
|
||||
"object_id": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["admin", "mailbox"],
|
||||
"object_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["admin", "alias"],
|
||||
"object_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["admin", "domain"],
|
||||
"object_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 7,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["admin", "mailbox"],
|
||||
"object_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["core", "user"],
|
||||
"object_id": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 9,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["admin",
|
||||
"alias"],
|
||||
"object_id": 1}},
|
||||
{
|
||||
"pk": 13,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["core",
|
||||
"user"],
|
||||
"object_id": 4}},
|
||||
{
|
||||
"pk": 14,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": true,
|
||||
"user": 1,
|
||||
"content_type": ["admin",
|
||||
"mailbox"],
|
||||
"object_id": 3}},
|
||||
{
|
||||
"pk": 15,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["admin",
|
||||
"mailbox"],
|
||||
"object_id": 3}},
|
||||
{
|
||||
"pk": 16,
|
||||
"model": "core.objectaccess",
|
||||
"fields": {
|
||||
"is_owner": false,
|
||||
"user": 2,
|
||||
"content_type": ["core",
|
||||
"user"],
|
||||
"object_id": 4}},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "admin.objectdates",
|
||||
"fields": {
|
||||
"creation": "2012-05-03T16:49:40+01:00",
|
||||
"last_modification": "2012-05-03T16:49:40+01:00"}},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "admin.objectdates",
|
||||
"fields": {
|
||||
"creation": "2012-05-03T16:50:15+01:00",
|
||||
"last_modification": "2012-05-03T16:50:36+01:00"}},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "admin.objectdates",
|
||||
"fields": {
|
||||
"creation": "2012-05-03T16:50:15+01:00",
|
||||
"last_modification": "2012-05-03T16:50:15+01:00"}},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "admin.objectdates",
|
||||
"fields": {
|
||||
"creation": "2012-05-03T16:51:04+01:00",
|
||||
"last_modification": "2012-05-03T16:51:04+01:00"}},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "admin.objectdates",
|
||||
"fields": {
|
||||
"creation": "2012-05-03T16:56:50+01:00",
|
||||
"last_modification": "2012-05-03T16:56:50+01:00"}},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "admin.domain",
|
||||
"fields": {
|
||||
"dates": 1,
|
||||
"enabled": true,
|
||||
"name": "demo.local",
|
||||
"quota": 10}},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "admin.mailbox",
|
||||
"fields": {
|
||||
"dates": 2,
|
||||
"quota": 10,
|
||||
"user": 2,
|
||||
"domain": 1,
|
||||
"address": "admin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "admin.mailbox",
|
||||
"fields": {
|
||||
"dates": 5,
|
||||
"quota": 10,
|
||||
"user": 4,
|
||||
"domain": 1,
|
||||
"address": "user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "admin.alias",
|
||||
"fields": {
|
||||
"dates": 3,
|
||||
"enabled": true,
|
||||
"domain": 1,
|
||||
"mboxes": [1],
|
||||
"address": "postmaster",
|
||||
"extmboxes": ""}},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "limits"}},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "postfix_autoreply"}},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "stats"}},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "amavis"}},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "webmail"}},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "sievefilters"}},
|
||||
{
|
||||
"pk": 7,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "postfix_relay_domains"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "core.extension",
|
||||
"fields": {
|
||||
"enabled": true,
|
||||
"name": "radicale"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": "admin@demo.local",
|
||||
"model": "admin.quota",
|
||||
"fields": {
|
||||
"username": "admin@demo.local",
|
||||
"bytes": 0,
|
||||
"messages": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": "user@demo.local",
|
||||
"model": "admin.quota",
|
||||
"fields": {
|
||||
"username": "user@demo.local",
|
||||
"bytes": 0,
|
||||
"messages": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
from .domain import (
|
||||
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
|
||||
)
|
||||
from .account import (
|
||||
AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
|
||||
AccountForm, AccountWizard
|
||||
)
|
||||
from .alias import AliasForm
|
||||
from .forward import ForwardForm
|
||||
from .import_ import ImportDataForm, ImportIdentitiesForm
|
||||
from .export import ExportDataForm, ExportDomainsForm, ExportIdentitiesForm
|
||||
|
||||
__all__ = [
|
||||
'DomainFormGeneral', 'DomainFormOptions', 'DomainForm',
|
||||
'AccountFormGeneral', 'AccountFormMail', 'AccountPermissionsForm',
|
||||
'AccountForm', 'AliasForm', 'ImportDataForm', 'ImportIdentitiesForm',
|
||||
'ExportDataForm', 'ExportDomainsForm', 'ExportIdentitiesForm',
|
||||
'ForwardForm', 'DomainWizard', 'AccountWizard'
|
||||
]
|
||||
@@ -1,478 +0,0 @@
|
||||
"""Forms related to accounts management."""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Mailbox, Alias
|
||||
)
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
|
||||
from modoboa.lib.form_utils import (
|
||||
DomainNameField, DynamicForm, TabForms, WizardForm
|
||||
)
|
||||
from modoboa.lib.permissions import get_account_roles
|
||||
from modoboa.lib.web_utils import render_to_json_response
|
||||
|
||||
|
||||
class AccountFormGeneral(forms.ModelForm):
|
||||
username = forms.CharField(
|
||||
label=ugettext_lazy("Username"),
|
||||
help_text=ugettext_lazy(
|
||||
"The user's name. Must be a valid e-mail address for simple users "
|
||||
"or administrators with a mailbox."
|
||||
)
|
||||
)
|
||||
role = forms.ChoiceField(
|
||||
label=ugettext_lazy("Role"),
|
||||
choices=[('', ugettext_lazy("Choose"))],
|
||||
help_text=ugettext_lazy("What level of permission this user will have")
|
||||
)
|
||||
password1 = forms.CharField(
|
||||
label=ugettext_lazy("Password"), widget=forms.widgets.PasswordInput
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=ugettext_lazy("Confirmation"),
|
||||
widget=forms.widgets.PasswordInput,
|
||||
help_text=ugettext_lazy(
|
||||
"Enter the same password as above, for verification."
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("username", "first_name", "last_name", "role", "is_active")
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(AccountFormGeneral, self).__init__(*args, **kwargs)
|
||||
self.fields = OrderedDict(
|
||||
(key, self.fields[key]) for key in
|
||||
['role', 'username', 'first_name', 'last_name', 'password1',
|
||||
'password2', 'is_active']
|
||||
)
|
||||
self.fields["is_active"].label = _("Enabled")
|
||||
self.user = user
|
||||
if user.group == "DomainAdmins":
|
||||
self.fields["role"] = forms.CharField(
|
||||
label="",
|
||||
widget=forms.HiddenInput(attrs={"class": "form-control"}),
|
||||
required=False
|
||||
)
|
||||
else:
|
||||
self.fields["role"].choices = [('', ugettext_lazy("Choose"))]
|
||||
self.fields["role"].choices += \
|
||||
get_account_roles(user, kwargs['instance']) \
|
||||
if 'instance' in kwargs else get_account_roles(user)
|
||||
|
||||
if "instance" in kwargs:
|
||||
account = kwargs["instance"]
|
||||
domain_disabled = account.mailbox_set.count() and \
|
||||
not account.mailbox_set.all()[0].domain.enabled
|
||||
if domain_disabled:
|
||||
self.fields["is_active"].widget.attrs['disabled'] = "disabled"
|
||||
if args:
|
||||
if args[0].get("password1", "") == "" \
|
||||
and args[0].get("password2", "") == "":
|
||||
self.fields["password1"].required = False
|
||||
self.fields["password2"].required = False
|
||||
if domain_disabled:
|
||||
del self.fields["is_active"]
|
||||
self.fields["role"].initial = account.group
|
||||
if not account.is_local \
|
||||
and parameters.get_admin(
|
||||
"LDAP_AUTH_METHOD", app="core") == "directbind":
|
||||
del self.fields["password1"]
|
||||
del self.fields["password2"]
|
||||
|
||||
def domain_is_disabled(self):
|
||||
"""Little shortcut to get the domain's state.
|
||||
|
||||
We need this information inside a template and the form is the
|
||||
only object available...
|
||||
|
||||
"""
|
||||
if not self.instance.mailbox_set.count():
|
||||
return False
|
||||
return not self.instance.mailbox_set.all()[0].domain.enabled
|
||||
|
||||
def clean_role(self):
|
||||
if self.user.group == "DomainAdmins":
|
||||
if self.instance == self.user:
|
||||
return "DomainAdmins"
|
||||
return "SimpleUsers"
|
||||
return self.cleaned_data["role"]
|
||||
|
||||
def clean_username(self):
|
||||
from django.core.validators import validate_email
|
||||
if "role" not in self.cleaned_data:
|
||||
return self.cleaned_data["username"]
|
||||
if self.cleaned_data["role"] != "SimpleUsers":
|
||||
return self.cleaned_data["username"]
|
||||
uname = self.cleaned_data["username"].lower()
|
||||
validate_email(uname)
|
||||
return uname
|
||||
|
||||
def clean_password2(self):
|
||||
password1 = self.cleaned_data.get("password1", "")
|
||||
password2 = self.cleaned_data["password2"]
|
||||
if password1 != password2:
|
||||
raise forms.ValidationError(
|
||||
_("The two password fields didn't match."))
|
||||
return password2
|
||||
|
||||
def save(self, commit=True):
|
||||
account = super(AccountFormGeneral, self).save(commit=False)
|
||||
if self.user == account and not self.cleaned_data["is_active"]:
|
||||
raise PermDeniedException(_("You can't disable your own account"))
|
||||
if commit:
|
||||
if "password1" in self.cleaned_data \
|
||||
and self.cleaned_data["password1"] != "":
|
||||
account.set_password(self.cleaned_data["password1"])
|
||||
account.save()
|
||||
account.set_role(self.cleaned_data["role"])
|
||||
return account
|
||||
|
||||
|
||||
class AccountFormMail(forms.Form, DynamicForm):
|
||||
|
||||
"""Form to handle mail part."""
|
||||
|
||||
email = forms.EmailField(label=ugettext_lazy("E-mail"), required=False)
|
||||
quota = forms.IntegerField(
|
||||
label=ugettext_lazy("Quota"),
|
||||
required=False,
|
||||
help_text=_("Quota in MB for this mailbox. Define a custom value or "
|
||||
"use domain's default one. Leave empty to define an "
|
||||
"unlimited value (not allowed for domain "
|
||||
"administrators)."),
|
||||
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
quota_act = forms.BooleanField(required=False)
|
||||
aliases = forms.EmailField(
|
||||
label=ugettext_lazy("Alias(es)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Alias(es) of this mailbox. Indicate only one address per input, "
|
||||
"press ENTER to add a new input. Use the '*' character to create "
|
||||
"a 'catchall' alias (ex: *@domain.tld)."
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "instance" in kwargs:
|
||||
self.mb = kwargs["instance"]
|
||||
del kwargs["instance"]
|
||||
else:
|
||||
self.mb = None
|
||||
super(AccountFormMail, self).__init__(*args, **kwargs)
|
||||
self.field_widths = {
|
||||
"quota": 3
|
||||
}
|
||||
self.extra_fields = []
|
||||
result = events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb)
|
||||
for fname, field in result:
|
||||
self.fields[fname] = field
|
||||
self.extra_fields.append(fname)
|
||||
if self.mb is not None:
|
||||
self.fields["email"].required = True
|
||||
cpt = 1
|
||||
for alias in self.mb.alias_set.all():
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
continue
|
||||
name = "aliases_%d" % cpt
|
||||
self._create_field(forms.EmailField, name, alias.full_address)
|
||||
cpt += 1
|
||||
self.fields["email"].initial = self.mb.full_address
|
||||
self.fields["quota_act"].initial = self.mb.use_domain_quota
|
||||
if not self.mb.use_domain_quota and self.mb.quota:
|
||||
self.fields["quota"].initial = self.mb.quota
|
||||
else:
|
||||
self.fields["quota_act"].initial = True
|
||||
|
||||
if len(args) and isinstance(args[0], QueryDict):
|
||||
self._load_from_qdict(args[0], "aliases", forms.EmailField)
|
||||
|
||||
def clean_email(self):
|
||||
"""Ensure lower case emails"""
|
||||
return self.cleaned_data["email"].lower()
|
||||
|
||||
def clean(self):
|
||||
"""Custom fields validation.
|
||||
|
||||
Check if quota is >= 0 only when the domain value is not used.
|
||||
"""
|
||||
super(AccountFormMail, self).clean()
|
||||
if not self.cleaned_data["quota_act"] \
|
||||
and self.cleaned_data['quota'] is not None:
|
||||
if self.cleaned_data["quota"] < 0:
|
||||
self.add_error("quota", _("Must be a positive integer"))
|
||||
return self.cleaned_data
|
||||
|
||||
def create_mailbox(self, user, account):
|
||||
"""Create a mailbox associated to :kw:`account`."""
|
||||
locpart, domname = split_mailbox(self.cleaned_data["email"])
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise NotFound(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
try:
|
||||
Mailbox.objects.get(address=locpart, domain=domain)
|
||||
except Mailbox.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict(
|
||||
_("Mailbox %s already exists" % self.cleaned_data["email"])
|
||||
)
|
||||
events.raiseEvent("CanCreate", user, "mailboxes")
|
||||
self.mb = Mailbox(address=locpart, domain=domain, user=account,
|
||||
use_domain_quota=self.cleaned_data["quota_act"])
|
||||
self.mb.set_quota(self.cleaned_data["quota"],
|
||||
user.has_perm("admin.add_domain"))
|
||||
self.mb.save(creator=user)
|
||||
|
||||
def update_mailbox(self, user, account):
|
||||
newaddress = None
|
||||
if self.cleaned_data["email"] != self.mb.full_address:
|
||||
newaddress = self.cleaned_data["email"]
|
||||
elif (account.group == "SimpleUsers" and
|
||||
account.username != self.mb.full_address):
|
||||
newaddress = account.username
|
||||
if newaddress is not None:
|
||||
self.mb.old_full_address = self.mb.full_address
|
||||
local_part, domname = split_mailbox(newaddress)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise NotFound(_("Domain does not exist"))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
self.mb.rename(local_part, domain)
|
||||
|
||||
self.mb.use_domain_quota = self.cleaned_data["quota_act"]
|
||||
override_rules = True \
|
||||
if not self.mb.quota or user.has_perm("admin.add_domain") \
|
||||
else False
|
||||
self.mb.set_quota(self.cleaned_data["quota"], override_rules)
|
||||
self.mb.save()
|
||||
events.raiseEvent('MailboxModified', self.mb)
|
||||
|
||||
def _update_aliases(self, user, account):
|
||||
"""Update mailbox aliases."""
|
||||
aliases = []
|
||||
for name, value in self.cleaned_data.iteritems():
|
||||
if not name.startswith("aliases"):
|
||||
continue
|
||||
if value == "":
|
||||
continue
|
||||
aliases.append(value.lower())
|
||||
|
||||
for alias in self.mb.alias_set.all():
|
||||
if alias.full_address not in aliases:
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
continue
|
||||
alias.delete()
|
||||
else:
|
||||
aliases.remove(alias.full_address)
|
||||
if not aliases:
|
||||
return
|
||||
events.raiseEvent(
|
||||
"CanCreate", user, "mailbox_aliases", len(aliases)
|
||||
)
|
||||
for alias in aliases:
|
||||
local_part, domname = split_mailbox(alias)
|
||||
try:
|
||||
self.mb.alias_set.get(address=local_part, domain__name=domname)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
al = Alias(address=local_part, enabled=account.is_active)
|
||||
al.domain = Domain.objects.get(name=domname)
|
||||
al.save(int_rcpts=[self.mb])
|
||||
al.post_create(user)
|
||||
|
||||
def save(self, user, account):
|
||||
"""Save or update account mailbox."""
|
||||
if self.cleaned_data["email"] == "":
|
||||
return None
|
||||
|
||||
if self.cleaned_data["quota_act"]:
|
||||
self.cleaned_data["quota"] = None
|
||||
|
||||
if not hasattr(self, "mb") or self.mb is None:
|
||||
self.create_mailbox(user, account)
|
||||
else:
|
||||
self.update_mailbox(user, account)
|
||||
events.raiseEvent(
|
||||
'SaveExtraFormFields', 'mailform', self.mb, self.cleaned_data
|
||||
)
|
||||
|
||||
account.email = self.cleaned_data["email"]
|
||||
account.save()
|
||||
|
||||
self._update_aliases(user, account)
|
||||
|
||||
return self.mb
|
||||
|
||||
|
||||
class AccountPermissionsForm(forms.Form, DynamicForm):
|
||||
domains = DomainNameField(
|
||||
label=ugettext_lazy("Domain(s)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy("Domain(s) that user administrates")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "instance" in kwargs:
|
||||
self.account = kwargs["instance"]
|
||||
del kwargs["instance"]
|
||||
|
||||
super(AccountPermissionsForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if not hasattr(self, "account") or self.account is None:
|
||||
return
|
||||
for pos, dom in enumerate(Domain.objects.get_for_admin(self.account)):
|
||||
name = "domains_%d" % (pos + 1)
|
||||
self._create_field(DomainNameField, name, dom.name)
|
||||
if len(args) and isinstance(args[0], QueryDict):
|
||||
self._load_from_qdict(args[0], "domains", DomainNameField)
|
||||
|
||||
def save(self):
|
||||
current_domains = [
|
||||
dom.name for dom in Domain.objects.get_for_admin(self.account)
|
||||
]
|
||||
for name, value in self.cleaned_data.items():
|
||||
if not name.startswith("domains"):
|
||||
continue
|
||||
if value in ["", None]:
|
||||
continue
|
||||
if value not in current_domains:
|
||||
domain = Domain.objects.get(name=value)
|
||||
domain.add_admin(self.account)
|
||||
|
||||
for domain in Domain.objects.get_for_admin(self.account):
|
||||
if not filter(lambda name: self.cleaned_data[name] == domain.name,
|
||||
self.cleaned_data.keys()):
|
||||
domain.remove_admin(self.account)
|
||||
|
||||
|
||||
class AccountForm(TabForms):
|
||||
|
||||
"""Account edition form."""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.user = request.user
|
||||
self.forms = [
|
||||
dict(id="general", title=_("General"),
|
||||
formtpl="admin/account_general_form.html",
|
||||
cls=AccountFormGeneral,
|
||||
new_args=[self.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 = [self.user]
|
||||
if "instances" in kwargs:
|
||||
cbargs += [kwargs["instances"]["general"]]
|
||||
self.forms += events.raiseQueryEvent("ExtraAccountForm", *cbargs)
|
||||
|
||||
super(AccountForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def extra_context(self, context):
|
||||
account = self.instances["general"]
|
||||
context.update({
|
||||
'title': account.username,
|
||||
'formid': 'accountform',
|
||||
'action': reverse("admin:account_change", args=[account.id]),
|
||||
})
|
||||
|
||||
def check_perms(self, account):
|
||||
if account.is_superuser:
|
||||
return False
|
||||
return self.user.has_perm("admin.add_domain") \
|
||||
and account.has_perm("core.add_user")
|
||||
|
||||
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
|
||||
|
||||
extra_forms = events.raiseQueryEvent(
|
||||
"CheckExtraAccountForm", self.account, form)
|
||||
if False in extra_forms:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_valid(self):
|
||||
"""Two steps validation.
|
||||
"""
|
||||
self.instances["general"].oldgroup = self.instances["general"].group
|
||||
if super(AccountForm, self).is_valid(mandatory_only=True):
|
||||
self.account = self.forms[0]["instance"].save()
|
||||
return super(AccountForm, self).is_valid(optional_only=True)
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Custom save method
|
||||
|
||||
As forms interact with each other, it is simpler to make
|
||||
custom code to save them.
|
||||
"""
|
||||
events.raiseEvent(
|
||||
"AccountModified", self.instances["general"], self.account
|
||||
)
|
||||
self.forms[1]["instance"].save(self.user, self.account)
|
||||
if len(self.forms) <= 2:
|
||||
return
|
||||
for f in self.forms[2:]:
|
||||
f["instance"].save()
|
||||
|
||||
def done(self):
|
||||
return render_to_json_response(_("Account updated"))
|
||||
|
||||
|
||||
class AccountWizard(WizardForm):
|
||||
|
||||
"""Account creation wizard.
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
super(AccountWizard, self).__init__(request)
|
||||
self.add_step(
|
||||
AccountFormGeneral, _("General"), new_args=[request.user]
|
||||
)
|
||||
self.add_step(
|
||||
AccountFormMail, _("Mail"), formtpl="admin/mailform.html"
|
||||
)
|
||||
|
||||
def extra_context(self, context):
|
||||
context.update({
|
||||
'title': _("New account"),
|
||||
'action': reverse("admin:account_add"),
|
||||
'formid': 'newaccount_form'
|
||||
})
|
||||
|
||||
def done(self):
|
||||
from modoboa.lib.web_utils import render_to_json_response
|
||||
|
||||
account = self.first_step.form.save()
|
||||
account.post_create(self.request.user)
|
||||
mailform = self.steps[1].form
|
||||
mailform.save(self.request.user, account)
|
||||
return render_to_json_response(_("Account created"))
|
||||
@@ -1,152 +0,0 @@
|
||||
"""Forms related to aliases management."""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Mailbox, Alias
|
||||
)
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
|
||||
from modoboa.lib.form_utils import (
|
||||
DynamicForm
|
||||
)
|
||||
|
||||
|
||||
class AliasForm(forms.ModelForm, DynamicForm):
|
||||
email = forms.EmailField(
|
||||
label=ugettext_lazy("Email address"),
|
||||
help_text=ugettext_lazy(
|
||||
"The distribution list address. Use the '*' character to create a "
|
||||
"'catchall' address (ex: *@domain.tld)."
|
||||
),
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
recipients = forms.EmailField(
|
||||
label=ugettext_lazy("Recipients"), required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Mailbox(es) this alias will point to. Indicate only one address "
|
||||
"per input, press ENTER to add a new input."
|
||||
),
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Alias
|
||||
fields = ("enabled",)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self.user = user
|
||||
super(AliasForm, self).__init__(*args, **kwargs)
|
||||
self.fields = OrderedDict(
|
||||
(key, self.fields[key]) for key in
|
||||
['email', 'recipients', 'enabled']
|
||||
)
|
||||
|
||||
if len(args) and isinstance(args[0], QueryDict):
|
||||
if "instance" in kwargs:
|
||||
if not kwargs["instance"].domain.enabled:
|
||||
del self.fields["enabled"]
|
||||
self._load_from_qdict(args[0], "recipients", forms.EmailField)
|
||||
elif "instance" in kwargs:
|
||||
dlist = kwargs["instance"]
|
||||
self.fields["email"].initial = dlist.full_address
|
||||
if not dlist.domain.enabled:
|
||||
self.fields["enabled"].widget.attrs["disabled"] = "disabled"
|
||||
cpt = 1
|
||||
for al in dlist.aliases.all():
|
||||
name = "recipients_%d" % cpt
|
||||
self._create_field(forms.EmailField, name, al.full_address, 2)
|
||||
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
|
||||
|
||||
def clean_email(self):
|
||||
localpart, domname = split_mailbox(self.cleaned_data["email"])
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise forms.ValidationError(_("Domain does not exist"))
|
||||
if not self.user.can_access(domain):
|
||||
raise forms.ValidationError(
|
||||
_("You don't have access to this domain")
|
||||
)
|
||||
return self.cleaned_data["email"].lower()
|
||||
|
||||
def set_recipients(self):
|
||||
"""Recipients dispatching
|
||||
|
||||
We make a difference between 'local' recipients (the ones hosted
|
||||
by Modoboa) and 'external' recipients.
|
||||
"""
|
||||
self.ext_rcpts = []
|
||||
self.int_rcpts = []
|
||||
total = 0
|
||||
|
||||
for k, v in self.cleaned_data.items():
|
||||
if not k.startswith("recipients"):
|
||||
continue
|
||||
if v == "":
|
||||
continue
|
||||
local_part, domname = split_mailbox(v)
|
||||
if domname is None:
|
||||
raise BadRequest(
|
||||
u"%s %s" % (_("Invalid mailbox"), v)
|
||||
)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
domain = None
|
||||
if domain is not None:
|
||||
try:
|
||||
rcpt = Alias.objects.get(domain=domain, address=local_part)
|
||||
if rcpt.full_address == self.cleaned_data["email"]:
|
||||
rcpt = None
|
||||
except Alias.DoesNotExist:
|
||||
rcpt = None
|
||||
if rcpt is None:
|
||||
try:
|
||||
rcpt = Mailbox.objects.get(
|
||||
domain=domain, address=local_part)
|
||||
except Mailbox.DoesNotExist:
|
||||
raise NotFound(
|
||||
_("Local recipient %s not found" % v)
|
||||
)
|
||||
if rcpt in self.int_rcpts:
|
||||
raise Conflict(
|
||||
_("Recipient %s already present" % v)
|
||||
)
|
||||
self.int_rcpts += [rcpt]
|
||||
total += 1
|
||||
continue
|
||||
|
||||
if v in self.ext_rcpts:
|
||||
raise Conflict(
|
||||
_("Recipient %s already present" % v)
|
||||
)
|
||||
self.ext_rcpts += [v]
|
||||
total += 1
|
||||
|
||||
if total == 0:
|
||||
raise BadRequest(_("No recipient defined"))
|
||||
|
||||
def save(self, commit=True):
|
||||
alias = super(AliasForm, self).save(commit=False)
|
||||
localpart, domname = split_mailbox(self.cleaned_data["email"])
|
||||
alias.address = localpart
|
||||
alias.domain = Domain.objects.get(name=domname)
|
||||
if commit:
|
||||
alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)
|
||||
self.save_m2m()
|
||||
return alias
|
||||
@@ -1,362 +0,0 @@
|
||||
"""Forms related to domains management."""
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.lib import check_if_domain_exists
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Mailbox, Alias, Quota
|
||||
)
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.exceptions import ModoboaException, Conflict
|
||||
from modoboa.lib.form_utils import (
|
||||
DomainNameField, YesNoField, WizardForm, DynamicForm, TabForms
|
||||
)
|
||||
from modoboa.lib.web_utils import render_to_json_response
|
||||
|
||||
|
||||
class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
quota = forms.IntegerField(
|
||||
label=ugettext_lazy("Quota"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Default quota in MB applied to mailboxes. Leave empty to use the "
|
||||
"default value."
|
||||
)
|
||||
)
|
||||
aliases = DomainNameField(
|
||||
label=ugettext_lazy("Alias(es)"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Alias(es) of this domain. Indicate only one name per input, "
|
||||
"press ENTER to add a new input."
|
||||
)
|
||||
)
|
||||
name = DomainNameField(
|
||||
widget=forms.TextInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ("name", "quota", "aliases", "enabled")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.oldname = None
|
||||
if "instance" in kwargs:
|
||||
self.oldname = kwargs["instance"].name
|
||||
super(DomainFormGeneral, self).__init__(*args, **kwargs)
|
||||
|
||||
self.field_widths = {
|
||||
"quota": 3
|
||||
}
|
||||
|
||||
if len(args) and isinstance(args[0], QueryDict):
|
||||
self._load_from_qdict(args[0], "aliases", DomainNameField)
|
||||
elif "instance" in kwargs:
|
||||
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_quota(self):
|
||||
if self.cleaned_data['quota'] is None:
|
||||
return int(parameters.get_admin('DEFAULT_DOMAIN_QUOTA'))
|
||||
if self.cleaned_data['quota'] < 0:
|
||||
raise forms.ValidationError(_("Must be a positive integer"))
|
||||
return self.cleaned_data['quota']
|
||||
|
||||
def clean(self):
|
||||
"""Custom fields validation.
|
||||
|
||||
We want to prevent duplicate names between domains and domain
|
||||
aliases. Extensions have the possibility to declare other
|
||||
objects (see *CheckDomainName* event).
|
||||
|
||||
The validation way is not very smart...
|
||||
"""
|
||||
super(DomainFormGeneral, self).clean()
|
||||
cleaned_data = self.cleaned_data
|
||||
name = cleaned_data["name"]
|
||||
label = check_if_domain_exists(name, [(DomainAlias, _('domain alias'))])
|
||||
if label is not None:
|
||||
self.add_error(
|
||||
"name", _("A %s with this name already exists") % unicode(label)
|
||||
)
|
||||
for k in cleaned_data.keys():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if cleaned_data[k] == "":
|
||||
del cleaned_data[k]
|
||||
continue
|
||||
if cleaned_data[k] == name:
|
||||
self._errors[k] = self.error_class(
|
||||
[_("A %s with this name already exists") % _("domain")]
|
||||
)
|
||||
del cleaned_data[k]
|
||||
continue
|
||||
label = check_if_domain_exists(
|
||||
cleaned_data[k], [(Domain, _("domain"))])
|
||||
if label is not None:
|
||||
self.add_error(
|
||||
k, _("A %s with this name already exists") % unicode(label))
|
||||
return cleaned_data
|
||||
|
||||
def update_mailbox_quotas(self, domain):
|
||||
"""Update all quota records associated to this domain.
|
||||
|
||||
This method must be called only when a domain gets renamed. As
|
||||
the primary key used for a quota is an email address, rename a
|
||||
domain will change all associated email addresses, so it will
|
||||
change the primary keys used for quotas. The consequence is we
|
||||
can't issue regular UPDATE queries using the .save() method of
|
||||
a Quota instance (it will trigger an INSERT as the primary key
|
||||
has changed).
|
||||
|
||||
So, we use this ugly hack to bypass this behaviour. It is not
|
||||
perfomant at all as it will generate one query per quota
|
||||
record to update.
|
||||
"""
|
||||
for q in Quota.objects.filter(username__contains="@%s" % self.oldname):
|
||||
username = q.username.replace(
|
||||
'@%s' % self.oldname, '@%s' % domain.name)
|
||||
newq = Quota.objects.create(
|
||||
username=username, bytes=q.bytes, messages=q.messages)
|
||||
q.delete()
|
||||
|
||||
def save(self, user, commit=True, domalias_post_create=False):
|
||||
"""Custom save method
|
||||
|
||||
Updating a domain may have consequences on other objects
|
||||
(domain alias, mailbox, quota). The most tricky part concerns
|
||||
quotas update.
|
||||
|
||||
"""
|
||||
d = super(DomainFormGeneral, self).save(commit=False)
|
||||
if commit:
|
||||
old_mail_homes = None
|
||||
if self.oldname is not None and d.name != self.oldname:
|
||||
d.name = self.oldname
|
||||
old_mail_homes = \
|
||||
dict((mb.id, mb.mail_home) for mb in d.mailbox_set.all())
|
||||
d.name = self.cleaned_data['name']
|
||||
d.save()
|
||||
Mailbox.objects.filter(domain=d, use_domain_quota=True) \
|
||||
.update(quota=d.quota)
|
||||
aliases = []
|
||||
for k, v in self.cleaned_data.iteritems():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if v in ["", None]:
|
||||
continue
|
||||
aliases.append(v)
|
||||
for dalias in d.domainalias_set.all():
|
||||
if dalias.name not in aliases:
|
||||
dalias.delete()
|
||||
else:
|
||||
aliases.remove(dalias.name)
|
||||
if aliases:
|
||||
events.raiseEvent(
|
||||
"CanCreate", user, "domain_aliases", len(aliases))
|
||||
for alias in aliases:
|
||||
try:
|
||||
d.domainalias_set.get(name=alias)
|
||||
except DomainAlias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
al = DomainAlias(name=alias, target=d, enabled=d.enabled)
|
||||
al.save(creator=user) if domalias_post_create else al.save()
|
||||
|
||||
if old_mail_homes is not None:
|
||||
self.update_mailbox_quotas(d)
|
||||
for mb in d.mailbox_set.all():
|
||||
mb.rename_dir(old_mail_homes[mb.id])
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class DomainFormOptions(forms.Form):
|
||||
create_dom_admin = YesNoField(
|
||||
label=ugettext_lazy("Create a domain administrator"),
|
||||
initial="no",
|
||||
help_text=ugettext_lazy(
|
||||
"Automatically create an administrator for this domain"
|
||||
)
|
||||
)
|
||||
|
||||
dom_admin_username = forms.CharField(
|
||||
label=ugettext_lazy("Name"),
|
||||
initial="admin",
|
||||
help_text=ugettext_lazy(
|
||||
"The administrator's name. Don't include the domain's name here, "
|
||||
"it will be automatically appended."
|
||||
),
|
||||
required=False
|
||||
)
|
||||
|
||||
create_aliases = YesNoField(
|
||||
label=ugettext_lazy("Create aliases"),
|
||||
initial="yes",
|
||||
help_text=ugettext_lazy(
|
||||
"Automatically create standard aliases for this domain"
|
||||
),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(DomainFormOptions, self).__init__(*args, **kwargs)
|
||||
result = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
|
||||
if False in result:
|
||||
self.fields = {}
|
||||
return
|
||||
if args:
|
||||
if args[0].get("create_dom_admin", "no") == "yes":
|
||||
self.fields["dom_admin_username"].required = True
|
||||
self.fields["create_aliases"].required = True
|
||||
|
||||
def clean_dom_admin_username(self):
|
||||
"""Ensure admin username is an email address."""
|
||||
if '@' in self.cleaned_data["dom_admin_username"]:
|
||||
raise forms.ValidationError(_("Invalid format"))
|
||||
return self.cleaned_data["dom_admin_username"]
|
||||
|
||||
def save(self, user, domain):
|
||||
if not self.fields:
|
||||
return
|
||||
if self.cleaned_data["create_dom_admin"] == "no":
|
||||
return
|
||||
username = "%s@%s" % (
|
||||
self.cleaned_data["dom_admin_username"], domain.name)
|
||||
try:
|
||||
da = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict(_("User '%s' already exists" % username))
|
||||
events.raiseEvent("CanCreate", user, "mailboxes")
|
||||
da = User(username=username, email=username, is_active=True)
|
||||
da.set_password("password")
|
||||
da.save()
|
||||
da.set_role("DomainAdmins")
|
||||
da.post_create(user)
|
||||
mb = Mailbox(
|
||||
address=self.cleaned_data["dom_admin_username"], domain=domain,
|
||||
user=da, use_domain_quota=True
|
||||
)
|
||||
mb.set_quota(override_rules=user.has_perm("admin.change_domain"))
|
||||
mb.save(creator=user)
|
||||
|
||||
if self.cleaned_data["create_aliases"] == "yes":
|
||||
events.raiseEvent("CanCreate", user, "mailbox_aliases")
|
||||
alias = Alias(address="postmaster", domain=domain, enabled=True)
|
||||
alias.save(int_rcpts=[mb])
|
||||
alias.post_create(user)
|
||||
|
||||
domain.add_admin(da)
|
||||
|
||||
|
||||
class DomainForm(TabForms):
|
||||
|
||||
"""Domain edition form."""
|
||||
|
||||
template_name = "admin/editdomainform.html"
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.user = request.user
|
||||
self.forms = []
|
||||
if self.user.has_perm("admin.change_domain"):
|
||||
self.forms.append({
|
||||
"id": "general",
|
||||
"title": _("General"),
|
||||
"formtpl": "admin/domain_general_form.html",
|
||||
"cls": DomainFormGeneral,
|
||||
"mandatory": True
|
||||
})
|
||||
|
||||
cbargs = [self.user]
|
||||
if "instances" in kwargs:
|
||||
cbargs += [kwargs["instances"]["general"]]
|
||||
self.forms += events.raiseQueryEvent("ExtraDomainForm", *cbargs)
|
||||
if not self.forms:
|
||||
self.active_id = "admins"
|
||||
super(DomainForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def extra_context(self, context):
|
||||
domain = self.instances["general"]
|
||||
domadmins = [u for u in domain.admins
|
||||
if self.request.user.can_access(u) and not u.is_superuser]
|
||||
if not self.request.user.is_superuser:
|
||||
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
|
||||
context.update({
|
||||
"title": domain.name,
|
||||
"action": reverse("admin:domain_change", args=[domain.pk]),
|
||||
"formid": "domform",
|
||||
"domain": domain,
|
||||
"domadmins": domadmins
|
||||
})
|
||||
|
||||
def is_valid(self):
|
||||
"""Custom validation.
|
||||
|
||||
We just save the current name before it is potentially
|
||||
modified.
|
||||
|
||||
"""
|
||||
self.instances["general"].oldname = self.instances["general"].name
|
||||
return super(DomainForm, self).is_valid()
|
||||
|
||||
def save(self):
|
||||
"""Custom save method.
|
||||
|
||||
As forms interact with each other, it is easier to make custom
|
||||
code to save them.
|
||||
"""
|
||||
self.forms[0]['instance'].save(
|
||||
self.request.user, domalias_post_create=True
|
||||
)
|
||||
for f in self.forms[1:]:
|
||||
f["instance"].save(self.request.user)
|
||||
|
||||
def done(self):
|
||||
events.raiseEvent("DomainModified", self.instances["general"])
|
||||
return render_to_json_response(_("Domain modified"))
|
||||
|
||||
|
||||
class DomainWizard(WizardForm):
|
||||
|
||||
"""Domain creation wizard.
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
super(DomainWizard, self).__init__(request)
|
||||
self.add_step(
|
||||
DomainFormGeneral, _("General"),
|
||||
formtpl="admin/domain_general_form.html"
|
||||
)
|
||||
self.add_step(
|
||||
DomainFormOptions, _("Options"),
|
||||
formtpl="admin/domain_options_form.html",
|
||||
new_args=[self.request.user]
|
||||
)
|
||||
|
||||
def extra_context(self, context):
|
||||
context.update({
|
||||
"title": _("New domain"),
|
||||
"action": reverse("admin:domain_add"),
|
||||
"formid": "domform"
|
||||
})
|
||||
|
||||
def done(self):
|
||||
genform = self.first_step.form
|
||||
domain = genform.save(self.request.user)
|
||||
domain.post_create(self.request.user)
|
||||
try:
|
||||
self.steps[1].form.save(self.request.user, domain)
|
||||
except ModoboaException:
|
||||
from django.db import transaction
|
||||
transaction.rollback()
|
||||
raise
|
||||
return render_to_json_response(_("Domain created"))
|
||||
@@ -1,45 +0,0 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
|
||||
class ExportDataForm(forms.Form):
|
||||
filename = forms.CharField(
|
||||
label=ugettext_lazy("File name"),
|
||||
max_length=100,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
sepchar = forms.CharField(
|
||||
label=ugettext_lazy("Separator"),
|
||||
max_length=1,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExportDataForm, self).__init__(*args, **kwargs)
|
||||
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
|
||||
|
||||
def clean_sepchar(self):
|
||||
if self.cleaned_data["sepchar"] == "":
|
||||
return ";"
|
||||
return str(self.cleaned_data["sepchar"])
|
||||
|
||||
def clean_filename(self):
|
||||
if self.cleaned_data["filename"] == "":
|
||||
return self.fields["filename"].initial
|
||||
return str(self.cleaned_data["filename"])
|
||||
|
||||
|
||||
class ExportDomainsForm(ExportDataForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExportDomainsForm, self).__init__(*args, **kwargs)
|
||||
self.fields["filename"].initial = "modoboa-domains.csv"
|
||||
|
||||
|
||||
class ExportIdentitiesForm(ExportDataForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExportIdentitiesForm, self).__init__(*args, **kwargs)
|
||||
self.fields["filename"].initial = "modoboa-identities.csv"
|
||||
@@ -1,46 +0,0 @@
|
||||
"""Forms related to forwards management."""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain
|
||||
)
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.lib.exceptions import BadRequest, PermDeniedException
|
||||
|
||||
|
||||
class ForwardForm(forms.Form):
|
||||
dest = forms.CharField(
|
||||
label=ugettext_lazy("Recipient(s)"),
|
||||
widget=forms.Textarea(attrs={"class": "form-control"}),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Indicate one or more recipients separated by a ','")
|
||||
)
|
||||
keepcopies = forms.BooleanField(
|
||||
label=ugettext_lazy("Keep local copies"),
|
||||
required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Forward messages and store copies into your local mailbox")
|
||||
)
|
||||
|
||||
def parse_dest(self):
|
||||
self.dests = []
|
||||
rawdata = self.cleaned_data["dest"].strip()
|
||||
if rawdata == "":
|
||||
return
|
||||
for d in rawdata.split(","):
|
||||
local_part, domname = split_mailbox(d)
|
||||
if not local_part or not domname or not len(domname):
|
||||
raise BadRequest("Invalid mailbox syntax for %s" % d)
|
||||
try:
|
||||
Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
self.dests += [d]
|
||||
else:
|
||||
raise PermDeniedException(
|
||||
_("You can't define a forward to a local destination. "
|
||||
"Please ask your administrator to create an alias "
|
||||
"instead.")
|
||||
)
|
||||
@@ -1,43 +0,0 @@
|
||||
"""Forms related to import operations."""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
|
||||
class ImportDataForm(forms.Form):
|
||||
|
||||
"""Base form to import objects."""
|
||||
|
||||
sourcefile = forms.FileField(label=ugettext_lazy("Select a file"))
|
||||
sepchar = forms.CharField(
|
||||
label=ugettext_lazy("Separator"),
|
||||
max_length=1,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
continue_if_exists = forms.BooleanField(
|
||||
label=ugettext_lazy("Continue on error"), required=False,
|
||||
help_text=ugettext_lazy("Don't treat duplicated objects as error")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ImportDataForm, self).__init__(*args, **kwargs)
|
||||
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
|
||||
|
||||
def clean_sepchar(self):
|
||||
if self.cleaned_data["sepchar"] == "":
|
||||
return ";"
|
||||
return str(self.cleaned_data["sepchar"])
|
||||
|
||||
|
||||
class ImportIdentitiesForm(ImportDataForm):
|
||||
|
||||
"""A form to import identities."""
|
||||
|
||||
crypt_password = forms.BooleanField(
|
||||
label=ugettext_lazy("Crypt passwords"), required=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Check this option if passwords contained in your file "
|
||||
"are not crypted"
|
||||
)
|
||||
)
|
||||
@@ -1,114 +0,0 @@
|
||||
# coding: utf-8
|
||||
from functools import wraps
|
||||
from itertools import chain
|
||||
from django.db.models import Q
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Alias
|
||||
)
|
||||
|
||||
|
||||
def needs_mailbox():
|
||||
"""Check if the current user owns at least one mailbox
|
||||
|
||||
Some applications (the webmail for example) need a mailbox to
|
||||
work.
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapped_f(request, *args, **kwargs):
|
||||
if request.user.mailbox_set.count():
|
||||
return f(request, *args, **kwargs)
|
||||
raise PermDeniedException(_("A mailbox is required"))
|
||||
return wrapped_f
|
||||
return decorator
|
||||
|
||||
|
||||
def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
|
||||
"""Return all the identities owned by a user.
|
||||
|
||||
:param user: the desired user
|
||||
:param str searchquery: search pattern
|
||||
:param list idtfilter: identity type filters
|
||||
:param list grpfilter: group names filters
|
||||
:return: a queryset
|
||||
"""
|
||||
accounts = []
|
||||
if idtfilter is None or not idtfilter or idtfilter == "account":
|
||||
ids = user.objectaccess_set \
|
||||
.filter(content_type=ContentType.objects.get_for_model(user)) \
|
||||
.values_list('object_id', flat=True)
|
||||
q = Q(pk__in=ids)
|
||||
if searchquery is not None:
|
||||
q &= Q(username__icontains=searchquery) \
|
||||
| Q(email__icontains=searchquery)
|
||||
if grpfilter is not None and grpfilter:
|
||||
if grpfilter == "SuperAdmins":
|
||||
q &= Q(is_superuser=True)
|
||||
else:
|
||||
q &= Q(groups__name=grpfilter)
|
||||
accounts = User.objects.select_related().filter(q)
|
||||
|
||||
aliases = []
|
||||
if idtfilter is None or not idtfilter \
|
||||
or (idtfilter in ["alias", "forward", "dlist"]):
|
||||
alct = ContentType.objects.get_for_model(Alias)
|
||||
ids = user.objectaccess_set.filter(content_type=alct) \
|
||||
.values_list('object_id', flat=True)
|
||||
q = Q(pk__in=ids)
|
||||
if searchquery is not None:
|
||||
if '@' in searchquery:
|
||||
local_part, domname = split_mailbox(searchquery)
|
||||
if local_part:
|
||||
q &= Q(address__icontains=local_part)
|
||||
if domname:
|
||||
q &= Q(domain__name__icontains=domname)
|
||||
else:
|
||||
q &= Q(address__icontains=searchquery) | \
|
||||
Q(domain__name__icontains=searchquery)
|
||||
aliases = Alias.objects.select_related().filter(q)
|
||||
if idtfilter is not None and idtfilter:
|
||||
aliases = [al for al in aliases if al.type == idtfilter]
|
||||
return chain(accounts, aliases)
|
||||
|
||||
|
||||
def get_domains(user, domfilter=None, searchquery=None, **extrafilters):
|
||||
"""Return all the domains the user can access.
|
||||
|
||||
:param ``User`` user: user object
|
||||
:param str searchquery: filter
|
||||
:rtype: list
|
||||
:return: a list of domains and/or relay domains
|
||||
"""
|
||||
domains = []
|
||||
if domfilter is None or not domfilter or domfilter == 'domain':
|
||||
domains = Domain.objects.get_for_admin(user)
|
||||
if searchquery is not None:
|
||||
q = Q(name__contains=searchquery)
|
||||
q |= Q(domainalias__name__contains=searchquery)
|
||||
domains = domains.filter(q).distinct()
|
||||
extra_domain_entries = events.raiseQueryEvent(
|
||||
'ExtraDomainEntries', user, domfilter, searchquery, **extrafilters
|
||||
)
|
||||
return chain(domains, extra_domain_entries)
|
||||
|
||||
|
||||
def check_if_domain_exists(name, extra_checks=None):
|
||||
"""Check if a domain already exists.
|
||||
|
||||
We not only look for domains, we also look for every object that
|
||||
could conflict with a domain (domain alias, etc.)
|
||||
|
||||
"""
|
||||
dtypes = events.raiseQueryEvent('CheckDomainName')
|
||||
if extra_checks is not None:
|
||||
dtypes = extra_checks + dtypes
|
||||
for dtype, label in dtypes:
|
||||
if dtype.objects.filter(name=name).exists():
|
||||
return label
|
||||
return None
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
import logging
|
||||
from optparse import make_option
|
||||
import os
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from modoboa.core.management.commands import CloseConnectionMixin
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.lib.exceptions import InternalError
|
||||
from modoboa.extensions.admin.models import MailboxOperation
|
||||
|
||||
|
||||
class OperationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Command(BaseCommand, CloseConnectionMixin):
|
||||
help = 'Handles rename and delete operations on mailboxes'
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option(
|
||||
'--pidfile', default='/tmp/handle_mailbox_operations.pid',
|
||||
help='Path to the file that will contain the PID of this process'
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Command, self).__init__(*args, **kwargs)
|
||||
self.logger = logging.getLogger('modoboa.admin')
|
||||
|
||||
def rename_mailbox(self, operation):
|
||||
if not os.path.exists(operation.argument):
|
||||
return
|
||||
new_mail_home = operation.mailbox.mail_home
|
||||
dirname = os.path.dirname(new_mail_home)
|
||||
if not os.path.exists(dirname):
|
||||
try:
|
||||
os.makedirs(dirname)
|
||||
except os.error as e:
|
||||
raise OperationError(str(e))
|
||||
code, output = exec_cmd(
|
||||
"mv %s %s" % (operation.argument, new_mail_home)
|
||||
)
|
||||
if code:
|
||||
raise OperationError(output)
|
||||
|
||||
def delete_mailbox(self, operation):
|
||||
if not os.path.exists(operation.argument):
|
||||
return
|
||||
code, output = exec_cmd(
|
||||
"rm -r %s" % operation.argument
|
||||
)
|
||||
if code:
|
||||
raise OperationError(output)
|
||||
|
||||
def check_pidfile(self, path):
|
||||
"""Check if this command is already running
|
||||
|
||||
:param str path: path to the file containing the PID
|
||||
:return: a boolean, True means we can go further
|
||||
"""
|
||||
if os.path.exists(path):
|
||||
with open(path) as fp:
|
||||
pid = fp.read().strip()
|
||||
code, output = exec_cmd(
|
||||
"grep handle_mailbox_operations /proc/%s/cmdline" % pid
|
||||
)
|
||||
if not code:
|
||||
return False
|
||||
with open(path, 'w') as fp:
|
||||
print >> fp, os.getpid()
|
||||
return True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
if parameters.get_admin("HANDLE_MAILBOXES") == 'no':
|
||||
return
|
||||
except parameters.NotDefined:
|
||||
return
|
||||
if not self.check_pidfile(options['pidfile']):
|
||||
return
|
||||
for ope in MailboxOperation.objects.all():
|
||||
try:
|
||||
f = getattr(self, '%s_mailbox' % ope.type)
|
||||
except AttributeError:
|
||||
continue
|
||||
try:
|
||||
f(ope)
|
||||
except (OperationError, InternalError) as e:
|
||||
self.logger.critical('%s failed (reason: %s)',
|
||||
ope, str(e).encode('utf-8'))
|
||||
else:
|
||||
self.logger.info('%s succeed', ope)
|
||||
ope.delete()
|
||||
os.unlink(options['pidfile'])
|
||||
@@ -1,161 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Alias',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('address', models.CharField(help_text="The alias address (without the domain part). For a 'catch-all' address, just enter an * character.", max_length=254, verbose_name='address')),
|
||||
('extmboxes', models.TextField(blank=True)),
|
||||
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
|
||||
('aliases', models.ManyToManyField(help_text='The aliases this alias points to', to='admin.Alias')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['domain__name', 'address'],
|
||||
'permissions': (('view_aliases', 'View aliases'),),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(help_text='The domain name', unique=True, max_length=100, verbose_name='name')),
|
||||
('quota', models.IntegerField()),
|
||||
('enabled', models.BooleanField(default=True, help_text='Check to activate this domain', verbose_name='enabled')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'permissions': (('view_domain', 'View domain'), ('view_domains', 'View domains')),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DomainAlias',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
|
||||
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
|
||||
],
|
||||
options={
|
||||
'permissions': (('view_domaliases', 'View domain aliases'),),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Mailbox',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('address', models.CharField(help_text='Mailbox address (without the @domain.tld part)', max_length=252, verbose_name='address')),
|
||||
('quota', models.PositiveIntegerField()),
|
||||
('use_domain_quota', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'permissions': (('view_mailboxes', 'View mailboxes'),),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MailboxOperation',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('type', models.CharField(max_length=20, choices=[(b'rename', b'rename'), (b'delete', b'delete')])),
|
||||
('argument', models.TextField()),
|
||||
('mailbox', models.ForeignKey(blank=True, to='admin.Mailbox', null=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ObjectDates',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('creation', models.DateTimeField(auto_now_add=True)),
|
||||
('last_modification', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quota',
|
||||
fields=[
|
||||
('username', models.EmailField(max_length=254, serialize=False, primary_key=True)),
|
||||
('bytes', models.BigIntegerField(default=0)),
|
||||
('messages', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailbox',
|
||||
name='dates',
|
||||
field=models.ForeignKey(to='admin.ObjectDates'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailbox',
|
||||
name='domain',
|
||||
field=models.ForeignKey(to='admin.Domain'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailbox',
|
||||
name='user',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domainalias',
|
||||
name='dates',
|
||||
field=models.ForeignKey(to='admin.ObjectDates'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domainalias',
|
||||
name='target',
|
||||
field=models.ForeignKey(verbose_name='target', to='admin.Domain', help_text='The domain this alias points to'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='dates',
|
||||
field=models.ForeignKey(to='admin.ObjectDates'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alias',
|
||||
name='dates',
|
||||
field=models.ForeignKey(to='admin.ObjectDates'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alias',
|
||||
name='domain',
|
||||
field=models.ForeignKey(to='admin.Domain'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alias',
|
||||
name='mboxes',
|
||||
field=models.ManyToManyField(help_text='The mailboxes this alias points to', to='admin.Mailbox', verbose_name='mailboxes'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='alias',
|
||||
unique_together=set([('address', 'domain')]),
|
||||
),
|
||||
]
|
||||
@@ -1,10 +0,0 @@
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from .domain_alias import DomainAlias
|
||||
from .mailbox import Mailbox, Quota, MailboxOperation
|
||||
from .alias import Alias
|
||||
|
||||
__all__ = [
|
||||
'Domain', 'DomainAlias', 'Mailbox', 'Quota', 'Alias',
|
||||
'MailboxOperation', 'AdminObject'
|
||||
]
|
||||
@@ -1,210 +0,0 @@
|
||||
"""Models related to aliases management."""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
import reversion
|
||||
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from .mailbox import Mailbox
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.lib.exceptions import (
|
||||
PermDeniedException, BadRequest, Conflict
|
||||
)
|
||||
|
||||
|
||||
class Alias(AdminObject):
|
||||
|
||||
"""
|
||||
Mailbox alias.
|
||||
"""
|
||||
address = models.CharField(
|
||||
ugettext_lazy('address'), max_length=254,
|
||||
help_text=ugettext_lazy(
|
||||
"The alias address (without the domain part). For a 'catch-all' "
|
||||
"address, just enter an * character."
|
||||
)
|
||||
)
|
||||
domain = models.ForeignKey(Domain)
|
||||
mboxes = models.ManyToManyField(
|
||||
Mailbox, verbose_name=ugettext_lazy('mailboxes'),
|
||||
help_text=ugettext_lazy("The mailboxes this alias points to")
|
||||
)
|
||||
aliases = models.ManyToManyField(
|
||||
'Alias',
|
||||
help_text=ugettext_lazy("The aliases this alias points to")
|
||||
)
|
||||
extmboxes = models.TextField(blank=True)
|
||||
enabled = models.BooleanField(
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy("Check to activate this alias"),
|
||||
default=True
|
||||
)
|
||||
_objectname = 'MailboxAlias'
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_aliases", "View aliases"),
|
||||
)
|
||||
unique_together = (("address", "domain"),)
|
||||
ordering = ["domain__name", "address"]
|
||||
app_label = 'admin'
|
||||
|
||||
def __unicode__(self):
|
||||
return self.full_address
|
||||
|
||||
@property
|
||||
def full_address(self):
|
||||
return "%s@%s" % (self.address, self.domain.name)
|
||||
|
||||
@property
|
||||
def identity(self):
|
||||
return self.full_address
|
||||
|
||||
@property
|
||||
def name_or_rcpt(self):
|
||||
rcpts_count = self.get_recipients_count()
|
||||
if not rcpts_count:
|
||||
return "---"
|
||||
rcpts = self.get_recipients()
|
||||
if rcpts_count > 1:
|
||||
return "%s, ..." % rcpts[0]
|
||||
return rcpts[0]
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
cpt = self.get_recipients_count()
|
||||
if cpt > 1:
|
||||
return "dlist"
|
||||
if self.extmboxes != "":
|
||||
return "forward"
|
||||
return "alias"
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
labels = {
|
||||
"dlist": _("distribution list"),
|
||||
"forward": _("forward"),
|
||||
"alias": _("alias")
|
||||
}
|
||||
altype = self.type
|
||||
return [{"name": altype, "label": labels[altype], "type": "idt"}]
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
super(Alias, self).post_create(creator)
|
||||
if creator.is_superuser:
|
||||
for admin in self.domain.admins:
|
||||
grant_access_to_object(admin, self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Custom save method."""
|
||||
if 'ext_rcpts' in kwargs:
|
||||
self.extmboxes = ",".join(kwargs['ext_rcpts']) \
|
||||
if kwargs['ext_rcpts'] else ""
|
||||
del kwargs['ext_rcpts']
|
||||
if 'int_rcpts' in kwargs:
|
||||
int_rcpts = kwargs['int_rcpts']
|
||||
del kwargs['int_rcpts']
|
||||
else:
|
||||
int_rcpts = []
|
||||
super(Alias, self).save(*args, **kwargs)
|
||||
curaliases = self.aliases.all()
|
||||
curmboxes = self.mboxes.all()
|
||||
for rcpt in int_rcpts:
|
||||
if isinstance(rcpt, Alias):
|
||||
if rcpt not in curaliases:
|
||||
self.aliases.add(rcpt)
|
||||
continue
|
||||
if rcpt not in curmboxes:
|
||||
self.mboxes.add(rcpt)
|
||||
for rcpt in curaliases:
|
||||
if rcpt not in int_rcpts:
|
||||
self.aliases.remove(rcpt)
|
||||
for rcpt in curmboxes:
|
||||
if rcpt not in int_rcpts:
|
||||
self.mboxes.remove(rcpt)
|
||||
|
||||
def get_recipients(self, with_external=True):
|
||||
"""Return the recipients list.
|
||||
|
||||
Internal and external addresses are mixed into a single list.
|
||||
|
||||
:param bool with_external: include external addresses or not
|
||||
:rtype: list
|
||||
:return: a list of addresses
|
||||
"""
|
||||
result = [al.full_address for al in self.aliases.all()]
|
||||
result += [mb.full_address for mb in self.mboxes.all()]
|
||||
if with_external and self.extmboxes != "":
|
||||
result += self.extmboxes.split(',')
|
||||
return result
|
||||
|
||||
def get_recipients_count(self):
|
||||
"""Return the number of recipients of this alias.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
total = 0
|
||||
if self.extmboxes != "":
|
||||
total += len(self.extmboxes.split(','))
|
||||
return total + self.aliases.count() + self.mboxes.count()
|
||||
|
||||
def from_csv(self, user, row, expected_elements=5):
|
||||
"""Create a new alias from a CSV file entry
|
||||
|
||||
"""
|
||||
if len(row) < expected_elements:
|
||||
raise BadRequest(_("Invalid line: %s" % row))
|
||||
localpart, domname = split_mailbox(row[1].strip())
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise BadRequest(_("Domain '%s' does not exist" % domname))
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
try:
|
||||
Alias.objects.get(address=localpart, domain__name=domain)
|
||||
except Alias.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict
|
||||
self.address = localpart
|
||||
self.domain = domain
|
||||
self.enabled = (row[2].strip() in ["True", "1", "yes", "y"])
|
||||
int_rcpts = []
|
||||
ext_rcpts = []
|
||||
for rcpt in row[3:]:
|
||||
rcpt = rcpt.strip()
|
||||
if not rcpt:
|
||||
continue
|
||||
localpart, domname = split_mailbox(rcpt)
|
||||
try:
|
||||
Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
ext_rcpts += [rcpt]
|
||||
continue
|
||||
try:
|
||||
target = Alias.objects.get(
|
||||
domain__name=domname, address=localpart
|
||||
)
|
||||
if target.full_address == self.full_address:
|
||||
target = None
|
||||
except Alias.DoesNotExist:
|
||||
target = None
|
||||
if target is None:
|
||||
try:
|
||||
target = Mailbox.objects.get(address=localpart,
|
||||
domain__name=domname)
|
||||
except Mailbox.DoesNotExist:
|
||||
raise BadRequest(_("Local recipient %s not found" % rcpt))
|
||||
int_rcpts += [target]
|
||||
self.save(int_rcpts=int_rcpts, ext_rcpts=ext_rcpts, creator=user)
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
row = [self.type, self.full_address.encode("utf-8"), self.enabled]
|
||||
row += self.get_recipients()
|
||||
csvwriter.writerow(row)
|
||||
|
||||
reversion.register(Alias)
|
||||
@@ -1,81 +0,0 @@
|
||||
from django.db import models
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.permissions import (
|
||||
grant_access_to_object, ungrant_access_to_object
|
||||
)
|
||||
|
||||
|
||||
class ObjectDates(models.Model):
|
||||
|
||||
"""Dates recording for admin objects
|
||||
|
||||
This table keeps creation and last modification dates for Domains,
|
||||
domain aliases, mailboxes and aliases objects.
|
||||
"""
|
||||
creation = models.DateTimeField(auto_now_add=True)
|
||||
last_modification = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'admin'
|
||||
|
||||
@staticmethod
|
||||
def set_for_object(obj):
|
||||
"""Initialize or update dates for a given object.
|
||||
|
||||
:param obj: an admin object (Domain, Mailbox, etc)
|
||||
"""
|
||||
try:
|
||||
dates = getattr(obj, "dates")
|
||||
except ObjectDates.DoesNotExist:
|
||||
dates = ObjectDates()
|
||||
dates.save()
|
||||
obj.dates = dates
|
||||
|
||||
|
||||
class AdminObject(models.Model):
|
||||
|
||||
"""Abstract model to support dates
|
||||
|
||||
Inherit from this model to automatically add the "dates" feature
|
||||
to another model. It defines the appropriate field and handles
|
||||
saves.
|
||||
"""
|
||||
dates = models.ForeignKey(ObjectDates)
|
||||
_objectname = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def creation(self):
|
||||
return self.dates.creation
|
||||
|
||||
@property
|
||||
def last_modification(self):
|
||||
return self.dates.last_modification
|
||||
|
||||
@property
|
||||
def objectname(self):
|
||||
if self._objectname is None:
|
||||
return self.__class__.__name__
|
||||
return self._objectname
|
||||
|
||||
def post_create(self, creator):
|
||||
grant_access_to_object(creator, self, is_owner=True)
|
||||
events.raiseEvent("%sCreated" % self.objectname, creator, self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
ObjectDates.set_for_object(self)
|
||||
if "creator" in kwargs:
|
||||
creator = kwargs["creator"]
|
||||
del kwargs["creator"]
|
||||
else:
|
||||
creator = None
|
||||
super(AdminObject, self).save(*args, **kwargs)
|
||||
if creator is not None:
|
||||
self.post_create(creator)
|
||||
|
||||
def delete(self):
|
||||
events.raiseEvent("%sDeleted" % self.objectname, self)
|
||||
ungrant_access_to_object(self)
|
||||
super(AdminObject, self).delete()
|
||||
@@ -1,185 +0,0 @@
|
||||
"""Models related to domains management."""
|
||||
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
import reversion
|
||||
|
||||
from .base import AdminObject
|
||||
from modoboa.core.models import User, ObjectAccess
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.lib.exceptions import BadRequest, Conflict
|
||||
|
||||
|
||||
class DomainManager(Manager):
|
||||
|
||||
def get_for_admin(self, admin):
|
||||
"""Return the domains belonging to this admin
|
||||
|
||||
The result is a ``QuerySet`` object, so this function can be used
|
||||
to fill ``ModelChoiceField`` objects.
|
||||
"""
|
||||
if admin.is_superuser:
|
||||
return self.get_query_set()
|
||||
return self.get_query_set().filter(owners__user=admin)
|
||||
|
||||
|
||||
class Domain(AdminObject):
|
||||
|
||||
"""Mail domain."""
|
||||
|
||||
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy("The domain name"))
|
||||
quota = models.IntegerField()
|
||||
enabled = models.BooleanField(
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy("Check to activate this domain"),
|
||||
default=True
|
||||
)
|
||||
owners = generic.GenericRelation(ObjectAccess)
|
||||
|
||||
objects = DomainManager()
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_domain", "View domain"),
|
||||
("view_domains", "View domains"),
|
||||
)
|
||||
ordering = ["name"]
|
||||
app_label = 'admin'
|
||||
|
||||
@property
|
||||
def domainalias_count(self):
|
||||
return self.domainalias_set.count()
|
||||
|
||||
@property
|
||||
def mailbox_count(self):
|
||||
return self.mailbox_set.count()
|
||||
|
||||
@property
|
||||
def mbalias_count(self):
|
||||
return self.alias_set.count()
|
||||
|
||||
@property
|
||||
def identities_count(self):
|
||||
"""Total number of identities in this domain."""
|
||||
return self.mailbox_set.count() + self.alias_set.count()
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return [{"name": "domain", "label": _("Domain"), "type": "dom"}]
|
||||
|
||||
@property
|
||||
def admins(self):
|
||||
"""Return the domain administrators of this domain
|
||||
|
||||
:return: a list of User objects
|
||||
"""
|
||||
return [oa.user for oa in self.owners.filter(user__is_superuser=False)]
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
return self.domainalias_set
|
||||
|
||||
def add_admin(self, account):
|
||||
"""Add a new administrator for this domain
|
||||
|
||||
:param User account: the administrotor to add
|
||||
"""
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
grant_access_to_object(account, self)
|
||||
for mb in self.mailbox_set.all():
|
||||
if mb.user.has_perm("admin.add_domain"):
|
||||
continue
|
||||
grant_access_to_object(account, mb)
|
||||
grant_access_to_object(account, mb.user)
|
||||
for al in self.alias_set.all():
|
||||
grant_access_to_object(account, al)
|
||||
|
||||
def remove_admin(self, account):
|
||||
"""Remove an administrator of this domain.
|
||||
|
||||
:param User account: administrator to remove
|
||||
"""
|
||||
from modoboa.lib.permissions import \
|
||||
ungrant_access_to_object, get_object_owner
|
||||
|
||||
if get_object_owner(self) == account:
|
||||
events.raiseEvent('DomainOwnershipRemoved', account, self)
|
||||
ungrant_access_to_object(self, account)
|
||||
for mb in self.mailbox_set.all():
|
||||
if mb.user.has_perm("admin.add_domain"):
|
||||
continue
|
||||
ungrant_access_to_object(mb, account)
|
||||
ungrant_access_to_object(mb.user, account)
|
||||
for al in self.alias_set.all():
|
||||
ungrant_access_to_object(al, account)
|
||||
|
||||
def delete(self, fromuser, keepdir=False):
|
||||
"""Custom delete method.
|
||||
"""
|
||||
from modoboa.lib.permissions import ungrant_access_to_objects
|
||||
from .mailbox import Quota
|
||||
|
||||
if self.domainalias_set.count():
|
||||
events.raiseEvent("DomainAliasDeleted", self.domainalias_set.all())
|
||||
ungrant_access_to_objects(self.domainalias_set.all())
|
||||
if self.alias_set.count():
|
||||
events.raiseEvent("MailboxAliasDeleted", self.alias_set.all())
|
||||
ungrant_access_to_objects(self.alias_set.all())
|
||||
if parameters.get_admin("AUTO_ACCOUNT_REMOVAL") == "yes":
|
||||
for account in User.objects.filter(mailbox__domain__name=self.name):
|
||||
account.delete(fromuser, keepdir)
|
||||
elif self.mailbox_set.count():
|
||||
Quota.objects.filter(username__contains='@%s' % self.name).delete()
|
||||
events.raiseEvent("MailboxDeleted", self.mailbox_set.all())
|
||||
ungrant_access_to_objects(self.mailbox_set.all())
|
||||
super(Domain, self).delete()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def from_csv(self, user, row):
|
||||
"""Create a new domain from a CSV entry.
|
||||
|
||||
The expected fields order is the following::
|
||||
|
||||
"domain", name, quota, enabled
|
||||
|
||||
:param ``core.User`` user: user creating the domain
|
||||
:param str row: a list containing domain's definition
|
||||
"""
|
||||
if len(row) < 4:
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
try:
|
||||
Domain.objects.get(name=self.name)
|
||||
except Domain.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict
|
||||
try:
|
||||
self.quota = int(row[2].strip())
|
||||
except ValueError:
|
||||
raise BadRequest(
|
||||
_("Invalid quota value for domain '%s'" % self.name))
|
||||
self.enabled = (row[3].strip() in ["True", "1", "yes", "y"])
|
||||
self.save(creator=user)
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
csvwriter.writerow(["domain", self.name, self.quota, self.enabled])
|
||||
for dalias in self.domainalias_set.all():
|
||||
dalias.to_csv(csvwriter)
|
||||
|
||||
def post_create(self, creator):
|
||||
"""Post creation actions.
|
||||
|
||||
:param ``User`` creator: user whos created this domain
|
||||
"""
|
||||
super(Domain, self).post_create(creator)
|
||||
for domalias in self.domainalias_set.all():
|
||||
domalias.post_create(creator)
|
||||
|
||||
reversion.register(Domain)
|
||||
@@ -1,72 +0,0 @@
|
||||
"""Models related to domain aliases management."""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
import reversion
|
||||
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from modoboa.lib.exceptions import BadRequest, Conflict
|
||||
|
||||
|
||||
class DomainAlias(AdminObject):
|
||||
|
||||
"""Domain aliases."""
|
||||
|
||||
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
|
||||
help_text=ugettext_lazy("The alias name"))
|
||||
target = models.ForeignKey(
|
||||
Domain, verbose_name=ugettext_lazy('target'),
|
||||
help_text=ugettext_lazy("The domain this alias points to")
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
ugettext_lazy('enabled'),
|
||||
help_text=ugettext_lazy("Check to activate this alias"),
|
||||
default=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_domaliases", "View domain aliases"),
|
||||
)
|
||||
app_label = 'admin'
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def from_csv(self, user, row):
|
||||
"""Create a domain alias from a CSV row
|
||||
|
||||
Expected format: ["domainalias", domain alias name, targeted domain, enabled]
|
||||
|
||||
:param user: a ``User`` object
|
||||
:param row: a list containing the alias definition
|
||||
"""
|
||||
if len(row) < 4:
|
||||
raise BadRequest(_("Invalid line"))
|
||||
self.name = row[1].strip()
|
||||
for model in [DomainAlias, Domain]:
|
||||
try:
|
||||
model.objects.get(name=self.name)
|
||||
except model.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict
|
||||
domname = row[2].strip()
|
||||
try:
|
||||
self.target = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise BadRequest(_("Unknown domain %s" % domname))
|
||||
self.enabled = row[3].strip() in ["True", "1", "yes", "y"]
|
||||
self.save(creator=user)
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
"""Export a domain alias using CSV format
|
||||
|
||||
:param csvwriter: a ``csv.writer`` object
|
||||
"""
|
||||
csvwriter.writerow(["domainalias", self.name,
|
||||
self.target.name, self.enabled])
|
||||
|
||||
reversion.register(DomainAlias)
|
||||
@@ -1,334 +0,0 @@
|
||||
"""Models related to mailboxes management."""
|
||||
|
||||
import os
|
||||
import pwd
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
import reversion
|
||||
|
||||
from .base import AdminObject
|
||||
from .domain import Domain
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import BadRequest, InternalError
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
|
||||
|
||||
class Quota(models.Model):
|
||||
|
||||
"""Keeps track of Mailbox current quota."""
|
||||
|
||||
username = models.EmailField(primary_key=True, max_length=254)
|
||||
bytes = models.BigIntegerField(default=0)
|
||||
messages = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
app_label = 'admin'
|
||||
|
||||
|
||||
class MailboxManager(Manager):
|
||||
|
||||
"""Custom manager for Mailbox."""
|
||||
|
||||
def get_for_admin(self, admin, squery=None):
|
||||
"""Return the mailboxes that belong to this admin.
|
||||
|
||||
The result will contain the mailboxes defined for each domain that
|
||||
user can see.
|
||||
|
||||
:param string squery: a search query
|
||||
:return: a list of ``Mailbox`` objects
|
||||
"""
|
||||
qf = None
|
||||
if squery is not None:
|
||||
if '@' in squery:
|
||||
parts = squery.split('@')
|
||||
addrfilter = '@'.join(parts[:-1])
|
||||
domfilter = parts[-1]
|
||||
qf = (
|
||||
Q(address__contains=addrfilter) &
|
||||
Q(domain__name__contains=domfilter)
|
||||
)
|
||||
else:
|
||||
qf = (
|
||||
Q(address__contains=squery) |
|
||||
Q(domain__name__contains=squery)
|
||||
)
|
||||
ids = admin.objectaccess_set \
|
||||
.filter(content_type=ContentType.objects.get_for_model(Mailbox)) \
|
||||
.values_list('object_id', flat=True)
|
||||
if qf is not None:
|
||||
qf = Q(pk__in=ids) & qf
|
||||
else:
|
||||
qf = Q(pk__in=ids)
|
||||
return self.get_query_set().select_related().filter(qf)
|
||||
|
||||
|
||||
class Mailbox(AdminObject):
|
||||
|
||||
"""User mailbox."""
|
||||
|
||||
address = models.CharField(
|
||||
ugettext_lazy('address'), max_length=252,
|
||||
help_text=ugettext_lazy(
|
||||
"Mailbox address (without the @domain.tld part)")
|
||||
)
|
||||
quota = models.PositiveIntegerField()
|
||||
use_domain_quota = models.BooleanField(default=False)
|
||||
domain = models.ForeignKey(Domain)
|
||||
user = models.ForeignKey(User)
|
||||
|
||||
objects = MailboxManager()
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_mailboxes", "View mailboxes"),
|
||||
)
|
||||
app_label = 'admin'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Mailbox, self).__init__(*args, **kwargs)
|
||||
self.__mail_home = None
|
||||
|
||||
def __str__(self):
|
||||
return self.full_address
|
||||
|
||||
def __full_address(self, localpart):
|
||||
return "%s@%s" % (localpart, self.domain.name)
|
||||
|
||||
@property
|
||||
def full_address(self):
|
||||
return self.__full_address(self.address)
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.user.is_active
|
||||
|
||||
@property
|
||||
def alias_count(self):
|
||||
return self.alias_set.count()
|
||||
|
||||
@property
|
||||
def mail_home(self):
|
||||
"""Retrieve the home directory of this mailbox.
|
||||
|
||||
The home directory refers to the place on the file system
|
||||
where the mailbox data is stored.
|
||||
|
||||
We ask dovecot to give us this information because there are
|
||||
several patterns to understand and we don't want to implement
|
||||
them.
|
||||
"""
|
||||
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
|
||||
if hm is None or hm == "no":
|
||||
return None
|
||||
if self.__mail_home is None:
|
||||
curuser = pwd.getpwuid(os.getuid()).pw_name
|
||||
mbowner = parameters.get_admin("MAILBOXES_OWNER")
|
||||
options = {}
|
||||
if curuser != mbowner:
|
||||
options['sudo_user'] = mbowner
|
||||
code, output = exec_cmd(
|
||||
"doveadm user %s -f home" % self.full_address, **options
|
||||
)
|
||||
if code:
|
||||
raise InternalError(
|
||||
_("Failed to retrieve mailbox location (%s)" % output))
|
||||
self.__mail_home = output.strip()
|
||||
return self.__mail_home
|
||||
|
||||
@property
|
||||
def alias_addresses(self):
|
||||
"""Return all alias address of this mailbox.
|
||||
|
||||
:rtype: list of string
|
||||
"""
|
||||
aliases = []
|
||||
for alias in self.alias_set.all():
|
||||
aliases += [alias.full_address]
|
||||
return aliases
|
||||
|
||||
@property
|
||||
def quota_value(self):
|
||||
"""Retrieve the ``Quota`` instance associated to this mailbox."""
|
||||
if not hasattr(self, "_quota_value"):
|
||||
try:
|
||||
self._quota_value = Quota.objects.get(
|
||||
username=self.full_address)
|
||||
except Quota.DoesNotExist:
|
||||
return None
|
||||
return self._quota_value
|
||||
|
||||
@quota_value.setter
|
||||
def quota_value(self, instance):
|
||||
"""Set the ``Quota`` for this mailbox."""
|
||||
self._quota_value = instance
|
||||
|
||||
def rename_dir(self, old_mail_home):
|
||||
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
|
||||
if hm is None or hm == "no":
|
||||
return
|
||||
MailboxOperation.objects.create(
|
||||
mailbox=self, type='rename', argument=old_mail_home
|
||||
)
|
||||
|
||||
def rename(self, address, domain):
|
||||
"""Rename the mailbox.
|
||||
|
||||
To update the associated Quota record, we must create a new
|
||||
one first, update the foreign key and then we can delete the
|
||||
original record!
|
||||
|
||||
:param string address: the new mailbox's address (local part)
|
||||
:param Domain domain: the new mailbox's domain
|
||||
|
||||
"""
|
||||
old_mail_home = self.mail_home
|
||||
old_qvalue = self.quota_value
|
||||
self.address = address
|
||||
self.domain = domain
|
||||
self.quota_value = Quota.objects.create(
|
||||
username=self.full_address, bytes=old_qvalue.bytes,
|
||||
messages=old_qvalue.messages
|
||||
)
|
||||
old_qvalue.delete()
|
||||
self.rename_dir(old_mail_home)
|
||||
|
||||
def delete_dir(self):
|
||||
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
|
||||
if hm is None or hm == "no":
|
||||
return
|
||||
MailboxOperation.objects.create(type='delete', argument=self.mail_home)
|
||||
|
||||
def set_quota(self, value=None, override_rules=False):
|
||||
"""Set or update quota's value for this mailbox.
|
||||
|
||||
A value equal to 0 means the mailbox won't have any quota. The
|
||||
following cases allow people to define such behaviour:
|
||||
* The domain has no quota
|
||||
* :keyword:`override_rules` is True
|
||||
|
||||
:param integer value: the quota's value
|
||||
:param bool override_rules: allow to override defined quota rules
|
||||
"""
|
||||
if value is None:
|
||||
if self.use_domain_quota:
|
||||
self.quota = self.domain.quota
|
||||
else:
|
||||
self.quota = 0
|
||||
elif int(value) > self.domain.quota and not override_rules:
|
||||
raise BadRequest(
|
||||
_("Quota is greater than the allowed domain's limit (%dM)"
|
||||
% self.domain.quota)
|
||||
)
|
||||
else:
|
||||
self.quota = value
|
||||
if not self.quota and self.domain.quota and not override_rules:
|
||||
raise BadRequest(_("A quota is required"))
|
||||
|
||||
def get_quota(self):
|
||||
"""Get quota limit.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
return int(self.quota_value.bytes / 1048576)
|
||||
|
||||
def get_quota_in_percent(self):
|
||||
"""Get current quota usage.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
if not self.quota:
|
||||
return 0
|
||||
return int(
|
||||
self.quota_value.bytes / float(self.quota * 1048576) * 100
|
||||
)
|
||||
|
||||
def post_create(self, creator):
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
super(Mailbox, self).post_create(creator)
|
||||
if creator.is_superuser and not self.user.has_perm("admin.add_domain"):
|
||||
# A super user is creating a new mailbox. Give
|
||||
# access to that mailbox (and the associated
|
||||
# account) to the appropriate domain admins,
|
||||
# except if the new account has a more important
|
||||
# role (SuperAdmin, Reseller)
|
||||
for admin in self.domain.admins:
|
||||
grant_access_to_object(admin, self)
|
||||
grant_access_to_object(admin, self.user)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Custom save.
|
||||
|
||||
We just make sure a quota record is defined for this mailbox.
|
||||
"""
|
||||
if self.quota_value is None:
|
||||
self.quota_value, created = Quota.objects.get_or_create(
|
||||
username=self.full_address)
|
||||
super(Mailbox, self).save(*args, **kwargs)
|
||||
|
||||
def delete(self, keepdir=False):
|
||||
"""Custom delete method
|
||||
|
||||
We try to delete the associated quota in the same time (it may
|
||||
has already been removed if we're deleting a domain).
|
||||
|
||||
:param bool keepdir: delete the mailbox home dir on the
|
||||
filesystem or not
|
||||
|
||||
"""
|
||||
try:
|
||||
q = Quota.objects.get(username=self.full_address)
|
||||
except Quota.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
q.delete()
|
||||
if not keepdir:
|
||||
self.delete_dir()
|
||||
super(Mailbox, self).delete()
|
||||
|
||||
reversion.register(Mailbox)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Mailbox)
|
||||
def mailbox_deleted_handler(sender, **kwargs):
|
||||
"""``Mailbox`` pre_delete signal receiver
|
||||
|
||||
In order to properly handle deletions (ie. we don't want to leave
|
||||
orphan records into the db), we define this custom receiver.
|
||||
|
||||
It manually removes the mailbox from the aliases it is linked to
|
||||
and then remove all empty aliases.
|
||||
"""
|
||||
from modoboa.lib.permissions import ungrant_access_to_object
|
||||
|
||||
mb = kwargs['instance']
|
||||
events.raiseEvent("MailboxDeleted", mb)
|
||||
ungrant_access_to_object(mb)
|
||||
for alias in mb.alias_set.all():
|
||||
alias.mboxes.remove(mb)
|
||||
if alias.mboxes.count() == 0:
|
||||
alias.delete()
|
||||
|
||||
|
||||
class MailboxOperation(models.Model):
|
||||
mailbox = models.ForeignKey(Mailbox, blank=True, null=True)
|
||||
type = models.CharField(
|
||||
max_length=20, choices=(('rename', 'rename'), ('delete', 'delete'))
|
||||
)
|
||||
argument = models.TextField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'admin'
|
||||
|
||||
def __str__(self):
|
||||
if self.type == 'rename':
|
||||
return 'Rename %s -> %s' % (self.argument, self.mailbox.mail_home)
|
||||
return 'Delete %s' % self.argument
|
||||
@@ -1,285 +0,0 @@
|
||||
"""Registration with Modoboa takes place here."""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.core.extensions import ModoExtension, exts_pool
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Mailbox, Alias
|
||||
)
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.email_utils import split_mailbox
|
||||
from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
|
||||
|
||||
|
||||
ADMIN_EVENTS = [
|
||||
"DomainCreated",
|
||||
"DomainModified",
|
||||
"DomainDeleted",
|
||||
"DomainOwnershipRemoved",
|
||||
"ExtraDomainEntries",
|
||||
"ExtraDomainMenuEntries",
|
||||
"ExtraDomainFilters",
|
||||
"GetDomainActions",
|
||||
"GetDomainModifyLink",
|
||||
"CheckDomainName",
|
||||
|
||||
"DomainAliasCreated",
|
||||
"DomainAliasDeleted",
|
||||
|
||||
"MailboxCreated",
|
||||
"MailboxDeleted",
|
||||
"MailboxModified",
|
||||
|
||||
"MailboxAliasCreated",
|
||||
"MailboxAliasDeleted",
|
||||
|
||||
"ExtraDomainForm",
|
||||
"FillDomainInstances",
|
||||
|
||||
"ExtraAccountForm",
|
||||
"CheckExtraAccountForm",
|
||||
"FillAccountInstances",
|
||||
|
||||
"ExtraDomainImportHelp",
|
||||
"ImportObject"
|
||||
]
|
||||
|
||||
PERMISSIONS = {
|
||||
"DomainAdmins": [
|
||||
["admin", "domain", "view_domain"],
|
||||
["admin", "mailbox", "add_mailbox"],
|
||||
["admin", "mailbox", "change_mailbox"],
|
||||
["admin", "mailbox", "delete_mailbox"],
|
||||
["admin", "alias", "add_alias"],
|
||||
["admin", "alias", "change_alias"],
|
||||
["admin", "alias", "delete_alias"],
|
||||
["admin", "mailbox", "view_mailboxes"],
|
||||
["admin", "alias", "view_aliases"],
|
||||
["admin", "domainalias", "view_domaliases"],
|
||||
["admin", "domainalias", "add_domainalias"],
|
||||
["admin", "domainalias", "change_domainalias"],
|
||||
["admin", "domainalias", "delete_domainalias"]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class AdminConsole(ModoExtension):
|
||||
name = "admin"
|
||||
label = ugettext_lazy("Administration console")
|
||||
version = "1.0"
|
||||
description = ugettext_lazy(
|
||||
"Web based console to manage domains, accounts and aliases"
|
||||
)
|
||||
always_active = True
|
||||
|
||||
def load(self):
|
||||
from modoboa.extensions.admin.app_settings import AdminParametersForm
|
||||
parameters.register(
|
||||
AdminParametersForm, ugettext_lazy("Administration"))
|
||||
events.declare(ADMIN_EVENTS)
|
||||
|
||||
exts_pool.register_extension(AdminConsole)
|
||||
|
||||
|
||||
@events.observe("GetExtraRolePermissions")
|
||||
def extra_permissions(rolename):
|
||||
"""Return extra permissions for :kw:`rolename`."""
|
||||
return PERMISSIONS.get(rolename, [])
|
||||
|
||||
|
||||
@events.observe("ExtraUprefsRoutes")
|
||||
def extra_routes():
|
||||
from django.conf.urls import url
|
||||
return [
|
||||
url(r'^user/forward/',
|
||||
'modoboa.extensions.admin.views.user.forward',
|
||||
name="user_forward"),
|
||||
]
|
||||
|
||||
|
||||
@events.observe("AdminMenuDisplay")
|
||||
def admin_menu(target, user):
|
||||
if target != "top_menu":
|
||||
return []
|
||||
entries = []
|
||||
if user.has_perm("admin.view_domains"):
|
||||
entries += [
|
||||
{"name": "domains",
|
||||
"url": reverse("admin:domain_list"),
|
||||
"label": _("Domains")}
|
||||
]
|
||||
if user.has_perm("core.add_user") or user.has_perm("admin.add_alias"):
|
||||
entries += [
|
||||
{"name": "identities",
|
||||
"url": reverse("admin:identity_list"),
|
||||
"label": _("Identities")},
|
||||
]
|
||||
return entries
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def user_menu(target, user):
|
||||
if target != "uprefs_menu":
|
||||
return []
|
||||
if not user.mailbox_set.count():
|
||||
return []
|
||||
return [
|
||||
{"name": "forward",
|
||||
"class": "ajaxnav",
|
||||
"url": "forward/",
|
||||
"label": ugettext_lazy("Forward")}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("RoleChanged")
|
||||
def grant_access_to_all_objects(user, role):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from modoboa.lib.permissions import grant_access_to_objects
|
||||
from modoboa.core.models import User
|
||||
|
||||
if role != "SuperAdmins":
|
||||
return
|
||||
grant_access_to_objects(
|
||||
user, User.objects.all(),
|
||||
ContentType.objects.get_for_model(User)
|
||||
)
|
||||
grant_access_to_objects(
|
||||
user, Domain.objects.all(),
|
||||
ContentType.objects.get_for_model(Domain)
|
||||
)
|
||||
grant_access_to_objects(
|
||||
user, DomainAlias.objects.all(),
|
||||
ContentType.objects.get_for_model(DomainAlias)
|
||||
)
|
||||
grant_access_to_objects(
|
||||
user, Mailbox.objects.all(),
|
||||
ContentType.objects.get_for_model(Mailbox)
|
||||
)
|
||||
grant_access_to_objects(
|
||||
user, Alias.objects.all(),
|
||||
ContentType.objects.get_for_model(Alias)
|
||||
)
|
||||
|
||||
|
||||
@events.observe("AccountExported")
|
||||
def export_admin_domains(admin):
|
||||
result = [admin.mailbox_set.all()[0].quota] \
|
||||
if admin.mailbox_set.count() else ['']
|
||||
if admin.group != "DomainAdmins":
|
||||
return result
|
||||
return result + [dom.name for dom in Domain.objects.get_for_admin(admin)]
|
||||
|
||||
|
||||
@events.observe("AccountImported")
|
||||
def import_account_mailbox(user, account, row):
|
||||
"""Handle extra fields when an account is imported.
|
||||
|
||||
Expected fields:
|
||||
|
||||
email address; quota; [domain; ...]
|
||||
|
||||
:param User user: user importing the account
|
||||
:param User account: account being imported
|
||||
:param list rom: list of fields (strings)
|
||||
"""
|
||||
account.email = row[0].strip()
|
||||
if account.email:
|
||||
account.save()
|
||||
mailbox, domname = split_mailbox(account.email)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise BadRequest(
|
||||
_("Account import failed (%s): domain does not exist"
|
||||
% account.username)
|
||||
)
|
||||
if not user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
try:
|
||||
mb = Mailbox.objects.get(address=mailbox, domain=domain)
|
||||
except Mailbox.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise Conflict(_("Mailbox %s already exists" % account.email))
|
||||
if len(row) == 1:
|
||||
quota = None
|
||||
else:
|
||||
try:
|
||||
quota = int(row[1].strip())
|
||||
except ValueError:
|
||||
raise BadRequest(
|
||||
_("Account import failed (%s): wrong quota value"
|
||||
% account.username)
|
||||
)
|
||||
use_domain_quota = True if not quota else False
|
||||
mb = Mailbox(address=mailbox, domain=domain,
|
||||
user=account, use_domain_quota=use_domain_quota)
|
||||
mb.set_quota(quota, override_rules=user.has_perm("admin.change_domain"))
|
||||
mb.save(creator=user)
|
||||
if account.group == "DomainAdmins":
|
||||
for domname in row[2:]:
|
||||
try:
|
||||
dom = Domain.objects.get(name=domname.strip())
|
||||
except Domain.DoesNotExist:
|
||||
continue
|
||||
dom.add_admin(account)
|
||||
|
||||
|
||||
@events.observe("AccountAutoCreated")
|
||||
def account_auto_created(user):
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.lib import check_if_domain_exists
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
|
||||
if parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX") == "no":
|
||||
return
|
||||
localpart, domname = split_mailbox(user.username)
|
||||
if user.group != 'SimpleUsers' and domname is None:
|
||||
return
|
||||
sadmins = User.objects.filter(is_superuser=True)
|
||||
try:
|
||||
domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
label = check_if_domain_exists(
|
||||
domname, [(DomainAlias, _('domain alias'))])
|
||||
if label is not None:
|
||||
return
|
||||
domain = Domain(name=domname, enabled=True, quota=0)
|
||||
domain.save(creator=sadmins[0])
|
||||
for su in sadmins[1:]:
|
||||
grant_access_to_object(su, domain)
|
||||
try:
|
||||
mb = Mailbox.objects.get(domain=domain, address=localpart)
|
||||
except Mailbox.DoesNotExist:
|
||||
mb = Mailbox(
|
||||
address=localpart, domain=domain, user=user, use_domain_quota=True
|
||||
)
|
||||
mb.set_quota(override_rules=True)
|
||||
mb.save(creator=sadmins[0])
|
||||
for su in sadmins[1:]:
|
||||
grant_access_to_object(su, mb)
|
||||
|
||||
|
||||
@events.observe("UserLogin")
|
||||
def user_logged_in(request, username, password):
|
||||
from modoboa.lib.cryptutils import encrypt
|
||||
|
||||
if request.user.mailbox_set.count():
|
||||
request.session["password"] = encrypt(password)
|
||||
|
||||
|
||||
@events.observe("AccountDeleted")
|
||||
def account_deleted(account, byuser, **kwargs):
|
||||
"""'AccountDeleted' listener.
|
||||
|
||||
When an account is deleted, we also need to remove its mailbox (if
|
||||
any).
|
||||
"""
|
||||
if not account.mailbox_set.count():
|
||||
return
|
||||
mb = account.mailbox_set.all()[0]
|
||||
if not byuser.can_access(mb):
|
||||
raise PermDeniedException
|
||||
keep_mb_dir = kwargs.get("keep_mb_dir", True)
|
||||
mb.delete(keepdir=keep_mb_dir)
|
||||
@@ -1,33 +0,0 @@
|
||||
#searchform {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
span.title {
|
||||
margin-left: 40px;
|
||||
margin-bottom: 10px;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
table th#actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table td[name="actions"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table .progress {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
table tbody tr:hover {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 85%;
|
||||
-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
#settings_content .form-horizontal .control-label {
|
||||
margin-left: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#settings_content > .form-horizontal .controls {
|
||||
margin-left: 240px;
|
||||
}
|
||||
@@ -1,556 +0,0 @@
|
||||
/**
|
||||
* Creates an instance of Admin.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} options - instance options
|
||||
* @classdesc This class contains utility methods used by the admin
|
||||
* application.
|
||||
*/
|
||||
var Admin = function(options) {
|
||||
Listing.call(this, options);
|
||||
};
|
||||
|
||||
Admin.prototype = {
|
||||
defaults: {
|
||||
deflocation: "list/",
|
||||
squery: null
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
this.options.defcallback = $.proxy(this.list_cb, this);
|
||||
Listing.prototype.initialize.call(this, this.options);
|
||||
this.listen();
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback used when the initial content of the listing is
|
||||
* received.
|
||||
*
|
||||
* @this Admin
|
||||
* @param {Object} data - response of the ajax call (JSON)
|
||||
*/
|
||||
list_cb: function(data) {
|
||||
if (data.rows) {
|
||||
$("#objects_table tbody").html(data.rows);
|
||||
} else {
|
||||
$("#objects_table tbody").html("");
|
||||
}
|
||||
this.update_listing(data);
|
||||
if (data.length === 0) {
|
||||
this.get_load_page_args();
|
||||
this.end_of_list_reached();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Children must override this method.
|
||||
*/
|
||||
listen: function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a new page using an AJAX request.
|
||||
*
|
||||
* @this Admin
|
||||
* @param {Object} e - event object
|
||||
*/
|
||||
page_loader: function(e) {
|
||||
var $link = get_target(e);
|
||||
e.preventDefault();
|
||||
if ($link.hasClass("navigation")) {
|
||||
$(".sidebar li.active").removeClass("active");
|
||||
$link.parent().addClass("active");
|
||||
}
|
||||
this.navobj.baseurl($link.attr("href")).update();
|
||||
},
|
||||
|
||||
importform_cb: function() {
|
||||
$(".submit").one('click', function(e) {
|
||||
e.preventDefault();
|
||||
if ($("#id_sourcefile").val() === "") {
|
||||
return;
|
||||
}
|
||||
$("#import_status").css("display", "block");
|
||||
$("#import_result").html("").removeClass("alert alert-danger");
|
||||
$("#importform").submit();
|
||||
});
|
||||
},
|
||||
|
||||
importdone: function(status, msg) {
|
||||
$("#import_status").css("display", "none");
|
||||
if (status == "ok") {
|
||||
$("#modalbox").modal('hide');
|
||||
this.reload_listing(msg);
|
||||
} else {
|
||||
$("#import_result").addClass("alert alert-danger");
|
||||
$("#import_result").html(msg);
|
||||
this.importform_cb();
|
||||
}
|
||||
},
|
||||
|
||||
exportform_cb: function() {
|
||||
$(".submit").one('click', function(e) {
|
||||
e.preventDefault();
|
||||
$("#exportform").submit();
|
||||
$("#modalbox").modal('hide');
|
||||
});
|
||||
},
|
||||
|
||||
reload_listing: function(data) {
|
||||
this.navobj.update(true);
|
||||
if (data) {
|
||||
$("body").notify("success", data, 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Admin.prototype = $.extend({}, Listing.prototype, Admin.prototype);
|
||||
|
||||
/**
|
||||
* Creates an instance of Domains.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} options - instance options
|
||||
* @classdesc This class extends the Admin one by adding methods
|
||||
* specific to domains management.
|
||||
*/
|
||||
var Domains = function(options) {
|
||||
Admin.call(this, options);
|
||||
};
|
||||
|
||||
Domains.prototype = {
|
||||
initialize: function(options) {
|
||||
Admin.prototype.initialize.call(this, options);
|
||||
this.options.navigation_params.push("domfilter", "srvfilter");
|
||||
this.options.eor_message = gettext("No more domain to show");
|
||||
this.register_tag_handler("dom");
|
||||
},
|
||||
|
||||
/**
|
||||
* Custom callbacks declaration.
|
||||
*/
|
||||
listen: function() {
|
||||
Admin.prototype.listen.call(this);
|
||||
$(document).on("click", "a.ajaxnav", $.proxy(this.page_loader, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the domain links embedded within a page.
|
||||
*
|
||||
* @param {Object} data - options
|
||||
*/
|
||||
init_domain_links: function(data) {
|
||||
var deloptions = (data.handle_mailboxes) ?
|
||||
{keepdir: gettext("Do not delete domain directory")} : {};
|
||||
var warnmsg = (data.auto_account_removal && data.auto_account_removal == "yes")
|
||||
? gettext("This operation will remove ALL data associated to this domain.")
|
||||
: gettext("This operation will remove all data associated to this domain, excepting accounts.");
|
||||
|
||||
$("a[name=deldomain]").confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
method: "POST",
|
||||
warning: warnmsg,
|
||||
checkboxes: deloptions,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
$(document).trigger('domain_listing_refresh');
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigation callback: default.
|
||||
*
|
||||
* @param {Object} data - response of the ajax call (JSON)
|
||||
*/
|
||||
list_cb: function(data) {
|
||||
Admin.prototype.list_cb.call(this, data);
|
||||
this.init_domain_links(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* A new page has been received, inject it.
|
||||
*
|
||||
* @param {Object} data - page content
|
||||
* @param {string} direction -
|
||||
*/
|
||||
add_new_page: function(data, direction) {
|
||||
Admin.prototype.add_new_page.call(this, data, direction);
|
||||
this.init_domain_links(data);
|
||||
},
|
||||
|
||||
change_inputs_state: function(value) {
|
||||
$("#id_dom_admin_username").attr("disabled", value);
|
||||
$("input[name=create_aliases]").attr("disabled", value);
|
||||
},
|
||||
|
||||
create_dom_admin_changed: function(e) {
|
||||
var $target = $(e.target);
|
||||
this.change_inputs_state(($target.val() == "yes") ? false : true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the main form contained in the domain edition modal.
|
||||
*
|
||||
* @this Domains
|
||||
*/
|
||||
generalform_init: function() {
|
||||
$('input:text:visible:first').focus();
|
||||
$("#original_aliases").dynamic_input({
|
||||
input_added: function($row) {
|
||||
$row.find("label").html("");
|
||||
},
|
||||
input_removed: function($input) {
|
||||
$input.parents(".form-group").remove();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
optionsform_init: function() {
|
||||
$("input[name=create_dom_admin]").click($.proxy(this.create_dom_admin_changed, this));
|
||||
this.change_inputs_state(
|
||||
$("input[name=create_dom_admin]:checked").val() == "yes" ? false : true
|
||||
);
|
||||
this.optionsform_prefill();
|
||||
},
|
||||
|
||||
optionsform_prefill: function() {
|
||||
var $span = $("#id_dom_admin_username").next("span");
|
||||
$span.html("@" + $("#id_name").val());
|
||||
},
|
||||
|
||||
domadminsform_init: function() {
|
||||
$("a[name=removeperm]").click(function(e) {
|
||||
var $tr = $(this).parents('tr');
|
||||
simple_ajax_request.apply(this, [e, {
|
||||
ok_cb: function(resp) {
|
||||
$tr.remove();
|
||||
if (!$("#domadmins").find("tr").length) {
|
||||
$("#admins").html('<div class="alert alert-info">'
|
||||
+ gettext("No domain administrator defined") + "</div>");
|
||||
}
|
||||
}
|
||||
}]);
|
||||
});
|
||||
},
|
||||
|
||||
newdomain_cb: function() {
|
||||
this.generalform_init();
|
||||
this.optionsform_init();
|
||||
$("#wizard").cwizard({
|
||||
formid: "domform",
|
||||
transition_callbacks: {
|
||||
1: this.optionsform_prefill
|
||||
},
|
||||
error_callbacks: {
|
||||
2: $.proxy(this.optionsform_init, this)
|
||||
},
|
||||
success_callback: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
},
|
||||
|
||||
domainform_cb: function() {
|
||||
this.generalform_init();
|
||||
this.domadminsform_init();
|
||||
$(".submit").one('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "domform",
|
||||
error_cb: $.proxy(this.domainform_cb, this),
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
}, this));
|
||||
$(document).trigger('domform_init');
|
||||
}
|
||||
};
|
||||
|
||||
Domains.prototype = $.extend({}, Admin.prototype, Domains.prototype);
|
||||
|
||||
/**
|
||||
* Creates an instance of Identities.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} options - instance options
|
||||
* @classdesc This class extends the Admin one by adding methods
|
||||
* specific to identities management.
|
||||
*/
|
||||
var Identities = function(options) {
|
||||
Admin.call(this, options);
|
||||
};
|
||||
|
||||
Identities.prototype = {
|
||||
initialize: function(options) {
|
||||
Admin.prototype.initialize.call(this, options);
|
||||
this.options.navigation_params.push("idtfilter", "grpfilter");
|
||||
this.options.eor_message = gettext("No more identity to show");
|
||||
this.domain_list = [];
|
||||
this.register_tag_handler("idt");
|
||||
this.register_tag_handler("grp", this.grp_tag_handler);
|
||||
},
|
||||
|
||||
listen: function() {
|
||||
Admin.prototype.listen.call(this);
|
||||
$(document).on("click", "a.ajaxnav", $.proxy(this.page_loader, this));
|
||||
$(document).on("click", "a.ajaxcall", $.proxy(this.send_call, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an ajax call.
|
||||
*/
|
||||
send_call: function(evt) {
|
||||
var $link = get_target(evt, "a");
|
||||
var $this = this;
|
||||
var method = $link.attr("data-call_method");
|
||||
|
||||
if (method === undefined) {
|
||||
method = "GET";
|
||||
}
|
||||
evt.preventDefault();
|
||||
$.ajax({
|
||||
url: $link.attr("href"),
|
||||
type: method,
|
||||
dataType: "json"
|
||||
}).done(function(data) {
|
||||
$this.reload_listing(data.respmsg);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the links embedded within a page.
|
||||
*
|
||||
* @param {Object} data - options
|
||||
*/
|
||||
init_identity_links: function(data) {
|
||||
var deloptions = {};
|
||||
|
||||
if (data.handle_mailboxes == "yes") {
|
||||
deloptions = {keepdir: gettext("Do not delete mailbox directory")};
|
||||
}
|
||||
|
||||
$("a[name=delaccount]").confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
method: "POST",
|
||||
checkboxes: deloptions,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
$("a[name=delalias]").confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
method: "DELETE",
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a type to let the server know what kind of object we expect.
|
||||
*
|
||||
* @this Listing
|
||||
*/
|
||||
get_load_page_args: function() {
|
||||
var args = Admin.prototype.get_load_page_args.call(this);
|
||||
|
||||
if (this.navobj.getbaseurl() == "list") {
|
||||
args.objtype = "identity";
|
||||
this.options.eor_message = gettext("No more identity to show");
|
||||
} else {
|
||||
args.objtype = "quota";
|
||||
this.options.eor_message = gettext("No more quota to show");
|
||||
}
|
||||
return args;
|
||||
},
|
||||
|
||||
/**
|
||||
* A new page has been received, inject it.
|
||||
*
|
||||
* @param {Object} data - page content
|
||||
* @param {string} direction -
|
||||
*/
|
||||
add_new_page: function(data, direction) {
|
||||
Admin.prototype.add_new_page.call(this, data, direction);
|
||||
this.init_identity_links(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigation callback: default.
|
||||
*
|
||||
* @param {Object} data - response of the ajax call (JSON)
|
||||
*/
|
||||
list_cb: function(data) {
|
||||
if ($(".sidebar li.active").length === 0) {
|
||||
var menu = this.navobj.getbaseurl();
|
||||
if (menu == "list") {
|
||||
menu = "identities";
|
||||
}
|
||||
$("a[name={0}]".format(menu)).parent().addClass("active");
|
||||
}
|
||||
$("#objects_table thead tr").html(data.headers);
|
||||
Admin.prototype.list_cb.call(this, data);
|
||||
this.init_identity_links(data);
|
||||
},
|
||||
|
||||
grp_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") === undefined &&
|
||||
$link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj
|
||||
.setparam("idtfilter", "account")
|
||||
.setparam(tag + "filter", text)
|
||||
.update();
|
||||
if ($("a[name=idt]").length === 0) {
|
||||
$("#taglist").append(this.make_tag("account", "idt"));
|
||||
}
|
||||
$("#taglist").append(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a list of domain from the server.
|
||||
*
|
||||
* @this Identities
|
||||
* @return {Array} a list of domain names
|
||||
*/
|
||||
get_domain_list: function() {
|
||||
if (!this.domain_list.length) {
|
||||
$.ajax({
|
||||
url: this.options.domain_list_url,
|
||||
dataType: "json",
|
||||
async: false
|
||||
}).done($.proxy(function(data) {
|
||||
this.domain_list = data;
|
||||
}, this));
|
||||
}
|
||||
return this.domain_list;
|
||||
},
|
||||
|
||||
simpleuser_mode: function() {
|
||||
$("#id_username").autocompleter({
|
||||
from_character: "@",
|
||||
choices: $.proxy(this.get_domain_list, this)
|
||||
});
|
||||
$("#id_email").addClass("disabled")
|
||||
.attr("readonly", "")
|
||||
.autocompleter("unbind");
|
||||
},
|
||||
|
||||
normal_mode: function() {
|
||||
$("#id_email").removeClass("disabled")
|
||||
.attr("readonly", null)
|
||||
.autocompleter("listen");
|
||||
},
|
||||
|
||||
generalform_init: function(notrigger) {
|
||||
$("#id_role").change($.proxy(function(e) {
|
||||
var $this = $(e.target);
|
||||
var value = $this.val();
|
||||
|
||||
if (value == "SimpleUsers" || value === "") {
|
||||
this.simpleuser_mode();
|
||||
} else {
|
||||
this.normal_mode();
|
||||
}
|
||||
}, this));
|
||||
if (notrigger !== undefined && notrigger) {
|
||||
return;
|
||||
}
|
||||
$("#id_role").trigger("change");
|
||||
},
|
||||
|
||||
mailform_init: function() {
|
||||
$("#id_aliases").autocompleter({
|
||||
from_character: "@",
|
||||
choices: $.proxy(this.get_domain_list, this)
|
||||
});
|
||||
$("#original_aliases").dynamic_input({
|
||||
input_added: function($row) {
|
||||
$row.find("label").html("");
|
||||
},
|
||||
input_removed: function($input) {
|
||||
$input.parents(".form-group").remove();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$("#id_email").autocompleter({
|
||||
from_character: "@",
|
||||
choices: $.proxy(this.get_domain_list, this)
|
||||
});
|
||||
if ($("#id_role").length) {
|
||||
$("#id_role").trigger("change");
|
||||
} else {
|
||||
this.simpleuser_mode();
|
||||
}
|
||||
$("#id_domains").autocompleter({
|
||||
choices: $.proxy(this.get_domain_list, this)
|
||||
});
|
||||
$("#original_domains").dynamic_input({
|
||||
input_added: function($row) {
|
||||
$row.find("label").html("");
|
||||
},
|
||||
input_removed: function($input) {
|
||||
$input.parents(".form-group").remove();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
activate_widget.call($("#id_quota_act"));
|
||||
},
|
||||
|
||||
accountform_init: function() {
|
||||
this.generalform_init(true);
|
||||
this.mailform_init();
|
||||
},
|
||||
|
||||
mailform_prefill: function() {
|
||||
var $role = $("#id_role");
|
||||
if (!$role.length || $role.val() === "" || $role.val() == "SimpleUsers") {
|
||||
$("#id_email").val($("#id_username").val());
|
||||
}
|
||||
},
|
||||
|
||||
newaccount_cb: function() {
|
||||
this.accountform_init();
|
||||
$("#wizard").cwizard({
|
||||
formid: "newaccount_form",
|
||||
transition_callbacks: {
|
||||
1: this.mailform_prefill
|
||||
},
|
||||
success_callback: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
},
|
||||
|
||||
editaccount_cb: function() {
|
||||
this.accountform_init();
|
||||
$('.submit').on('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "accountform",
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
}, this));
|
||||
},
|
||||
|
||||
aliasform_cb: function() {
|
||||
$("#id_email").autocompleter({
|
||||
from_character: "@",
|
||||
choices: $.proxy(this.get_domain_list, this)
|
||||
});
|
||||
$("#original_recipients").dynamic_input({
|
||||
input_added: function($row) {
|
||||
$row.find("label").html("");
|
||||
},
|
||||
input_removed: function($input) {
|
||||
$input.parents(".form-group").remove();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$(".submit").on('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: "aliasform",
|
||||
reload_on_success: false,
|
||||
success_cb: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
}, this));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Identities.prototype = $.extend({}, Admin.prototype, Identities.prototype);
|
||||
@@ -1,15 +0,0 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import tables
|
||||
|
||||
|
||||
class ExtensionsTable(tables.Table):
|
||||
idkey = "id"
|
||||
selection = tables.SelectionColumn("selection", width="4%", header=False)
|
||||
label = tables.Column("label", label=ugettext_lazy("Name"), width="15%")
|
||||
version = tables.Column(
|
||||
"version", label=ugettext_lazy("Version"), width="6%"
|
||||
)
|
||||
descr = tables.Column("description", label=ugettext_lazy("Description"))
|
||||
|
||||
cols_order = ["selection", "label", "version", "descr"]
|
||||
@@ -1,12 +0,0 @@
|
||||
{% load form_tags %}
|
||||
{% render_field form.role %}
|
||||
{% render_field form.username %}
|
||||
{% render_field form.first_name %}
|
||||
{% render_field form.last_name %}
|
||||
{% render_field form.password1 %}
|
||||
{% render_field form.password2 %}
|
||||
{% if form.domain_is_disabled %}
|
||||
{% include "common/generic_field.html" with field=form.is_active label_width="col-sm-4" inline_message=_("Domain is disabled") %}
|
||||
{% else %}
|
||||
{% render_field form.is_active %}
|
||||
{% endif %}
|
||||
@@ -1,10 +0,0 @@
|
||||
{% extends "common/generic_modal_form.html" %}{% load form_tags %}
|
||||
{% block formbody %}
|
||||
{% render_field form.email %}
|
||||
{% render_fields_group form "recipients" %}
|
||||
{% if not form.instance.pk or form.instance.domain.enabled %}
|
||||
{% render_field form.enabled %}
|
||||
{% else %}
|
||||
{% include "common/generic_field.html" with field=form.enabled label_width="col-sm-4" inline_message=_("Domain is disabled") extra_classes="info" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load i18n form_tags %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.quota appended_text=_("MB") %}
|
||||
{% render_fields_group form "aliases" %}
|
||||
{% render_field form.enabled %}
|
||||
@@ -1,8 +0,0 @@
|
||||
{% load i18n form_tags %}
|
||||
{% if form.fields %}
|
||||
{% render_field form.create_dom_admin %}
|
||||
{% render_field form.dom_admin_username appended_text="@" %}
|
||||
{% render_field form.create_aliases %}
|
||||
{% else %}
|
||||
<div class="alert alert-warning">{% trans "You can't create a new domain administrator" %}</div>
|
||||
{% endif %}
|
||||
@@ -1,63 +0,0 @@
|
||||
{% extends "admin/header.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load admin_tags %}
|
||||
|
||||
{% block leftcol %}
|
||||
<ul class="nav nav-sidebar">
|
||||
{% domains_menu "statistics" user %}
|
||||
</ul>
|
||||
|
||||
{% get_extra_admin_content user "leftcol" selection %}
|
||||
{% endblock %}
|
||||
|
||||
{% block apparea %}
|
||||
<h2>{% trans "Statistics" %} <small>{% trans "Domains and identities" %}</small></h2>
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
{% if user.group == "SuperAdmins" %}
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Global counters" %}</h3></div>
|
||||
<div class="panel-body"></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domains" %}</td><td>{{ domains_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domain aliases" %}</td><td>{{ domain_aliases_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Identities" %}</td><td>{{ identities_counter }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Per-domain counters" %}</h3></div>
|
||||
<div class="panel-body"></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Domain" %}</th>
|
||||
<th>{% trans "Identities" %}</th>
|
||||
</tr>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td>{{ domain.name }}</td><td>{{ domain.identities_count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,29 +0,0 @@
|
||||
{% extends "admin/listing.html" %}{% load i18n admin_tags lib_tags %}
|
||||
|
||||
{% block pagetitle %}{% trans "Domains" %}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
{% extra_static_content "domains" "css" user %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
admin = new Domains({
|
||||
load_page_url: "{% url 'admin:_domain_list' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% extra_static_content "domains" "js" user %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}{% domains_menu "domains" user %}{% endblock %}
|
||||
|
||||
{% block table_header %}
|
||||
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
|
||||
<th id="aliases" width="">{% trans "Alias(es)" %}</th>
|
||||
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
{% endblock %}
|
||||
@@ -1,9 +0,0 @@
|
||||
{% load i18n admin_tags lib_tags %}
|
||||
{% for domain in domains %}
|
||||
<tr id="{{ domain.id }}" class="{% if not domain.enabled %}text-muted{% endif %}" name="{{ forloop.counter }}">
|
||||
<td name="name">{% domain_modify_link domain %}{% if not domain.enabled %} ({% trans "disabled" %}){% endif %}</td>
|
||||
<td name="aliases">{% domain_aliases domain %} </td>
|
||||
<td name="tags">{% render_tags domain.tags %}</td>
|
||||
<td name="actions">{% domain_actions user domain %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1,28 +0,0 @@
|
||||
{% extends "common/tabforms.html" %}{% load i18n lib_tags admin_tags %}
|
||||
{% block extratabs %}
|
||||
<li class="{% if tabs.active_id == 'admins' %}active{% endif %}">
|
||||
<a href="#admins" data-toggle="tab">{% trans "Administrators" %}</a>
|
||||
</li>
|
||||
<li class="{% if tabs.active_id == 'misc' %}active{% endif %}">
|
||||
<a href="#misc" data-toggle="tab">{% trans "Miscellaneous" %}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block extratabs_content %}
|
||||
<div class="tab-pane{% if tabs.active_id == 'admins' %} active{% endif %}" id="admins">{% if domadmins %}
|
||||
<table id="domadmins" class="table table-condensed">
|
||||
<tr><th>{% trans "Username" %}</th><th>{% trans "Name" %}</th><th></th></tr>
|
||||
{% for da in domadmins %}<tr>
|
||||
<td>{{ da.username }}</td>
|
||||
<td>{{ da.fullname }}</td>
|
||||
<td name="actions">{% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %}</td>
|
||||
</tr>{% endfor %}
|
||||
</table>{% else %}<div class="alert alert-info">{% trans "No domain administrator defined" %}</div>{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane{% if tabs.active_id == 'misc' %} active{% endif %}" id="misc">
|
||||
<div class="row"><span class="title">{% trans "Creation date" %}</span><span>{{ domain.creation }}</span></div>
|
||||
<div class="row"><span class="title">{% trans "Last modification date" %}</span><span>{{ domain.last_modification }}</span></div>
|
||||
<div class="row"><span class="title">{% trans "Domain aliases" %}</span><span>{{ domain.domainalias_count }}</span></div>
|
||||
<div class="row"><span class="title">{% trans "Mailboxes" %}</span><span>{{ domain.mailbox_count }}</span></div>
|
||||
<div class="row"><span class="title">{% trans "Mailbox aliases" %}</span><span>{{ domain.mbalias_count }}</span></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,11 +0,0 @@
|
||||
{% load i18n form_tags %}{% load url from future %}
|
||||
<h2>{% trans "Forward" %} <small>{% trans "Automatically forward emails to another address" %}</small></h2>
|
||||
<hr>
|
||||
<form class="form-horizontal" method="POST" action="{% url 'user_forward' %}"
|
||||
id="uprefs_form">
|
||||
{% csrf_token %}
|
||||
{% render_form form %}
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary col-lg-offset-8 col-md-offset-8 col-sm-offset-7 col-lg-1 col-md-1 col-sm-2" id="update" name="update">{% trans 'Update' %}</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,12 +0,0 @@
|
||||
{% extends "twocols.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/admin.css' %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript" src="{% static 'js/history.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/listing.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'admin/js/admin.js' %}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends "admin/listing.html" %}
|
||||
{% load admin_tags lib_tags i18n %}{% load url from future %}
|
||||
{% load static %}
|
||||
|
||||
{% block pagetitle %}{% trans "Identities" %}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
{% extra_static_content "identities" "css" user %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="{% static 'js/autocompleter.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
admin = new Identities({
|
||||
domain_list_url: "{% url 'admin:domain_flat_list' %}",
|
||||
load_page_url: "{% url 'admin:identity_page' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% extra_static_content "identities" "js" user %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}{% identities_menu user "identities" %}{% endblock %}
|
||||
@@ -1,10 +0,0 @@
|
||||
{% load i18n admin_tags lib_tags %}
|
||||
{% for ident in identities %}
|
||||
{% check_identity_status ident as identity_is_enabled %}
|
||||
<tr id="{{ ident.id }}" class="{% if not identity_is_enabled %}text-muted{% endif %}" name="{{ forloop.counter }}">
|
||||
<td name="identity">{% identity_modify_link ident %}{% if not identity_is_enabled %} (disabled){% endif %}</td>
|
||||
<td name="name_or_rcpt">{{ ident.name_or_rcpt }}</td>
|
||||
<td name="tags">{% render_tags ident.tags %}</td>
|
||||
<td name="actions">{% identity_actions user ident %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load i18n %}
|
||||
<th id="identity" class="sortable" data-sort_order="identity" width="">{% trans "Name" %}</th>
|
||||
<th id="name_or_rcpt" class="sortable" data-sort_order="name_or_rcpt" width="">{% trans "Fullname/Recipient" %}</th>
|
||||
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head profile="http://www.w3.org/2005/10/profile">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<script type="text/javascript">
|
||||
parent.admin.importdone("{{ status }}", "{{ msg|escapejs }}");
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
{% extends "common/generic_modal_form.html" %}
|
||||
{% load form_tags i18n %}
|
||||
{% load static %}
|
||||
{% block formbody %}
|
||||
<div class="alert alert-info">
|
||||
<h4>{% trans "Help" %}</h4>
|
||||
{{ helptext|safe }}
|
||||
</div>
|
||||
{% render_form form %}
|
||||
<iframe id="import_target" name="import_target" src="#"
|
||||
style="width:0;height:0;border:0px solid #fff;"></iframe>
|
||||
<div id="import_status" style="text-align: center;display:none">
|
||||
<img src="{% static 'css/spinner.gif' %}" /> {% trans "Importing..." %}
|
||||
</div>
|
||||
<div id="import_result"></div>
|
||||
{% endblock %}
|
||||
@@ -1,47 +0,0 @@
|
||||
{% extends "admin/header.html" %}{% load i18n admin_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/jquery.sortable.css' %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="{% static 'js/creation_wizard.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/dynamic_input.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery.sortable.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery.infinitescroll.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
var admin;
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block leftcol %}
|
||||
<ul class="nav nav-sidebar">
|
||||
{% block actions %}{% endblock %}
|
||||
</ul>
|
||||
|
||||
{% get_extra_admin_content user "leftcol" selection %}
|
||||
{% endblock %}
|
||||
|
||||
{% block apparea %}
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{% include "admin/searchform.html" %}
|
||||
</div>
|
||||
<div id="taglist" class="col-sm-5"> </div>
|
||||
</div>
|
||||
|
||||
<div id="listing">
|
||||
<table id="objects_table" class="table {{ styles }}">
|
||||
<thead>
|
||||
<tr>
|
||||
{% block table_header %}{% endblock %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,11 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% if target == "permissions" %}
|
||||
<option value="">{% trans "Choose a value" %}</option>
|
||||
{% endif %}
|
||||
{% for mb in mailboxes %}
|
||||
{% if target == "permissions" %}
|
||||
<option value='{{ mb.id }}'>{{ mb.name }}</option>
|
||||
{% else %}
|
||||
<option value='{{ mb.id }}'>{{ mb.full_address }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load form_tags %}
|
||||
{% render_field form.email %}
|
||||
{% include "common/generic_field.html" with field=form.quota help_display_mode="tooltip" label_width="col-sm-4" appended_text=_("MB") activator=True activator_value=form.quota_act.value deactivate_if_empty=False activator_label=_("Use domain default value") activator_size="col-sm-5" %}
|
||||
{% render_extra_fields form %}
|
||||
{% render_fields_group form "aliases" %}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "common/generic_modal_form.html" %}{% load form_tags %}{% block formbody %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.quota appended_text=_("MB") %}
|
||||
{% render_fields_group form "aliases" %}
|
||||
{% render_field form.enabled %}
|
||||
{% endblock %}
|
||||
@@ -1,2 +0,0 @@
|
||||
{% load form_tags %}
|
||||
{% render_fields_group form "domains" %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load i18n %}
|
||||
<th width="50%" class="sortable" data-sort_order="address">{% trans "Address" %}</th>
|
||||
<th class="sortable" data-sort_order="quota_value__bytes">{% trans "Value" %}</th>
|
||||
<th class="sortable" data-sort_order="quota">{% trans "Limit" %}</th>
|
||||
<th class="sortable" data-sort_order="quota_usage" id="quotabar">{% trans "Usage (%)" %}</th>
|
||||
@@ -1,15 +0,0 @@
|
||||
{% load lib_tags admin_tags %}
|
||||
|
||||
{% for mbox in mboxes %}
|
||||
<tr>
|
||||
<td>{% identity_modify_link mbox.user "mail" %}</td>
|
||||
<td>{{ mbox.get_quota }}M</td>
|
||||
<td>{{ mbox.quota }}M</td>
|
||||
<td name="quota">
|
||||
<div id="quotabar" rel="tooltip" title="{{ mbox.get_quota_in_percent }}%"
|
||||
class="progress">
|
||||
<div class="{% progress_color mbox.get_quota_in_percent %}" style="width: {{ mbox.get_quota_in_percent }}%"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1,11 +0,0 @@
|
||||
{% load i18n %}
|
||||
<form id="searchform" class="form-horizontal" role="form">
|
||||
<div class="form-group has-feedback">
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text"
|
||||
value={% if squery %}"{{ squery }}"{% else %}"{% trans 'Search' %}"{% endif %}
|
||||
id="searchquery" name="searchquery" />
|
||||
<span class="fa fa-search form-control-feedback"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,280 +0,0 @@
|
||||
"""
|
||||
Admin extension tags.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.templatetags.lib_tags import render_link
|
||||
from modoboa.lib.web_utils import render_actions
|
||||
|
||||
register = template.Library()
|
||||
|
||||
genders = {
|
||||
"Enabled": (ugettext_lazy("enabled_m"), ugettext_lazy("enabled_f"))
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domains_menu(selection, user):
|
||||
"""Specific menu for domain related operations.
|
||||
|
||||
Corresponds to the menu visible on the left column when you go to
|
||||
*Domains*.
|
||||
|
||||
:param str selection: menu entry currently selected
|
||||
:param ``User`` user: connected user
|
||||
:rtype: str
|
||||
:return: rendered menu (as HTML)
|
||||
"""
|
||||
domain_list_url = (
|
||||
"list/" if selection != "statistics" else reverse("admin:domain_list")
|
||||
)
|
||||
entries = [
|
||||
{"name": "domains",
|
||||
"label": _("List domains"),
|
||||
"img": "fa fa-user",
|
||||
"class": "ajaxnav navigation",
|
||||
"url": domain_list_url},
|
||||
{"name": "statistics",
|
||||
"label": _("Statistics"),
|
||||
"img": "fa fa-line-chart",
|
||||
"class": "navigation",
|
||||
"url": reverse("admin:domain_statistics")}
|
||||
]
|
||||
if user.has_perm("admin.add_domain"):
|
||||
entries += [
|
||||
{"name": "newdomain",
|
||||
"label": _("Add domain"),
|
||||
"img": "fa fa-plus",
|
||||
"modal": True,
|
||||
"modalcb": "admin.newdomain_cb",
|
||||
"url": reverse("admin:domain_add")},
|
||||
]
|
||||
entries += events.raiseQueryEvent("ExtraDomainMenuEntries", user)
|
||||
entries += [
|
||||
{"name": "import",
|
||||
"label": _("Import"),
|
||||
"img": "fa fa-folder-open",
|
||||
"url": reverse("admin:domain_import"),
|
||||
"modal": True,
|
||||
"modalcb": "admin.importform_cb"},
|
||||
{"name": "export",
|
||||
"label": _("Export"),
|
||||
"img": "fa fa-share-alt",
|
||||
"url": reverse("admin:domain_export"),
|
||||
"modal": True,
|
||||
"modalcb": "admin.exportform_cb"}
|
||||
]
|
||||
|
||||
return render_to_string('common/menulist.html', {
|
||||
"entries": entries,
|
||||
"selection": selection,
|
||||
"user": user
|
||||
})
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def identities_menu(user, selection=None):
|
||||
"""Menu specific to the Identities page.
|
||||
|
||||
:param ``User`` user: the connecter user
|
||||
:rtype: str
|
||||
:return: the rendered menu
|
||||
"""
|
||||
entries = [
|
||||
{"name": "identities",
|
||||
"label": _("List identities"),
|
||||
"img": "fa fa-user",
|
||||
"class": "ajaxnav navigation",
|
||||
"url": "list/"},
|
||||
{"name": "quotas",
|
||||
"label": _("List quotas"),
|
||||
"img": "fa fa-hdd-o",
|
||||
"class": "ajaxnav navigation",
|
||||
"url": "quotas/"},
|
||||
{"name": "newaccount",
|
||||
"label": _("Add account"),
|
||||
"img": "fa fa-plus",
|
||||
"modal": True,
|
||||
"modalcb": "admin.newaccount_cb",
|
||||
"url": reverse("admin:account_add")},
|
||||
{"name": "newalias",
|
||||
"label": _("Add alias"),
|
||||
"img": "fa fa-plus",
|
||||
"modal": True,
|
||||
"modalcb": "admin.aliasform_cb",
|
||||
"url": reverse("admin:alias_add")},
|
||||
{"name": "newforward",
|
||||
"label": _("Add forward"),
|
||||
"img": "fa fa-plus",
|
||||
"modal": True,
|
||||
"modalcb": "admin.aliasform_cb",
|
||||
"url": reverse("admin:forward_add")},
|
||||
{"name": "newdlist",
|
||||
"label": _("Add distribution list"),
|
||||
"img": "fa fa-plus",
|
||||
"modal": True,
|
||||
"modalcb": "admin.aliasform_cb",
|
||||
"url": reverse("admin:dlist_add")},
|
||||
{"name": "import",
|
||||
"label": _("Import"),
|
||||
"img": "fa fa-folder-open",
|
||||
"url": reverse("admin:identity_import"),
|
||||
"modal": True,
|
||||
"modalcb": "admin.importform_cb"},
|
||||
{"name": "export",
|
||||
"label": _("Export"),
|
||||
"img": "fa fa-share-alt",
|
||||
"url": reverse("admin:identity_export"),
|
||||
"modal": True,
|
||||
"modalcb": "admin.exportform_cb"
|
||||
}
|
||||
]
|
||||
|
||||
return render_to_string('common/menulist.html', {
|
||||
"entries": entries,
|
||||
"user": user
|
||||
})
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_actions(user, domain):
|
||||
actions = []
|
||||
if domain.__class__.__name__ == 'Domain':
|
||||
actions = [
|
||||
{"name": "listidentities",
|
||||
"url": "{0}#list/?searchquery=@{1}".format(
|
||||
reverse("admin:identity_list"), domain.name),
|
||||
"title": _("View the domain's identities"),
|
||||
"img": "fa fa-user"}
|
||||
]
|
||||
if user.has_perm("admin.delete_domain"):
|
||||
actions.append({
|
||||
"name": "deldomain",
|
||||
"url": reverse("admin:domain_delete", args=[domain.id]),
|
||||
"title": _("Delete %s?" % domain.name),
|
||||
"img": "fa fa-trash"
|
||||
})
|
||||
else:
|
||||
actions = events.raiseQueryEvent('GetDomainActions', user, domain)
|
||||
|
||||
return render_actions(actions)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def identity_actions(user, ident):
|
||||
name = ident.__class__.__name__
|
||||
objid = ident.id
|
||||
if name == "User":
|
||||
actions = events.raiseQueryEvent("ExtraAccountActions", ident)
|
||||
actions += [
|
||||
{"name": "delaccount",
|
||||
"url": reverse("admin:account_delete", args=[objid]),
|
||||
"img": "fa fa-trash",
|
||||
"title": _("Delete %s?" % ident.username)},
|
||||
]
|
||||
else:
|
||||
actions = [
|
||||
{"name": "delalias",
|
||||
"url": reverse("admin:alias_delete") + "?selection=%s" % objid,
|
||||
"img": "fa fa-trash",
|
||||
"title": _("Delete %s?" % ident.full_address)},
|
||||
]
|
||||
return render_actions(actions)
|
||||
|
||||
|
||||
@register.assignment_tag
|
||||
def check_identity_status(identity):
|
||||
"""Check if identity is enabled or not."""
|
||||
if identity.__class__.__name__ == "User":
|
||||
if identity.mailbox_set.count() \
|
||||
and not identity.mailbox_set.all()[0].domain.enabled:
|
||||
return False
|
||||
elif not identity.is_active:
|
||||
return False
|
||||
elif not identity.enabled or not identity.domain.enabled:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_modify_link(domain):
|
||||
linkdef = {"label": domain.name, "modal": True}
|
||||
if domain.__class__.__name__ == "Domain":
|
||||
linkdef["url"] = reverse("admin:domain_change", args=[domain.id])
|
||||
linkdef["modalcb"] = "admin.domainform_cb"
|
||||
else:
|
||||
tmp = events.raiseDictEvent('GetDomainModifyLink', domain)
|
||||
for key in ['url', 'modalcb']:
|
||||
linkdef[key] = tmp[key]
|
||||
return render_link(linkdef)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domain_aliases(domain):
|
||||
"""Display domain aliases of this domain.
|
||||
|
||||
:param domain:
|
||||
:rtype: str
|
||||
"""
|
||||
if not domain.aliases.count():
|
||||
return '---'
|
||||
res = ''
|
||||
for alias in domain.aliases.all():
|
||||
res += '%s<br/>' % alias.name
|
||||
return res
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def identity_modify_link(identity, active_tab='default'):
|
||||
"""Return the appropriate modification link.
|
||||
|
||||
According to the identity type, a specific modification link (URL)
|
||||
must be used.
|
||||
|
||||
:param identity: a ``User`` or ``Alias`` instance
|
||||
:param str active_tab: the tab to display
|
||||
:rtype: str
|
||||
"""
|
||||
linkdef = {"label": identity.identity, "modal": True}
|
||||
if identity.__class__.__name__ == "User":
|
||||
linkdef["url"] = reverse("admin:account_change", args=[identity.id])
|
||||
linkdef["url"] += "?active_tab=%s" % active_tab
|
||||
linkdef["modalcb"] = "admin.editaccount_cb"
|
||||
else:
|
||||
linkdef["url"] = reverse("admin:alias_change", args=[identity.id])
|
||||
linkdef["modalcb"] = "admin.aliasform_cb"
|
||||
return render_link(linkdef)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def domadmin_actions(daid, domid):
|
||||
actions = [{
|
||||
"name": "removeperm",
|
||||
"url": "{0}?domid={1}&daid={2}".format(
|
||||
reverse("admin:permission_remove"), domid, daid),
|
||||
"img": "fa fa-trash",
|
||||
"title": _("Remove this permission")
|
||||
}]
|
||||
return render_actions(actions)
|
||||
|
||||
|
||||
@register.filter
|
||||
def gender(value, target):
|
||||
if value in genders:
|
||||
trans = target == "m" and genders[value][0] or genders[value][1]
|
||||
if trans.find("_") == -1:
|
||||
return trans
|
||||
return value
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def get_extra_admin_content(user, target, currentpage):
|
||||
res = events.raiseQueryEvent(
|
||||
"ExtraAdminContent", user, target, currentpage
|
||||
)
|
||||
return "".join(res)
|
||||
@@ -1,14 +0,0 @@
|
||||
from .domain import DomainTestCase
|
||||
from .domain_alias import DomainAliasTestCase
|
||||
from .account import AccountTestCase, PermissionsTestCase
|
||||
from .alias import AliasTestCase
|
||||
from .import_ import ImportTestCase
|
||||
from .export import ExportTestCase
|
||||
from .password_schemes import PasswordSchemesTestCase
|
||||
from .user import ForwardTestCase
|
||||
|
||||
__all__ = [
|
||||
'DomainTestCase', 'DomainAliasTestCase', 'AccountTestCase',
|
||||
'PermissionsTestCase', 'AliasTestCase', 'ImportTestCase',
|
||||
'ExportTestCase', 'PasswordSchemesTestCase', 'ForwardTestCase'
|
||||
]
|
||||
@@ -1,197 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin import factories
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Mailbox
|
||||
)
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
|
||||
class AccountTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(AccountTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
|
||||
def test_crud(self):
|
||||
values = dict(
|
||||
username="tester@test.com", first_name="Tester", last_name="Toto",
|
||||
password1="toto", password2="toto", role="SimpleUsers",
|
||||
quota_act=True,
|
||||
is_active=True, email="tester@test.com", stepid='step2'
|
||||
)
|
||||
self.ajax_post(reverse("admin:account_add"), values)
|
||||
|
||||
account = User.objects.get(username="tester@test.com")
|
||||
mb = account.mailbox_set.all()[0]
|
||||
self.assertEqual(mb.full_address, "tester@test.com")
|
||||
self.assertEqual(mb.quota, 10)
|
||||
self.assertEqual(mb.enabled, True)
|
||||
self.assertEqual(mb.quota_value.username, "tester@test.com")
|
||||
self.assertEqual(account.username, mb.full_address)
|
||||
self.assertTrue(account.check_password("toto"))
|
||||
self.assertEqual(account.first_name, "Tester")
|
||||
self.assertEqual(account.last_name, "Toto")
|
||||
self.assertEqual(mb.domain.mailbox_count, 3)
|
||||
|
||||
values["username"] = "pouet@test.com"
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[account.id]), values
|
||||
)
|
||||
mb = Mailbox.objects.get(pk=mb.id)
|
||||
self.assertEqual(mb.full_address, "pouet@test.com")
|
||||
self.assertEqual(mb.quota_value.username, "pouet@test.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:account_delete", args=[account.id]), {}
|
||||
)
|
||||
|
||||
def _set_quota(self, email, value, expected_status=200):
|
||||
account = User.objects.get(username=email)
|
||||
values = {
|
||||
"username": email, "role": "SimpleUsers", "quota_act": False,
|
||||
"is_active": True, "quota": value, "email": email
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[account.id]),
|
||||
values, status=expected_status
|
||||
)
|
||||
|
||||
def test_set_nul_quota_as_superadmin(self):
|
||||
self._set_quota("user@test.com", 0)
|
||||
|
||||
def test_set_nul_quota_as_domainadmin(self):
|
||||
self.clt.logout()
|
||||
self.assertTrue(
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
)
|
||||
self._set_quota("user@test.com", 0, 400)
|
||||
self.clt.logout()
|
||||
self.assertTrue(
|
||||
self.clt.login(username="admin@test2.com", password="toto")
|
||||
)
|
||||
self._set_quota("user@test2.com", 0)
|
||||
|
||||
|
||||
class PermissionsTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(PermissionsTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
self.user = User.objects.get(username='user@test.com')
|
||||
self.values = dict(
|
||||
username=self.user.username, role="DomainAdmins",
|
||||
is_active=self.user.is_active, email="user@test.com",
|
||||
quota_act=True
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.clt.logout()
|
||||
|
||||
def test_domain_admins(self):
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertEqual(self.user.group, "DomainAdmins")
|
||||
|
||||
self.values["role"] = "SimpleUsers"
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertNotEqual(self.user.group, 'DomainAdmins')
|
||||
|
||||
def test_superusers(self):
|
||||
self.values["role"] = "SuperAdmins"
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertEqual(
|
||||
User.objects.get(username="user@test.com").is_superuser, True
|
||||
)
|
||||
|
||||
self.values["role"] = "SimpleUsers"
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[self.user.id]),
|
||||
self.values
|
||||
)
|
||||
self.assertEqual(User.objects.get(
|
||||
username="user@test.com").is_superuser, False)
|
||||
|
||||
def test_self_modif(self):
|
||||
self.clt.logout()
|
||||
self.assertTrue(
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
)
|
||||
admin = User.objects.get(username="admin@test.com")
|
||||
values = dict(
|
||||
username="admin@test.com", first_name="Admin",
|
||||
password1="", password2="",
|
||||
quota=10, is_active=True, email="admin@test.com"
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[admin.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(admin.group, "DomainAdmins")
|
||||
self.assertTrue(admin.can_access(Domain.objects.get(name="test.com")))
|
||||
|
||||
values["role"] = "SuperAdmins"
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[admin.id]),
|
||||
values
|
||||
)
|
||||
admin = User.objects.get(username="admin@test.com")
|
||||
self.assertEqual(admin.group, "DomainAdmins")
|
||||
|
||||
def test_domadmin_access(self):
|
||||
self.clt.logout()
|
||||
self.assertEqual(
|
||||
self.clt.login(username="admin@test.com", password="toto"),
|
||||
True)
|
||||
response = self.clt.get(reverse("admin:domain_list"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.clt.get(
|
||||
reverse("admin:account_change", args=[self.user.id]),
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertNotEqual(response["Content-Type"], "application/json")
|
||||
|
||||
def test_domainadmin_deletes_superadmin(self):
|
||||
"""Check domain admins restrictions about super admins
|
||||
|
||||
When a super admin owns a mailbox and a domain admin exists
|
||||
for the associated domain, this domain admin must not be able
|
||||
to access the super admin.
|
||||
"""
|
||||
values = dict(
|
||||
username="superadmin2@test.com", first_name="Super",
|
||||
last_name="Admin", password1="toto", password2="toto",
|
||||
role="SuperAdmins", is_active=True,
|
||||
email="superadmin2@test.com", stepid='step2'
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("admin:account_add"),
|
||||
values
|
||||
)
|
||||
account = User.objects.get(username="superadmin2@test.com")
|
||||
self.clt.logout()
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
self.ajax_post(
|
||||
reverse("admin:account_delete", args=[account.id]), {}, 403
|
||||
)
|
||||
|
||||
def test_domainadmin_dlist_local_domain_not_owned(self):
|
||||
"""Check if a domain admin can use a local mailbox he can't
|
||||
access as a recipient in a distribution list"""
|
||||
values = dict(
|
||||
email="all@test.com",
|
||||
recipients="user@test.com",
|
||||
recipients_1="user@test2.com",
|
||||
enabled=True
|
||||
)
|
||||
self.ajax_post(reverse("admin:dlist_add"), values)
|
||||
@@ -1,130 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin import factories
|
||||
from modoboa.extensions.admin.models import (
|
||||
Alias
|
||||
)
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
|
||||
class AliasTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(AliasTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
|
||||
def test_alias(self):
|
||||
user = User.objects.get(username="user@test.com")
|
||||
values = dict(
|
||||
username="user@test.com", role=user.group,
|
||||
is_active=user.is_active, email="user@test.com",
|
||||
aliases="toto@test.com", aliases_1="titi@test.com"
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[user.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
|
||||
|
||||
del values["aliases_1"]
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[user.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 1)
|
||||
|
||||
def test_upper_case_alias(self):
|
||||
"""Try to create an upper case alias."""
|
||||
user = User.objects.get(username="user@test.com")
|
||||
values = dict(
|
||||
username="user@test.com", role=user.group,
|
||||
is_active=user.is_active, email="user@test.com",
|
||||
aliases="Toto@test.com"
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("admin:account_change", args=[user.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(user.mailbox_set.first().alias_set.count(), 1)
|
||||
self.assertEqual(
|
||||
user.mailbox_set.first().alias_set.first(), "toto@test.com")
|
||||
|
||||
values = {
|
||||
"email": "Titi@test.com", "recipient": "user@test.com",
|
||||
"enabled": True
|
||||
}
|
||||
self.ajax_post(reverse("admin:alias_add"), values)
|
||||
self.assertEqual(
|
||||
user.mailbox_set.first().alias_set.first(), "titi@test.com")
|
||||
|
||||
def test_dlist(self):
|
||||
values = dict(email="all@test.com",
|
||||
recipients="user@test.com",
|
||||
recipients_1="admin@test.com",
|
||||
recipients_2="ext@titi.com",
|
||||
enabled=True)
|
||||
self.ajax_post(
|
||||
reverse("admin:dlist_add"), values
|
||||
)
|
||||
user = User.objects.get(username="user@test.com")
|
||||
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
|
||||
admin = User.objects.get(username="admin@test.com")
|
||||
self.assertEqual(admin.mailbox_set.all()[0].alias_set.count(), 1)
|
||||
|
||||
dlist = Alias.objects.get(address="all", domain__name="test.com")
|
||||
self.assertEqual(len(dlist.get_recipients()), 3)
|
||||
del values["recipients_1"]
|
||||
self.ajax_post(
|
||||
reverse("admin:alias_change", args=[dlist.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(dlist.get_recipients_count(), 2)
|
||||
|
||||
self.ajax_post(
|
||||
reverse("admin:alias_delete") + "?selection=%d" % dlist.id, {}
|
||||
)
|
||||
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
|
||||
address="all", domain__name="test.com")
|
||||
|
||||
def test_forward(self):
|
||||
values = dict(email="forward2@test.com", recipients="rcpt@dest.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:forward_add"), values
|
||||
)
|
||||
fwd = Alias.objects.get(address="forward2", domain__name="test.com")
|
||||
self.assertEqual(fwd.get_recipients_count(), 1)
|
||||
|
||||
values["recipients"] = "rcpt2@dest.com"
|
||||
self.ajax_post(
|
||||
reverse("admin:alias_change",
|
||||
args=[fwd.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(fwd.get_recipients_count(), 1)
|
||||
|
||||
self.ajax_post(
|
||||
reverse("admin:alias_delete") + "?selection=%d"
|
||||
% fwd.id, {}
|
||||
)
|
||||
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
|
||||
address="forward2", domain__name="test.com")
|
||||
|
||||
def test_forward_and_local_copies(self):
|
||||
values = dict(email="user@test.com", recipients="rcpt@dest.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:forward_add"), values
|
||||
)
|
||||
fwd = Alias.objects.get(address="user", domain__name="test.com")
|
||||
self.assertEqual(fwd.get_recipients_count(), 1)
|
||||
|
||||
values["recipients"] = "rcpt@dest.com"
|
||||
values["recipients_1"] = "user@test.com"
|
||||
self.ajax_post(
|
||||
reverse("admin:alias_change", args=[fwd.id]),
|
||||
values
|
||||
)
|
||||
fwd = Alias.objects.get(pk=fwd.pk)
|
||||
self.assertEqual(fwd.get_recipients_count(), 2)
|
||||
self.assertEqual(fwd.aliases.count(), 0)
|
||||
@@ -1,112 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Alias
|
||||
)
|
||||
from modoboa.extensions.admin import factories
|
||||
|
||||
|
||||
class DomainTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(DomainTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
|
||||
def test_create(self):
|
||||
"""Test the creation of a domain
|
||||
"""
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 100, "create_dom_admin": "no",
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.ajax_post(reverse("admin:domain_add"), values)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
self.assertEqual(dom.name, "pouet.com")
|
||||
self.assertEqual(dom.quota, 100)
|
||||
self.assertEqual(dom.enabled, False)
|
||||
self.assertFalse(dom.admins)
|
||||
|
||||
def test_create_with_template(self):
|
||||
"""Test the creation of a domain with a template
|
||||
|
||||
"""
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 100, "create_dom_admin": "yes",
|
||||
"dom_admin_username": "toto", "create_aliases": "yes",
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_add"),
|
||||
values
|
||||
)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
da = User.objects.get(username="toto@pouet.com")
|
||||
self.assertIn(da, dom.admins)
|
||||
al = Alias.objects.get(address="postmaster", domain__name="pouet.com")
|
||||
self.assertIn(da.mailbox_set.all()[0], al.mboxes.all())
|
||||
self.assertTrue(da.can_access(al))
|
||||
|
||||
def test_create_with_template_and_empty_quota(self):
|
||||
"""Test the creation of a domain with a template and no quota"""
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 0, "create_dom_admin": "yes",
|
||||
"dom_admin_username": "toto", "create_aliases": "yes",
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_add"),
|
||||
values
|
||||
)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
da = User.objects.get(username="toto@pouet.com")
|
||||
self.assertIn(da, dom.admins)
|
||||
al = Alias.objects.get(address="postmaster", domain__name="pouet.com")
|
||||
self.assertIn(da.mailbox_set.all()[0], al.mboxes.all())
|
||||
self.assertTrue(da.can_access(al))
|
||||
|
||||
def test_create_using_default_quota(self):
|
||||
parameters.save_admin('DEFAULT_DOMAIN_QUOTA', 50, app='admin')
|
||||
values = {
|
||||
"name": "pouet.com", "create_dom_admin": "yes",
|
||||
"dom_admin_username": "toto", "create_aliases": "yes",
|
||||
"stepid": 'step2'
|
||||
}
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_add"),
|
||||
values
|
||||
)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
self.assertEqual(dom.quota, 50)
|
||||
da = User.objects.get(username="toto@pouet.com")
|
||||
self.assertEqual(da.mailbox_set.all()[0].quota, 50)
|
||||
|
||||
def test_modify(self):
|
||||
"""Test the modification of a domain
|
||||
|
||||
Rename 'test.com' domain to 'pouet.com'
|
||||
"""
|
||||
values = {
|
||||
"name": "pouet.com", "quota": 100, "enabled": True
|
||||
}
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_change", args=[dom.id]),
|
||||
values
|
||||
)
|
||||
dom = Domain.objects.get(name="pouet.com")
|
||||
self.assertEqual(dom.name, "pouet.com")
|
||||
self.assertTrue(dom.enabled)
|
||||
|
||||
def test_delete(self):
|
||||
"""Test the removal of a domain
|
||||
"""
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_delete", args=[dom.id]),
|
||||
{}
|
||||
)
|
||||
with self.assertRaises(Domain.DoesNotExist):
|
||||
Domain.objects.get(pk=1)
|
||||
@@ -1,47 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias
|
||||
)
|
||||
from modoboa.extensions.admin import factories
|
||||
|
||||
|
||||
class DomainAliasTestCase(ModoTestCase):
|
||||
fixtures = ['initial_users.json']
|
||||
|
||||
def setUp(self):
|
||||
super(DomainAliasTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
self.dom = Domain.objects.get(name='test.com')
|
||||
|
||||
def test_model(self):
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
domal = DomainAlias()
|
||||
domal.name = "domalias.net"
|
||||
domal.target = dom
|
||||
domal.save()
|
||||
self.assertEqual(dom.domainalias_count, 1)
|
||||
|
||||
domal.name = "domalias.org"
|
||||
domal.save()
|
||||
|
||||
domal.delete()
|
||||
|
||||
def test_form(self):
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
values = dict(name=dom.name, quota=dom.quota, enabled=dom.enabled,
|
||||
aliases="domalias.net", aliases_1="domalias.com")
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_change",
|
||||
args=[dom.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(dom.domainalias_set.count(), 2)
|
||||
|
||||
del values["aliases_1"]
|
||||
self.ajax_post(
|
||||
reverse("admin:domain_change",
|
||||
args=[dom.id]),
|
||||
values
|
||||
)
|
||||
self.assertEqual(dom.domainalias_set.count(), 1)
|
||||
@@ -1,84 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.extensions.admin import factories
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
|
||||
class ExportTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(ExportTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
|
||||
def __export_identities(self, idtfilter="", grpfilter=""):
|
||||
self.clt.get(
|
||||
reverse("admin:_identity_list")
|
||||
+ "?grpfilter=%s&idtfilter=%s" % (grpfilter, idtfilter)
|
||||
)
|
||||
return self.clt.post(
|
||||
reverse("admin:identity_export"),
|
||||
{"filename": "test.csv"}
|
||||
)
|
||||
|
||||
def assertListEqual(self, list1, list2):
|
||||
list2 = list2.split('\r\n')
|
||||
for entry in list1.split('\r\n'):
|
||||
if not entry:
|
||||
continue
|
||||
self.assertIn(entry, list2)
|
||||
|
||||
def test_export_identities(self):
|
||||
response = self.__export_identities()
|
||||
self.assertListEqual(
|
||||
"account;admin;{CRYPT}dTTsGDkA5ZHKg;;;True;SuperAdmins;;\r\naccount;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com\r\naccount;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\nalias;alias@test.com;True;user@test.com\r\nforward;forward@test.com;True;user@external.com\r\ndlist;postmaster@test.com;True;toto@titi.com;test@truc.fr\r\n",
|
||||
response.content.strip()
|
||||
)
|
||||
|
||||
def test_export_simpleusers(self):
|
||||
response = self.__export_identities(
|
||||
idtfilter="account", grpfilter="SimpleUsers"
|
||||
)
|
||||
self.assertListEqual(
|
||||
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10",
|
||||
response.content.strip()
|
||||
)
|
||||
|
||||
def test_export_superadmins(self):
|
||||
response = self.__export_identities(
|
||||
idtfilter="account", grpfilter="SuperAdmins"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.content.strip(),
|
||||
"account;admin;{CRYPT}dTTsGDkA5ZHKg;;;True;SuperAdmins;;"
|
||||
)
|
||||
|
||||
def test_export_domainadmins(self):
|
||||
response = self.__export_identities(
|
||||
idtfilter="account", grpfilter="DomainAdmins"
|
||||
)
|
||||
self.assertListEqual(
|
||||
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com",
|
||||
response.content.strip()
|
||||
)
|
||||
|
||||
def test_export_aliases(self):
|
||||
response = self.__export_identities(idtfilter="alias")
|
||||
self.assertEqual(
|
||||
response.content.strip(),
|
||||
"alias;alias@test.com;True;user@test.com"
|
||||
)
|
||||
|
||||
def test_export_forwards(self):
|
||||
response = self.__export_identities(idtfilter="forward")
|
||||
self.assertEqual(
|
||||
response.content.strip(),
|
||||
"forward;forward@test.com;True;user@external.com"
|
||||
)
|
||||
|
||||
def test_export_dlists(self):
|
||||
response = self.__export_identities(idtfilter="dlist")
|
||||
self.assertEqual(
|
||||
response.content.strip(),
|
||||
"dlist;postmaster@test.com;True;toto@titi.com;test@truc.fr"
|
||||
)
|
||||
@@ -1,191 +0,0 @@
|
||||
# coding: utf-8
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin import factories
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, Alias, DomainAlias
|
||||
)
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
|
||||
class ImportTestCase(ModoTestCase):
|
||||
fixtures = ["initial_users.json"]
|
||||
|
||||
def setUp(self):
|
||||
super(ImportTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
|
||||
def test_domains_import(self):
|
||||
f = ContentFile(b"""domain; domain1.com; 100; True
|
||||
domain; domain2.com; 200; False
|
||||
domainalias; domalias1.com; domain1.com; True
|
||||
""", name="domains.csv")
|
||||
self.clt.post(
|
||||
reverse("admin:domain_import"), {
|
||||
"sourcefile": f
|
||||
}
|
||||
)
|
||||
admin = User.objects.get(username="admin")
|
||||
dom = Domain.objects.get(name="domain1.com")
|
||||
self.assertEqual(dom.quota, 100)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
domalias = DomainAlias.objects.get(name="domalias1.com")
|
||||
self.assertEqual(domalias.target, dom)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(domalias))
|
||||
dom = Domain.objects.get(name="domain2.com")
|
||||
self.assertEqual(dom.quota, 200)
|
||||
self.assertFalse(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
def test_import_domains_with_conflict(self):
|
||||
f = ContentFile(b"""domain;test.alias;10;True
|
||||
domainalias;test.alias;test.com;True
|
||||
""", name="domains.csv")
|
||||
resp = self.clt.post(
|
||||
reverse("admin:domain_import"), {
|
||||
"sourcefile": f
|
||||
}
|
||||
)
|
||||
self.assertIn('Object already exists: domainalias', resp.content)
|
||||
|
||||
def test_identities_import(self):
|
||||
f = ContentFile(b"""
|
||||
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 0
|
||||
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 5; test.com
|
||||
alias; alias1@test.com; True; user1@test.com
|
||||
forward; fwd1@test.com; True; user@extdomain.com
|
||||
dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
|
||||
""", name="identities.csv")
|
||||
self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
admin = User.objects.get(username="admin")
|
||||
u1 = User.objects.get(username="user1@test.com")
|
||||
mb1 = u1.mailbox_set.all()[0]
|
||||
self.assertTrue(admin.is_owner(u1))
|
||||
self.assertEqual(u1.email, "user1@test.com")
|
||||
self.assertEqual(u1.first_name, "User")
|
||||
self.assertEqual(u1.last_name, "One")
|
||||
self.assertTrue(u1.is_active)
|
||||
self.assertEqual(u1.group, "SimpleUsers")
|
||||
self.assertTrue(mb1.use_domain_quota)
|
||||
self.assertEqual(mb1.quota, 0)
|
||||
self.assertTrue(admin.is_owner(mb1))
|
||||
self.assertEqual(mb1.full_address, "user1@test.com")
|
||||
self.assertTrue(
|
||||
self.clt.login(username="user1@test.com", password="toto")
|
||||
)
|
||||
|
||||
da = User.objects.get(username="truc@test.com")
|
||||
damb = da.mailbox_set.all()[0]
|
||||
self.assertEqual(da.first_name, u"René")
|
||||
self.assertEqual(da.group, "DomainAdmins")
|
||||
self.assertEqual(damb.quota, 5)
|
||||
self.assertFalse(damb.use_domain_quota)
|
||||
self.assertEqual(damb.full_address, "truc@test.com")
|
||||
dom = Domain.objects.get(name="test.com")
|
||||
self.assertIn(da, dom.admins)
|
||||
u = User.objects.get(username="user@test.com")
|
||||
self.assertTrue(da.can_access(u))
|
||||
|
||||
al = Alias.objects.get(address="alias1", domain__name="test.com")
|
||||
self.assertIn(u1.mailbox_set.all()[0], al.mboxes.all())
|
||||
self.assertTrue(admin.is_owner(al))
|
||||
|
||||
fwd = Alias.objects.get(address="fwd1", domain__name="test.com")
|
||||
self.assertIn("user@extdomain.com", fwd.extmboxes)
|
||||
self.assertTrue(admin.is_owner(fwd))
|
||||
|
||||
dlist = Alias.objects.get(address="dlist", domain__name="test.com")
|
||||
self.assertIn(u1.mailbox_set.all()[0], dlist.mboxes.all())
|
||||
self.assertIn("user@extdomain.com", dlist.extmboxes)
|
||||
self.assertTrue(admin.is_owner(dlist))
|
||||
|
||||
def test_import_invalid_quota(self):
|
||||
f = ContentFile(b"""
|
||||
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; test.com
|
||||
""", name="identities.csv")
|
||||
resp = self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn('wrong quota value', resp.content)
|
||||
|
||||
def test_import_quota_too_big(self):
|
||||
self.clt.logout()
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
f = ContentFile(b"""
|
||||
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 20
|
||||
""", name="identities.csv")
|
||||
resp = self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn('Quota is greater than the allowed', resp.content)
|
||||
|
||||
def test_import_missing_quota(self):
|
||||
f = ContentFile(b"""
|
||||
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
|
||||
""", name="identities.csv")
|
||||
resp = self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
account = User.objects.get(username="user1@test.com")
|
||||
self.assertEqual(
|
||||
account.mailbox_set.all()[0].quota,
|
||||
account.mailbox_set.all()[0].domain.quota
|
||||
)
|
||||
|
||||
def test_import_duplicate(self):
|
||||
f = ContentFile(b"""
|
||||
account; admin@test.com; toto; Admin; ; True; DomainAdmins; admin@test.com; 0; test.com
|
||||
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0; test.com
|
||||
""", name="identities.csv")
|
||||
self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True,
|
||||
"continue_if_exists": True}
|
||||
)
|
||||
admin = User.objects.get(username="admin")
|
||||
u1 = User.objects.get(username="truc@test.com")
|
||||
self.assertTrue(admin.is_owner(u1))
|
||||
|
||||
def test_import_superadmin(self):
|
||||
"""Check if a domain admin can import a superadmin
|
||||
|
||||
Expected result: no
|
||||
"""
|
||||
self.clt.logout()
|
||||
self.assertTrue(
|
||||
self.clt.login(username="admin@test.com", password="toto")
|
||||
)
|
||||
f = ContentFile(b"""
|
||||
account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com; 50
|
||||
""", name="identities.csv")
|
||||
self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True,
|
||||
"continue_if_exists": True}
|
||||
)
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get(username="sa@test.com")
|
||||
|
||||
def test_import_alias_with_empty_values(self):
|
||||
f = ContentFile(b"""
|
||||
alias;user.alias@test.com;True;user@test.com;;;;;;;;;;;;;;;;
|
||||
""", name="identities.csv")
|
||||
self.clt.post(
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True,
|
||||
"continue_if_exists": True}
|
||||
)
|
||||
alias = Alias.objects.get(
|
||||
domain__name="test.com", address="user.alias"
|
||||
)
|
||||
self.assertEqual(alias.type, "alias")
|
||||
@@ -1,55 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.core import load_core_settings
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.extensions.admin import factories
|
||||
|
||||
|
||||
class PasswordSchemesTestCase(ModoTestCase):
|
||||
fixtures = ['initial_users.json']
|
||||
|
||||
def setUp(self):
|
||||
super(PasswordSchemesTestCase, self).setUp()
|
||||
factories.populate_database()
|
||||
load_core_settings()
|
||||
|
||||
def _create_account(self):
|
||||
values = dict(
|
||||
username="tester@test.com", first_name="Tester", last_name="Toto",
|
||||
password1="toto", password2="toto", role="SimpleUsers",
|
||||
quota_act=True,
|
||||
is_active=True, email="tester@test.com", stepid='step2'
|
||||
)
|
||||
self.ajax_post(
|
||||
reverse("admin:account_add"),
|
||||
values
|
||||
)
|
||||
|
||||
def _test_scheme(self, name, startpattern):
|
||||
parameters.save_admin('PASSWORD_SCHEME', name, app='core')
|
||||
self._create_account()
|
||||
account = User.objects.get(username='tester@test.com')
|
||||
self.assertTrue(account.password.startswith(startpattern))
|
||||
self.assertTrue(account.check_password('toto'))
|
||||
|
||||
def test_sha512crypt_scheme(self):
|
||||
self._test_scheme('sha512crypt', '{SHA512-CRYPT}')
|
||||
|
||||
def test_sha256crypt_scheme(self):
|
||||
self._test_scheme('sha256crypt', '{SHA256-CRYPT}')
|
||||
|
||||
def test_md5crypt_scheme(self):
|
||||
self._test_scheme('md5crypt', '{MD5-CRYPT}')
|
||||
|
||||
def test_sha256_scheme(self):
|
||||
self._test_scheme('sha256', '{SHA256}')
|
||||
|
||||
def test_md5_scheme(self):
|
||||
self._test_scheme('md5', '{MD5}')
|
||||
|
||||
def test_crypt(self):
|
||||
self._test_scheme('crypt', '{CRYPT}')
|
||||
|
||||
def test_plain(self):
|
||||
self._test_scheme('plain', '{PLAIN}')
|
||||
@@ -1,26 +0,0 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.extensions.admin.factories import populate_database
|
||||
from modoboa.extensions.admin.models import Alias
|
||||
|
||||
|
||||
class ForwardTestCase(ModoTestCase):
|
||||
fixtures = ['initial_users.json']
|
||||
|
||||
def setUp(self):
|
||||
super(ForwardTestCase, self).setUp()
|
||||
populate_database()
|
||||
|
||||
def test_forward_permissions(self):
|
||||
self.clt.logout()
|
||||
self.clt.login(username='user@test.com', password='toto')
|
||||
self.ajax_post(
|
||||
reverse('user_forward'),
|
||||
{'dest': 'user@extdomain.com', 'keepcopies': True}
|
||||
)
|
||||
forward = Alias.objects.get(address='user', domain__name='test.com')
|
||||
sadmin = User.objects.get(username='admin')
|
||||
self.assertTrue(sadmin.can_access(forward))
|
||||
domadmin = User.objects.get(username='admin@test.com')
|
||||
self.assertTrue(domadmin.can_access(forward))
|
||||
@@ -1,60 +0,0 @@
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'modoboa.extensions.admin.views.domain',
|
||||
|
||||
url(r'^$', 'index', name="index"),
|
||||
url(r'^domains/$', 'domains', name="domain_list"),
|
||||
url(r'^domains/list/$', '_domains', name="_domain_list"),
|
||||
url(r'^domains/flatlist/$', 'domains_list', name="domain_flat_list"),
|
||||
url(r'^domains/stats/$', 'domain_statistics', name="domain_statistics"),
|
||||
url(r'^domains/new/', 'newdomain', name='domain_add'),
|
||||
url(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain', name="domain_change"),
|
||||
url(r'^domains/(?P<dom_id>\d+)/delete/$', 'deldomain',
|
||||
name="domain_delete"),
|
||||
)
|
||||
|
||||
urlpatterns += patterns(
|
||||
'modoboa.extensions.admin.views.identity',
|
||||
|
||||
url(r'^permissions/remove/$', 'remove_permission',
|
||||
name="permission_remove"),
|
||||
|
||||
url(r'^identities/$', 'identities', name="identity_list"),
|
||||
url(r'^identities/list/$', '_identities', name="_identity_list"),
|
||||
url(r'^identities/quotas/$', 'list_quotas', name="quota_list"),
|
||||
url(r'^identities/page/$', 'get_next_page', name="identity_page"),
|
||||
|
||||
url(r'^accounts/list/$', 'accounts_list', name="account_list"),
|
||||
url(r'^accounts/new/$', 'newaccount', name="account_add"),
|
||||
url(r'^accounts/edit/(?P<accountid>\d+)/$', 'editaccount',
|
||||
name="account_change"),
|
||||
url(r'^accounts/delete/(?P<accountid>\d+)/$', 'delaccount',
|
||||
name="account_delete"),
|
||||
)
|
||||
|
||||
urlpatterns += patterns(
|
||||
'modoboa.extensions.admin.views.alias',
|
||||
|
||||
url(r'^distriblists/new/$', 'newdlist', name="dlist_add"),
|
||||
url(r'^forwards/new/$', 'newforward', name="forward_add"),
|
||||
|
||||
url(r'^aliases/new/$', 'newalias', name="alias_add"),
|
||||
url(r'^aliases/edit/(?P<alid>\d+)/$', 'editalias', name="alias_change"),
|
||||
url(r'^aliases/delete/$', 'delalias', name="alias_delete"),
|
||||
)
|
||||
|
||||
urlpatterns += patterns(
|
||||
'modoboa.extensions.admin.views.import',
|
||||
|
||||
url(r'^domains/import/$', 'import_domains', name="domain_import"),
|
||||
url(r'^identities/import/$', 'import_identities', name="identity_import"),
|
||||
)
|
||||
|
||||
urlpatterns += patterns(
|
||||
'modoboa.extensions.admin.views.export',
|
||||
|
||||
url(r'^domains/export/$', 'export_domains', name="domain_export"),
|
||||
url(r'^identities/export/$', 'export_identities', name="identity_export"),
|
||||
)
|
||||
@@ -1,135 +0,0 @@
|
||||
import reversion
|
||||
from django.db import IntegrityError
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from django.shortcuts import render
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required
|
||||
)
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.web_utils import render_to_json_response
|
||||
from modoboa.lib.exceptions import PermDeniedException, Conflict
|
||||
from modoboa.extensions.admin.forms import AliasForm
|
||||
from modoboa.extensions.admin.models import Alias
|
||||
|
||||
|
||||
def _validate_alias(request, form, successmsg, callback=None):
|
||||
"""Alias validation
|
||||
|
||||
Common function shared between creation and modification actions.
|
||||
"""
|
||||
if form.is_valid():
|
||||
form.set_recipients()
|
||||
try:
|
||||
alias = form.save()
|
||||
except IntegrityError:
|
||||
raise Conflict(_("Alias with this name already exists"))
|
||||
if callback:
|
||||
callback(request.user, alias)
|
||||
return render_to_json_response(successmsg)
|
||||
|
||||
return render_to_json_response({'form_errors': form.errors}, status=400)
|
||||
|
||||
|
||||
def _new_alias(request, title, action, successmsg,
|
||||
tplname="admin/aliasform.html"):
|
||||
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
|
||||
if request.method == "POST":
|
||||
def callback(user, alias):
|
||||
alias.post_create(user)
|
||||
|
||||
form = AliasForm(request.user, request.POST)
|
||||
return _validate_alias(
|
||||
request, form, successmsg, callback
|
||||
)
|
||||
|
||||
ctx = {
|
||||
"title": title,
|
||||
"action": action,
|
||||
"formid": "aliasform",
|
||||
"action_label": _("Create"),
|
||||
"action_classes": "submit",
|
||||
"form": AliasForm(request.user)
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_alias")
|
||||
@reversion.create_revision()
|
||||
def newdlist(request):
|
||||
return _new_alias(
|
||||
request, _("New distribution list"), reverse("admin:dlist_add"),
|
||||
_("Distribution list created")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_alias")
|
||||
@reversion.create_revision()
|
||||
def newalias(request):
|
||||
return _new_alias(
|
||||
request, _("New alias"), reverse("admin:alias_add"),
|
||||
_("Alias created")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_alias")
|
||||
@reversion.create_revision()
|
||||
def newforward(request):
|
||||
return _new_alias(
|
||||
request, _("New forward"), reverse("admin:forward_add"),
|
||||
_("Forward created")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.change_alias")
|
||||
@reversion.create_revision()
|
||||
def editalias(request, alid, tplname="admin/aliasform.html"):
|
||||
alias = Alias.objects.get(pk=alid)
|
||||
if not request.user.can_access(alias):
|
||||
raise PermDeniedException
|
||||
if request.method == "POST":
|
||||
if len(alias.get_recipients()) >= 2:
|
||||
successmsg = _("Distribution list modified")
|
||||
elif alias.extmboxes != "":
|
||||
successmsg = _("Forward modified")
|
||||
else:
|
||||
successmsg = _("Alias modified")
|
||||
form = AliasForm(request.user, request.POST, instance=alias)
|
||||
return _validate_alias(request, form, successmsg)
|
||||
|
||||
ctx = {
|
||||
'action': reverse("admin:alias_change", args=[alias.id]),
|
||||
'formid': 'aliasform',
|
||||
'title': alias.full_address,
|
||||
'action_label': _('Update'),
|
||||
'action_classes': 'submit',
|
||||
'form': AliasForm(request.user, instance=alias)
|
||||
}
|
||||
return render(request, tplname, ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.delete_alias")
|
||||
def delalias(request):
|
||||
selection = request.GET["selection"].split(",")
|
||||
for alid in selection:
|
||||
alias = Alias.objects.get(pk=alid)
|
||||
if not request.user.can_access(alias):
|
||||
raise PermDeniedException
|
||||
if alias.type == 'dlist':
|
||||
msg = "Distribution list deleted"
|
||||
msgs = "Distribution lists deleted"
|
||||
elif alias.type == 'forward':
|
||||
msg = "Forward deleted"
|
||||
msgs = "Forwards deleted"
|
||||
else:
|
||||
msg = "Alias deleted"
|
||||
msgs = "Aliases deleted"
|
||||
alias.delete()
|
||||
|
||||
msg = ungettext(msg, msgs, len(selection))
|
||||
return render_to_json_response(msg)
|
||||
@@ -1,154 +0,0 @@
|
||||
"""
|
||||
Domain related views.
|
||||
"""
|
||||
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
|
||||
import reversion
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.web_utils import (
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
from modoboa.lib.exceptions import (
|
||||
PermDeniedException
|
||||
)
|
||||
from modoboa.lib.listing import get_sort_order, get_listing_page
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Mailbox, Alias
|
||||
)
|
||||
from modoboa.extensions.admin.forms import (
|
||||
DomainForm
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_domains
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
return HttpResponseRedirect(reverse("admin:domain_list"))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("admin.view_domains")
|
||||
or u.has_perm("admin.view_mailboxes")
|
||||
)
|
||||
def _domains(request):
|
||||
sort_order, sort_dir = get_sort_order(request.GET, "name")
|
||||
filters = dict(
|
||||
(flt, request.GET.get(flt, None))
|
||||
for flt in ['domfilter', 'searchquery']
|
||||
+ events.raiseQueryEvent('ExtraDomainFilters')
|
||||
)
|
||||
request.session['domains_filters'] = filters
|
||||
domainlist = get_domains(request.user, **filters)
|
||||
if sort_order == 'name':
|
||||
domainlist = sorted(
|
||||
domainlist,
|
||||
key=lambda d: getattr(d, sort_order), reverse=sort_dir == '-'
|
||||
)
|
||||
else:
|
||||
domainlist = sorted(domainlist, key=lambda d: d.tags[0],
|
||||
reverse=sort_dir == '-')
|
||||
context = {
|
||||
"handle_mailboxes": parameters.get_admin(
|
||||
"HANDLE_MAILBOXES", raise_error=False),
|
||||
"auto_account_removal": parameters.get_admin("AUTO_ACCOUNT_REMOVAL")
|
||||
}
|
||||
page = get_listing_page(domainlist, request.GET.get("page", 1))
|
||||
if page is None:
|
||||
context["length"] = 0
|
||||
else:
|
||||
context["rows"] = _render_to_string(
|
||||
request, 'admin/domains_table.html', {
|
||||
'domains': page.object_list,
|
||||
}
|
||||
)
|
||||
context["pages"] = [page.number]
|
||||
return render_to_json_response(context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def domains(request, tplname="admin/domains.html"):
|
||||
if not request.user.has_perm("admin.view_domains"):
|
||||
if request.user.has_perm("admin.view_mailboxes"):
|
||||
return HttpResponseRedirect(
|
||||
reverse("admin:identity_list")
|
||||
)
|
||||
return HttpResponseRedirect(reverse("core:user_index"))
|
||||
return render(request, tplname, {"selection": "domains"})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.add_user")
|
||||
def domains_list(request):
|
||||
doms = [dom.name for dom in Domain.objects.get_for_admin(request.user)]
|
||||
return render_to_json_response(doms)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_domain")
|
||||
@reversion.create_revision()
|
||||
def newdomain(request):
|
||||
from modoboa.extensions.admin.forms import DomainWizard
|
||||
|
||||
events.raiseEvent("CanCreate", request.user, "domains")
|
||||
return DomainWizard(request).process()
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.view_domains")
|
||||
@reversion.create_revision()
|
||||
def editdomain(request, dom_id):
|
||||
"""Edit domain view."""
|
||||
domain = Domain.objects.get(pk=dom_id)
|
||||
if not request.user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
|
||||
instances = dict(general=domain)
|
||||
events.raiseEvent("FillDomainInstances", request.user, domain, instances)
|
||||
return DomainForm(request, instances=instances).process()
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.delete_domain")
|
||||
def deldomain(request, dom_id):
|
||||
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
|
||||
try:
|
||||
mb = Mailbox.objects.get(user__id=request.user.id)
|
||||
except Mailbox.DoesNotExist:
|
||||
mb = None
|
||||
|
||||
dom = Domain.objects.get(pk=dom_id)
|
||||
if not request.user.can_access(dom):
|
||||
raise PermDeniedException
|
||||
if mb and mb.domain == dom:
|
||||
raise PermDeniedException(_("You can't delete your own domain"))
|
||||
dom.delete(request.user, keepdir)
|
||||
|
||||
msg = ungettext("Domain deleted", "Domains deleted", 1)
|
||||
return render_to_json_response(msg)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.view_domains")
|
||||
def domain_statistics(request):
|
||||
"""A simple page to present domain statistics."""
|
||||
context = {}
|
||||
if request.user.group == "SuperAdmins":
|
||||
context.update({
|
||||
"domains_counter": Domain.objects.count(),
|
||||
"domain_aliases_counter": DomainAlias.objects.count(),
|
||||
"identities_counter": User.objects.count() + Alias.objects.count(),
|
||||
})
|
||||
context.update({"domains": Domain.objects.get_for_admin(request.user)})
|
||||
return render(request, "admin/domain_statistics.html", context)
|
||||
@@ -1,90 +0,0 @@
|
||||
"""Export related views."""
|
||||
|
||||
import cStringIO
|
||||
import csv
|
||||
|
||||
from rfc6266 import build_header
|
||||
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.extensions.admin.forms import (
|
||||
ExportIdentitiesForm, ExportDomainsForm
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_domains, get_identities
|
||||
|
||||
|
||||
def _export(content, filename):
|
||||
"""Export a csv file's content
|
||||
|
||||
:param content: the content to export (string)
|
||||
:param filename: the name that will appear into the response
|
||||
:return: an ``HttpResponse`` object
|
||||
"""
|
||||
resp = HttpResponse(content)
|
||||
resp["Content-Type"] = "text/csv"
|
||||
resp["Content-Length"] = len(content)
|
||||
resp["Content-Disposition"] = build_header(filename)
|
||||
return resp
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
|
||||
)
|
||||
def export_identities(request):
|
||||
ctx = {
|
||||
"title": _("Export identities"),
|
||||
"action_label": _("Export"),
|
||||
"action_classes": "submit",
|
||||
"formid": "exportform",
|
||||
"action": reverse("admin:identity_export"),
|
||||
}
|
||||
|
||||
if request.method == "POST":
|
||||
form = ExportIdentitiesForm(request.POST)
|
||||
form.is_valid()
|
||||
fp = cStringIO.StringIO()
|
||||
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
|
||||
identities = get_identities(
|
||||
request.user, **request.session['identities_filters'])
|
||||
for ident in identities:
|
||||
ident.to_csv(csvwriter)
|
||||
content = fp.getvalue()
|
||||
fp.close()
|
||||
return _export(content, form.cleaned_data["filename"])
|
||||
|
||||
ctx["form"] = ExportIdentitiesForm()
|
||||
return render(request, "common/generic_modal_form.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_domain")
|
||||
def export_domains(request):
|
||||
ctx = {
|
||||
"title": _("Export domains"),
|
||||
"action_label": _("Export"),
|
||||
"action_classes": "submit",
|
||||
"formid": "exportform",
|
||||
"action": reverse("admin:domain_export"),
|
||||
}
|
||||
|
||||
if request.method == "POST":
|
||||
form = ExportDomainsForm(request.POST)
|
||||
form.is_valid()
|
||||
fp = cStringIO.StringIO()
|
||||
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
|
||||
for dom in get_domains(request.user,
|
||||
**request.session['domains_filters']):
|
||||
dom.to_csv(csvwriter)
|
||||
content = fp.getvalue()
|
||||
fp.close()
|
||||
return _export(content, form.cleaned_data["filename"])
|
||||
|
||||
ctx["form"] = ExportDomainsForm()
|
||||
return render(request, "common/generic_modal_form.html", ctx)
|
||||
@@ -1,211 +0,0 @@
|
||||
"""Identity related views."""
|
||||
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
|
||||
import reversion
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.forms import (
|
||||
AccountForm
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_identities
|
||||
from modoboa.extensions.admin.models import Mailbox, Domain
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import (
|
||||
PermDeniedException, BadRequest
|
||||
)
|
||||
from modoboa.lib.listing import (
|
||||
get_sort_order, get_listing_page
|
||||
)
|
||||
from modoboa.lib.web_utils import (
|
||||
_render_to_string, render_to_json_response
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
|
||||
)
|
||||
def _identities(request):
|
||||
filters = dict((fname, request.GET.get(fname, None))
|
||||
for fname in ['searchquery', 'idtfilter', 'grpfilter'])
|
||||
request.session['identities_filters'] = filters
|
||||
idents_list = get_identities(request.user, **filters)
|
||||
sort_order, sort_dir = get_sort_order(request.GET, "identity",
|
||||
["identity", "name_or_rcpt", "tags"])
|
||||
if sort_order in ["identity", "name_or_rcpt"]:
|
||||
objects = sorted(idents_list, key=lambda o: getattr(o, sort_order),
|
||||
reverse=sort_dir == '-')
|
||||
else:
|
||||
objects = sorted(idents_list, key=lambda o: o.tags[0],
|
||||
reverse=sort_dir == '-')
|
||||
context = {
|
||||
"handle_mailboxes": parameters.get_admin(
|
||||
"HANDLE_MAILBOXES", raise_error=False)
|
||||
}
|
||||
page = get_listing_page(objects, request.GET.get("page", 1))
|
||||
if page is None:
|
||||
context["length"] = 0
|
||||
else:
|
||||
context["headers"] = _render_to_string(
|
||||
request, "admin/identity_headers.html", {})
|
||||
context["rows"] = _render_to_string(
|
||||
request, "admin/identities_table.html", {
|
||||
"identities": page.object_list
|
||||
}
|
||||
)
|
||||
context["pages"] = [page.number]
|
||||
return render_to_json_response(context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_mailbox")
|
||||
def list_quotas(request):
|
||||
from modoboa.lib.db_utils import db_type
|
||||
|
||||
sort_order, sort_dir = get_sort_order(request.GET, "address")
|
||||
mboxes = Mailbox.objects.get_for_admin(
|
||||
request.user, request.GET.get("searchquery", None)
|
||||
)
|
||||
mboxes = mboxes.exclude(quota=0)
|
||||
if sort_order in ["address", "quota"]:
|
||||
mboxes = mboxes.order_by("%s%s" % (sort_dir, sort_order))
|
||||
elif sort_order == "quota_value__bytes":
|
||||
where = "admin_mailbox.address||'@'||admin_domain.name"
|
||||
mboxes = mboxes.extra(
|
||||
select={"quota_value__bytes": "admin_quota.bytes"},
|
||||
where=["admin_quota.username=%s" % where],
|
||||
tables=["admin_quota", "admin_domain"],
|
||||
order_by=["%s%s" % (sort_dir, sort_order)]
|
||||
)
|
||||
elif sort_order == "quota_usage":
|
||||
where = "admin_mailbox.address||'@'||admin_domain.name"
|
||||
db_type = db_type()
|
||||
if db_type == "postgres":
|
||||
select = (
|
||||
"(admin_quota.bytes::float / (CAST(admin_mailbox.quota "
|
||||
"AS BIGINT) * 1048576)) * 100"
|
||||
)
|
||||
else:
|
||||
select = (
|
||||
"admin_quota.bytes / (admin_mailbox.quota "
|
||||
"* 1048576) * 100"
|
||||
)
|
||||
if db_type == "mysql":
|
||||
where = "CONCAT(admin_mailbox.address,'@',admin_domain.name)"
|
||||
mboxes = mboxes.extra(
|
||||
select={'quota_usage': select},
|
||||
where=["admin_quota.username=%s" % where],
|
||||
tables=["admin_quota", "admin_domain"],
|
||||
order_by=["%s%s" % (sort_dir, sort_order)]
|
||||
)
|
||||
else:
|
||||
raise BadRequest(_("Invalid request"))
|
||||
page = get_listing_page(mboxes, request.GET.get("page", 1))
|
||||
context = {
|
||||
"headers": _render_to_string(
|
||||
request, "admin/quota_headers.html", {}
|
||||
)
|
||||
}
|
||||
if page is None:
|
||||
context["length"] = 0
|
||||
else:
|
||||
context["rows"] = _render_to_string(
|
||||
request, "admin/quotas.html", {
|
||||
"mboxes": page
|
||||
}
|
||||
)
|
||||
context["pages"] = [page.number]
|
||||
return render_to_json_response(context)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("admin.add_user") or u.has_perm("admin.add_alias")
|
||||
or u.has_perm("admin.add_mailbox")
|
||||
)
|
||||
def get_next_page(request):
|
||||
"""Return the next page of the identity list."""
|
||||
if request.GET.get("objtype", "identity") == "identity":
|
||||
return _identities(request)
|
||||
return list_quotas(request)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("admin.add_user") or u.has_perm("admin.add_alias")
|
||||
)
|
||||
@ensure_csrf_cookie
|
||||
def identities(request, tplname="admin/identities.html"):
|
||||
return render(request, tplname, {
|
||||
"selection": "identities",
|
||||
"deflocation": "list/"
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.add_user")
|
||||
def accounts_list(request):
|
||||
accs = User.objects.filter(is_superuser=False) \
|
||||
.exclude(groups__name='SimpleUsers')
|
||||
res = [a.username for a in accs.all()]
|
||||
return render_to_json_response(res)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.add_user")
|
||||
@reversion.create_revision()
|
||||
def newaccount(request):
|
||||
"""Create a new account."""
|
||||
from modoboa.extensions.admin.forms import AccountWizard
|
||||
return AccountWizard(request).process()
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.change_user")
|
||||
@reversion.create_revision()
|
||||
def editaccount(request, accountid, tplname="common/tabforms.html"):
|
||||
account = User.objects.get(pk=accountid)
|
||||
if not request.user.can_access(account):
|
||||
raise PermDeniedException
|
||||
mb = None
|
||||
if account.mailbox_set.count():
|
||||
mb = account.mailbox_set.all()[0]
|
||||
|
||||
instances = dict(general=account, mail=mb, perms=account)
|
||||
events.raiseEvent("FillAccountInstances", request.user, account, instances)
|
||||
return AccountForm(request, instances=instances).process()
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.delete_user")
|
||||
def delaccount(request, accountid):
|
||||
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
|
||||
User.objects.get(pk=accountid).delete(request.user, keep_mb_dir=keepdir)
|
||||
return render_to_json_response(
|
||||
ungettext("Account deleted", "Accounts deleted", 1)
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_domain")
|
||||
def remove_permission(request):
|
||||
domid = request.GET.get("domid", None)
|
||||
daid = request.GET.get("daid", None)
|
||||
if domid is None or daid is None:
|
||||
raise BadRequest(_("Invalid request"))
|
||||
try:
|
||||
account = User.objects.get(pk=daid)
|
||||
domain = Domain.objects.get(pk=domid)
|
||||
except (User.DoesNotExist, Domain.DoesNotExist):
|
||||
raise BadRequest(_("Invalid request"))
|
||||
if not request.user.can_access(account) or \
|
||||
not request.user.can_access(domain):
|
||||
raise PermDeniedException
|
||||
domain.remove_admin(account)
|
||||
return render_to_json_response({})
|
||||
@@ -1,177 +0,0 @@
|
||||
"""Import related views."""
|
||||
|
||||
import csv
|
||||
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import reversion
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.forms import ImportIdentitiesForm, ImportDataForm
|
||||
from modoboa.extensions.admin.models import (
|
||||
Domain, DomainAlias, Alias
|
||||
)
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import ModoboaException, Conflict
|
||||
|
||||
|
||||
def import_domain(user, row, formopts):
|
||||
"""Specific code for domains import"""
|
||||
dom = Domain()
|
||||
dom.from_csv(user, row)
|
||||
|
||||
|
||||
def import_domainalias(user, row, formopts):
|
||||
"""Specific code for domain aliases import"""
|
||||
domalias = DomainAlias()
|
||||
domalias.from_csv(user, row)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.add_domain")
|
||||
def import_domains(request):
|
||||
if request.method == "POST":
|
||||
return importdata(request)
|
||||
|
||||
helptext = _("""Provide a CSV file where lines respect one of the following formats:
|
||||
<ul>
|
||||
<li><em>domain; name; quota; enabled</em></li>
|
||||
<li><em>domainalias; name; targeted domain; enabled</em></li>
|
||||
%s
|
||||
</ul>
|
||||
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
|
||||
<p>You can use a different character as separator.</p>
|
||||
""" % ''.join([unicode(hlp) for hlp in
|
||||
events.raiseQueryEvent('ExtraDomainImportHelp')]))
|
||||
|
||||
ctx = dict(
|
||||
title=_("Import domains"),
|
||||
action_label=_("Import"),
|
||||
action_classes="submit",
|
||||
action=reverse("admin:domain_import"),
|
||||
formid="importform",
|
||||
enctype="multipart/form-data",
|
||||
target="import_target",
|
||||
helptext=helptext,
|
||||
form=ImportDataForm()
|
||||
)
|
||||
return render(request, "admin/importform.html", ctx)
|
||||
|
||||
|
||||
def import_account(user, row, formopts):
|
||||
"""Specific code for accounts import"""
|
||||
account = User()
|
||||
account.from_csv(user, row, formopts["crypt_password"])
|
||||
|
||||
|
||||
def _import_alias(user, row, **kwargs):
|
||||
"""Specific code for aliases import"""
|
||||
alias = Alias()
|
||||
alias.from_csv(user, row, **kwargs)
|
||||
|
||||
|
||||
def import_alias(user, row, formopts):
|
||||
_import_alias(user, row, expected_elements=4)
|
||||
|
||||
|
||||
def import_forward(user, row, formopts):
|
||||
_import_alias(user, row, expected_elements=4)
|
||||
|
||||
|
||||
def import_dlist(user, row, formopts):
|
||||
_import_alias(user, row)
|
||||
|
||||
|
||||
@reversion.create_revision()
|
||||
def importdata(request, formclass=ImportDataForm):
|
||||
"""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 = formclass(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
try:
|
||||
reader = csv.reader(request.FILES['sourcefile'],
|
||||
delimiter=form.cleaned_data['sepchar'])
|
||||
except csv.Error as inst:
|
||||
error = str(inst)
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
cpt = 0
|
||||
for row in reader:
|
||||
if not row:
|
||||
continue
|
||||
try:
|
||||
fct = globals()["import_%s" % row[0].strip()]
|
||||
except KeyError:
|
||||
fct = events.raiseQueryEvent(
|
||||
'ImportObject', row[0].strip()
|
||||
)
|
||||
if not fct:
|
||||
continue
|
||||
fct = fct[0]
|
||||
try:
|
||||
fct(request.user, row, form.cleaned_data)
|
||||
except Conflict:
|
||||
if form.cleaned_data["continue_if_exists"]:
|
||||
continue
|
||||
raise Conflict(
|
||||
_("Object already exists: %s"
|
||||
% form.cleaned_data['sepchar'].join(row[:2]))
|
||||
)
|
||||
cpt += 1
|
||||
msg = _("%d objects imported successfully" % cpt)
|
||||
return render(request, "admin/import_done.html", {
|
||||
"status": "ok", "msg": msg
|
||||
})
|
||||
except (ModoboaException) as e:
|
||||
error = str(e)
|
||||
|
||||
return render(request, "admin/import_done.html", {
|
||||
"status": "ko", "msg": error
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
|
||||
)
|
||||
def import_identities(request):
|
||||
if request.method == "POST":
|
||||
return importdata(request, ImportIdentitiesForm)
|
||||
|
||||
helptext = _("""Provide a CSV file where lines respect one of the following formats:
|
||||
<ul>
|
||||
<li><em>account; loginname; password; first name; last name; enabled; group; address; quota; [, domain, ...]</em></li>
|
||||
<li><em>alias; address; enabled; internal recipient</em></li>
|
||||
<li><em>forward; address; enabled; external recipient</em></li>
|
||||
<li><em>dlist; address; enabled; recipient; recipient; ...</em></li>
|
||||
</ul>
|
||||
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
|
||||
|
||||
<p>You can use a different character as separator.</p>
|
||||
""")
|
||||
ctx = dict(
|
||||
title=_("Import identities"),
|
||||
action_label=_("Import"),
|
||||
action_classes="submit",
|
||||
action=reverse("admin:identity_import"),
|
||||
formid="importform",
|
||||
enctype="multipart/form-data",
|
||||
target="import_target",
|
||||
form=ImportIdentitiesForm(),
|
||||
helptext=helptext
|
||||
)
|
||||
return render(request, "admin/importform.html", ctx)
|
||||
@@ -1,57 +0,0 @@
|
||||
import reversion
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib.web_utils import (
|
||||
render_to_json_response, _render_to_string
|
||||
)
|
||||
from modoboa.extensions.admin.lib import needs_mailbox
|
||||
from modoboa.extensions.admin.models import Alias, Mailbox
|
||||
from modoboa.extensions.admin.forms import ForwardForm
|
||||
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@reversion.create_revision()
|
||||
def forward(request, tplname='admin/forward.html'):
|
||||
mb = request.user.mailbox_set.all()[0]
|
||||
try:
|
||||
al = Alias.objects.get(address=mb.address,
|
||||
domain__name=mb.domain.name)
|
||||
except Alias.DoesNotExist:
|
||||
al = None
|
||||
if request.method == "POST":
|
||||
form = ForwardForm(request.POST)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
if al is None:
|
||||
al = Alias()
|
||||
al.address = mb.address
|
||||
al.domain = mb.domain
|
||||
al.enabled = mb.user.is_active
|
||||
intdests = []
|
||||
if form.cleaned_data["keepcopies"]:
|
||||
intdests += [mb]
|
||||
form.parse_dest()
|
||||
al.save(
|
||||
int_rcpts=intdests, ext_rcpts=form.dests, creator=request.user
|
||||
)
|
||||
return render_to_json_response(_("Forward updated"))
|
||||
|
||||
return render_to_json_response(
|
||||
{'form_errors': form.errors}, status=400
|
||||
)
|
||||
|
||||
form = ForwardForm()
|
||||
if al is not None:
|
||||
form.fields["dest"].initial = al.extmboxes
|
||||
try:
|
||||
al.mboxes.get(pk=mb.id)
|
||||
except Mailbox.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
form.fields["keepcopies"].initial = True
|
||||
return render_to_json_response({
|
||||
"content": _render_to_string(request, tplname, {
|
||||
"form": form
|
||||
})
|
||||
})
|
||||
@@ -162,7 +162,8 @@ def topredirection(request):
|
||||
else:
|
||||
path = reverse("core:user_index")
|
||||
else:
|
||||
path = reverse("admin:domain_list")
|
||||
# FIXME
|
||||
path = reverse("modoboa_admin:domain_list")
|
||||
return HttpResponseRedirect(path)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user