diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index 2ba6dc86b..c19db186e 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -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', diff --git a/modoboa/core/views/auth.py b/modoboa/core/views/auth.py index 44325a366..e515cb61f 100644 --- a/modoboa/core/views/auth.py +++ b/modoboa/core/views/auth.py @@ -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.") diff --git a/modoboa/extensions/admin/__init__.py b/modoboa/extensions/admin/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/admin/app_settings.py b/modoboa/extensions/admin/app_settings.py deleted file mode 100644 index 8638bce9e..000000000 --- a/modoboa/extensions/admin/app_settings.py +++ /dev/null @@ -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'] diff --git a/modoboa/extensions/admin/factories.py b/modoboa/extensions/admin/factories.py deleted file mode 100644 index e49956e83..000000000 --- a/modoboa/extensions/admin/factories.py +++ /dev/null @@ -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) diff --git a/modoboa/extensions/admin/fixtures/demo.json b/modoboa/extensions/admin/fixtures/demo.json deleted file mode 100644 index d3691219b..000000000 --- a/modoboa/extensions/admin/fixtures/demo.json +++ /dev/null @@ -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 - } - } -] diff --git a/modoboa/extensions/admin/forms/__init__.py b/modoboa/extensions/admin/forms/__init__.py deleted file mode 100644 index dbb5a36ef..000000000 --- a/modoboa/extensions/admin/forms/__init__.py +++ /dev/null @@ -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' -] diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py deleted file mode 100644 index 3d929f697..000000000 --- a/modoboa/extensions/admin/forms/account.py +++ /dev/null @@ -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")) diff --git a/modoboa/extensions/admin/forms/alias.py b/modoboa/extensions/admin/forms/alias.py deleted file mode 100644 index 12485eebc..000000000 --- a/modoboa/extensions/admin/forms/alias.py +++ /dev/null @@ -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 diff --git a/modoboa/extensions/admin/forms/domain.py b/modoboa/extensions/admin/forms/domain.py deleted file mode 100644 index 8fc8afb15..000000000 --- a/modoboa/extensions/admin/forms/domain.py +++ /dev/null @@ -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")) diff --git a/modoboa/extensions/admin/forms/export.py b/modoboa/extensions/admin/forms/export.py deleted file mode 100644 index f1f8a868b..000000000 --- a/modoboa/extensions/admin/forms/export.py +++ /dev/null @@ -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" diff --git a/modoboa/extensions/admin/forms/forward.py b/modoboa/extensions/admin/forms/forward.py deleted file mode 100644 index c4d1e4a7b..000000000 --- a/modoboa/extensions/admin/forms/forward.py +++ /dev/null @@ -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.") - ) diff --git a/modoboa/extensions/admin/forms/import_.py b/modoboa/extensions/admin/forms/import_.py deleted file mode 100644 index 0091e4ba8..000000000 --- a/modoboa/extensions/admin/forms/import_.py +++ /dev/null @@ -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" - ) - ) diff --git a/modoboa/extensions/admin/lib.py b/modoboa/extensions/admin/lib.py deleted file mode 100644 index 9ba1e9585..000000000 --- a/modoboa/extensions/admin/lib.py +++ /dev/null @@ -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 diff --git a/modoboa/extensions/admin/management/__init__.py b/modoboa/extensions/admin/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/admin/management/commands/__init__.py b/modoboa/extensions/admin/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py b/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py deleted file mode 100644 index b0cea9d46..000000000 --- a/modoboa/extensions/admin/management/commands/handle_mailbox_operations.py +++ /dev/null @@ -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']) diff --git a/modoboa/extensions/admin/migrations/0001_initial.py b/modoboa/extensions/admin/migrations/0001_initial.py deleted file mode 100644 index ed6048142..000000000 --- a/modoboa/extensions/admin/migrations/0001_initial.py +++ /dev/null @@ -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')]), - ), - ] diff --git a/modoboa/extensions/admin/migrations/__init__.py b/modoboa/extensions/admin/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/admin/models/__init__.py b/modoboa/extensions/admin/models/__init__.py deleted file mode 100644 index 641eff9ad..000000000 --- a/modoboa/extensions/admin/models/__init__.py +++ /dev/null @@ -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' -] diff --git a/modoboa/extensions/admin/models/alias.py b/modoboa/extensions/admin/models/alias.py deleted file mode 100644 index 646dab326..000000000 --- a/modoboa/extensions/admin/models/alias.py +++ /dev/null @@ -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) diff --git a/modoboa/extensions/admin/models/base.py b/modoboa/extensions/admin/models/base.py deleted file mode 100644 index c576bdcad..000000000 --- a/modoboa/extensions/admin/models/base.py +++ /dev/null @@ -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() diff --git a/modoboa/extensions/admin/models/domain.py b/modoboa/extensions/admin/models/domain.py deleted file mode 100644 index 8d996def4..000000000 --- a/modoboa/extensions/admin/models/domain.py +++ /dev/null @@ -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) diff --git a/modoboa/extensions/admin/models/domain_alias.py b/modoboa/extensions/admin/models/domain_alias.py deleted file mode 100644 index ba784a3cb..000000000 --- a/modoboa/extensions/admin/models/domain_alias.py +++ /dev/null @@ -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) diff --git a/modoboa/extensions/admin/models/mailbox.py b/modoboa/extensions/admin/models/mailbox.py deleted file mode 100644 index fcc46e52c..000000000 --- a/modoboa/extensions/admin/models/mailbox.py +++ /dev/null @@ -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 diff --git a/modoboa/extensions/admin/modo_extension.py b/modoboa/extensions/admin/modo_extension.py deleted file mode 100644 index 15d64de5b..000000000 --- a/modoboa/extensions/admin/modo_extension.py +++ /dev/null @@ -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) diff --git a/modoboa/extensions/admin/static/admin/css/admin.css b/modoboa/extensions/admin/static/admin/css/admin.css deleted file mode 100644 index 2768bf843..000000000 --- a/modoboa/extensions/admin/static/admin/css/admin.css +++ /dev/null @@ -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; -} diff --git a/modoboa/extensions/admin/static/admin/css/settings.css b/modoboa/extensions/admin/static/admin/css/settings.css deleted file mode 100644 index 24fc269b9..000000000 --- a/modoboa/extensions/admin/static/admin/css/settings.css +++ /dev/null @@ -1,9 +0,0 @@ - -#settings_content .form-horizontal .control-label { - margin-left: 20px; - text-align: left; -} - -#settings_content > .form-horizontal .controls { - margin-left: 240px; -} \ No newline at end of file diff --git a/modoboa/extensions/admin/static/admin/js/admin.js b/modoboa/extensions/admin/static/admin/js/admin.js deleted file mode 100644 index 85eb24af9..000000000 --- a/modoboa/extensions/admin/static/admin/js/admin.js +++ /dev/null @@ -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('
| {% trans "Type" %} | -{% trans "Quantity" %} | -
|---|---|
| {% trans "Domains" %} | {{ domains_counter }} | -
| {% trans "Domain aliases" %} | {{ domain_aliases_counter }} | -
| {% trans "Identities" %} | {{ identities_counter }} | -
| {% trans "Domain" %} | -{% trans "Identities" %} | -
|---|---|
| {{ domain.name }} | {{ domain.identities_count }} | -
| {% trans "Username" %} | {% trans "Name" %} | |
|---|---|---|
| {{ da.username }} | -{{ da.fullname }} | -{% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %} | -
The first element of each line is mandatory and must be equal to one of the previous values.
-You can use a different character as separator.
-""" % ''.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: -The first element of each line is mandatory and must be equal to one of the previous values.
- -You can use a different character as separator.
-""") - 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) diff --git a/modoboa/extensions/admin/views/user.py b/modoboa/extensions/admin/views/user.py deleted file mode 100644 index 55166166c..000000000 --- a/modoboa/extensions/admin/views/user.py +++ /dev/null @@ -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 - }) - }) diff --git a/modoboa/lib/web_utils.py b/modoboa/lib/web_utils.py index 947c32503..f79484023 100644 --- a/modoboa/lib/web_utils.py +++ b/modoboa/lib/web_utils.py @@ -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)