* Improved parameters management (see #392)

* Supporting mailboxes rename and removal only with Dovecot 2.x (see #389)
This commit is contained in:
Antoine Nguyen
2013-04-06 16:19:48 +02:00
parent d64a61ac55
commit 679cda71af
44 changed files with 1176 additions and 692 deletions
+93 -97
View File
@@ -1,85 +1,10 @@
# coding: utf-8
from modoboa.lib import parameters, events
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.db.utils import DatabaseError
from models import Extension, Mailbox
def update_uid(value):
"""VIRTUAL_UID modification callback
Update all mailboxes with the new UID value
:param value: the new UID
"""
Mailbox.objects.all().update(uid=Mailbox.resolve_uid(value))
def update_gid(value):
"""VIRTUAL_GID modification callback
Update all mailboxes with the new GID value
:param value: the new GID
"""
Mailbox.objects.all().update(gid=Mailbox.resolve_gid(value))
parameters.register_admin(
"AUTHENTICATION_TYPE", type="list", deflt="local",
values=[('local', ugettext_lazy("Local")),
('ldap', "LDAP")],
help=ugettext_lazy("The backend used for authentication")
)
parameters.register_admin(
"LDA", type="list", deflt="dovecot",
values=[('dovecot', "Dovecot"), ('postfix', 'Postfix')],
help=ugettext_lazy("Local Delivery Agent")
)
parameters.register_admin(
"STORAGE_PATH", type="string", deflt="/var/vmail",
visible_if="LDA=postfix",
help=ugettext_lazy("Path to the root directory where messages are stored")
)
parameters.register_admin(
"VIRTUAL_UID", type="string", deflt="vmail", modify_cb=update_uid,
visible_if="LDA=postfix",
help=ugettext_lazy("UID of the virtual user which owns domains/mailboxes/messages on the filesystem")
)
parameters.register_admin(
"VIRTUAL_GID", type="string", deflt="vmail", modify_cb=update_gid,
visible_if="LDA=postfix",
help=ugettext_lazy("GID of the virtual user which owns domains/mailboxes/messages on the filesystem")
)
parameters.register_admin(
"MAILBOX_TYPE", type="list", deflt="maildir",
values=[("maildir", "maildir"), ("mbox", "mbox")],
visible_if="LDA=postfix",
help=ugettext_lazy("Mailboxes storage format")
)
parameters.register_admin(
"MAILDIR_ROOT", type="string", deflt=".maildir",
visible_if="MAILBOX_TYPE=maildir",
help=ugettext_lazy("Sub-directory (inside the mailbox) where messages are stored when using the maildir format")
)
parameters.register_admin(
"AUTO_ACCOUNT_REMOVAL", type="list_yesno", deflt="no",
help=ugettext_lazy("When a mailbox is removed, also remove the associated account")
)
parameters.register_admin(
"PASSWORD_SCHEME", type="list", deflt="md5crypt",
values=[("crypt", "crypt"),
("md5", "md5"),
("md5crypt", "md5crypt"),
("sha256", "sha256"),
("plain", "plain")],
help=ugettext_lazy("Scheme used to crypt mailbox passwords")
)
parameters.register_admin(
"ITEMS_PER_PAGE", type="int", deflt=30,
help=ugettext_lazy("Number of displayed items per page")
)
from modoboa.lib import parameters, events
from modoboa.lib.formutils import YesNoField, SeparatorField
from modoboa.lib.sysutils import exec_cmd
def enabled_applications():
@@ -91,6 +16,7 @@ def enabled_applications():
:return: a list
"""
from modoboa.admin.models import Extension
from modoboa.lib.dbutils import db_table_exists
result = [("admin", "admin"), ("userprefs", "userprefs")]
@@ -100,12 +26,92 @@ def enabled_applications():
return sorted(result, key=lambda e: e[0])
parameters.register_admin(
"DEFAULT_TOP_REDIRECTION", type="list", deflt="admin",
app="general",
values=enabled_applications(),
help=ugettext_lazy("The default redirection used when no application is specified")
)
class GeneralParametersForm(parameters.AdminParametersForm):
app = "admin"
sep1 = SeparatorField(label=ugettext_lazy("Authentication"))
authentication_type = forms.ChoiceField(
label=ugettext_lazy("Authentication type"),
choices=[('local', ugettext_lazy("Local")),
('ldap', "LDAP")],
initial="local",
help_text=ugettext_lazy("The backend used for authentication")
)
password_scheme = forms.ChoiceField(
label=ugettext_lazy("Default password scheme"),
choices=[("crypt", "crypt"),
("md5", "md5"),
("md5crypt", "md5crypt"),
("sha256", "sha256"),
("plain", "plain")],
initial="md5crypt",
help_text=ugettext_lazy("Scheme used to crypt mailbox passwords")
)
secret_key = forms.CharField(
label=ugettext_lazy("Secret key"),
initial="abcdefghijklmnop",
help_text=ugettext_lazy("Key used to encrypt/decrypt passwords")
)
sep2 = 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 ower"),
initial="vmail",
help_text=ugettext_lazy("The UNIX account who owns mailboxes on the filesystem")
)
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"),
)
sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous"))
items_per_page = forms.IntegerField(
label=ugettext_lazy("Items per page"),
initial=30,
help_text=ugettext_lazy("Number of displayed items per page")
)
default_top_redirection = forms.ChoiceField(
label=ugettext_lazy("Default top redirection"),
choices=[],
initial="admin",
help_text=ugettext_lazy("The default redirection used when no application is specified")
)
def __init__(self, *args, **kwargs):
super(GeneralParametersForm, self).__init__(*args, **kwargs)
self.fields["default_top_redirection"].choices = enabled_applications()
hide_fields = False
try:
code, version = exec_cmd("dovecot --version")
except OSError, e:
hide_fields = True
else:
if code or not version.strip().startswith("2"):
hide_fields = True
if hide_fields:
del self.fields["handle_mailboxes"]
del self.fields["mailboxes_owner"]
# Visibility rules
def visibility_mailboxes_owner(self):
return "handle_mailboxes=yes"
parameters.register(GeneralParametersForm, ugettext_lazy("General"))
@events.observe("ExtDisabled")
@@ -114,16 +120,6 @@ def unset_default_topredirection(extension):
Simple callback to change the default redirection if the
corresponding extension is being disabled.
"""
topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION", app="general")
topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION")
if topredirection == extension.name:
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "userprefs", app="general")
@events.observe("ExtDisabled", "ExtEnabled")
def update_available_applications(extension):
"""Simple callback to update the list of available applications
Must be called each time an extension is disabled/enabled.
"""
parameters.update_admin("DEFAULT_TOP_REDIRECTION", app="general",
values=enabled_applications())
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "userprefs")
+1 -1
View File
@@ -1 +1 @@
[{"pk": 2, "model": "admin.user", "fields": {"username": "user@test.com", "is_active": "True", "is_superuser": "False", "password": "{PLAIN}toto", "groups": [2]}},{"pk": 3, "model": "admin.user", "fields": {"username": "admin@test.com", "is_active": "True", "is_superuser": "False", "password": "{PLAIN}toto", "groups": [1]}},{"pk": 1, "model": "admin.ObjectDates", "fields": {"creation": "2011-09-07 09:27:07.579603", "last_modification": "2011-09-07 09:27:07.579603"}}, {"pk": 2, "model": "admin.ObjectDates", "fields": {"creation": "2011-09-07 09:27:07.579603", "last_modification": "2011-09-07 09:27:07.579603"}}, {"pk": 1, "model": "admin.Domain", "fields": {"name" : "test.com", "quota" : "100", "dates": "1"}}, {"pk": 1, "model": "admin.Mailbox", "fields": {"address": "user", "domain": "1", "dates": "1", "uid": "1001", "gid": "1001", "path": "user", "quota": "10", "user": "2"}}, {"pk": 2, "model": "admin.Mailbox", "fields": {"address": "admin", "domain": "1", "dates": "2", "uid": "1001", "gid": "1001", "path": "admin", "quota": "10", "user": "3"}}]
[{"pk": 2, "model": "admin.user", "fields": {"username": "user@test.com", "is_active": "True", "is_superuser": "False", "password": "{PLAIN}toto", "groups": [2]}},{"pk": 3, "model": "admin.user", "fields": {"username": "admin@test.com", "is_active": "True", "is_superuser": "False", "password": "{PLAIN}toto", "groups": [1]}},{"pk": 1, "model": "admin.ObjectDates", "fields": {"creation": "2011-09-07 09:27:07.579603", "last_modification": "2011-09-07 09:27:07.579603"}}, {"pk": 2, "model": "admin.ObjectDates", "fields": {"creation": "2011-09-07 09:27:07.579603", "last_modification": "2011-09-07 09:27:07.579603"}}, {"pk": 1, "model": "admin.Domain", "fields": {"name" : "test.com", "quota" : "100", "dates": "1"}}, {"pk": 1, "model": "admin.Mailbox", "fields": {"address": "user", "domain": "1", "dates": "1", "quota": "10", "user": "2"}}, {"pk": 2, "model": "admin.Mailbox", "fields": {"address": "admin", "domain": "1", "dates": "2", "quota": "10", "user": "3"}}]
+21 -8
View File
@@ -75,10 +75,15 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
def save(self, user, commit=True):
d = super(DomainFormGeneral, self).save(commit=False)
if commit:
if self.oldname is not None and d.name != self.oldname:
if not d.rename_dir(self.oldname):
raise AdminError(_("Failed to rename domain, check permissions"))
old_mail_homes = None
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm == "yes":
if self.oldname is not None and d.name != self.oldname:
old_mail_homes = {mb.id: mb.mail_home for mb in d.mailbox_set.all()}
d.save()
if old_mail_homes is not None:
for mb in d.mailbox_set.all():
mb.rename_dir(old_mail_homes[mb.id])
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
@@ -561,14 +566,22 @@ class AccountFormMail(forms.Form, DynamicForm):
grant_access_to_object(admin, self.mb.user)
else:
old_mail_home = None
if self.cleaned_data["email"] != self.mb.full_address:
if self.mb.rename_dir(domname, locpart):
self.mb.domain = domain
self.mb.address = locpart
else:
raise AdminError(_("Failed to rename mailbox, check permissions"))
old_mail_home = self.mb.mail_home
self.mb.domain = domain
self.mb.address = locpart
self.mb.set_quota(self.cleaned_data["quota"], user.has_perm("admin.add_domain"))
self.mb.save()
if old_mail_home is not None:
# Problem here: the mailbox has already been saved so,
# if the rename fails, we can't rollback!
# Maybe django-reversion could be the solution?
try:
self.mb.rename_dir(old_mail_home)
except AdminError:
# do rollback
pass
account.email = self.cleaned_data["email"]
account.save()
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Mailbox.uid'
db.delete_column('admin_mailbox', 'uid')
# Deleting field 'Mailbox.gid'
db.delete_column('admin_mailbox', 'gid')
# Deleting field 'Mailbox.path'
db.delete_column('admin_mailbox', 'path')
def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'Mailbox.uid'
raise RuntimeError("Cannot reverse this migration. 'Mailbox.uid' and its values cannot be restored.")
# User chose to not deal with backwards NULL issues for 'Mailbox.gid'
raise RuntimeError("Cannot reverse this migration. 'Mailbox.gid' and its values cannot be restored.")
# User chose to not deal with backwards NULL issues for 'Mailbox.path'
raise RuntimeError("Cannot reverse this migration. 'Mailbox.path' and its values cannot be restored.")
models = {
'admin.alias': {
'Meta': {'ordering': "['domain__name', 'address']", 'unique_together': "(('address', 'domain'),)", 'object_name': 'Alias'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'})
},
'admin.domain': {
'Meta': {'ordering': "['name']", 'object_name': 'Domain'},
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'quota': ('django.db.models.fields.IntegerField', [], {})
},
'admin.domainalias': {
'Meta': {'object_name': 'DomainAlias'},
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"})
},
'admin.extension': {
'Meta': {'object_name': 'Extension'},
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
},
'admin.mailbox': {
'Meta': {'object_name': 'Mailbox'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'quota': ('django.db.models.fields.IntegerField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'admin.objectaccess': {
'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'admin.objectdates': {
'Meta': {'object_name': 'ObjectDates'},
'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['admin']
+44 -118
View File
@@ -547,27 +547,6 @@ class Domain(DatesAware):
@property
def admins(self):
return [oa.user for oa in self.owners.filter(user__is_superuser=False)]
def create_dir(self):
if parameters.get_admin("LDA") == "postfix":
path = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), self.name)
return exec_as_vuser("mkdir -p %s" % path)
return True
def rename_dir(self, oldname):
if parameters.get_admin("LDA") == "postfix":
stpath = parameters.get_admin("STORAGE_PATH")
return exec_as_vuser("mv %s/%s %s/%s" \
% (stpath, oldname, stpath, self.name))
return True
def delete_dir(self):
if parameters.get_admin("LDA") == "postfix":
return exec_as_vuser("rm -r %s/%s" \
% (parameters.get_admin("STORAGE_PATH"),
self.name))
return True
def delete(self, fromuser, keepdir=False):
from modoboa.lib.permissions import ungrant_access_to_object, ungrant_access_to_objects
@@ -578,6 +557,10 @@ class Domain(DatesAware):
if self.mailbox_set.count():
events.raiseEvent("DeleteMailbox", self.mailbox_set.all())
ungrant_access_to_objects(self.mailbox_set.all())
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm == "yes" and not keepdir:
for mb in self.mailbox_set.all():
mb.delete_dir()
if self.alias_set.count():
events.raiseEvent("MailboxAliasDelete", self.alias_set.all())
ungrant_access_to_objects(self.alias_set.all())
@@ -587,13 +570,6 @@ class Domain(DatesAware):
events.raiseEvent("DeleteDomain", self)
ungrant_access_to_object(self)
super(Domain, self).delete()
if not keepdir:
self.delete_dir()
def save(self, *args, **kwargs):
if not self.create_dir():
raise AdminError(_("Failed to initialise domain, check permissions"))
super(Domain, self).save(*args, **kwargs)
def __str__(self):
return self.name
@@ -665,30 +641,27 @@ class Mailbox(DatesAware):
help_text=ugettext_lazy("Mailbox address (without the @domain.tld part)")
)
quota = models.IntegerField()
uid = models.IntegerField()
gid = models.IntegerField()
path = models.CharField(max_length=255)
domain = models.ForeignKey(Domain)
user = models.ForeignKey(User)
class Meta:
permissions = (
("view_mailboxes", "View mailboxes"),
)
)
def __init__(self, *args, **kwargs):
super(Mailbox, self).__init__(*args, **kwargs)
self.mbtype = parameters.get_admin("MAILBOX_TYPE")
self.mdirroot = parameters.get_admin("MAILDIR_ROOT")
if not self.mdirroot.endswith('/'):
self.mdirroot += '/'
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 "%s@%s" % (self.address, self.domain.name)
return self.__full_address(self.address)
@property
def name_and_address(self):
@@ -701,92 +674,48 @@ class Mailbox(DatesAware):
def enabled(self):
return self.user.is_active
@property
def full_path(self):
path = os.path.join(parameters.get_admin("STORAGE_PATH"),
self.domain.name, self.address)
return os.path.abspath(path)
@property
def alias_count(self):
return self.alias_set.count()
def create_dir(self):
if self.mbtype == "mbox":
self.path = "%s/Inbox" % self.address
else:
self.path = "%s/" % self.address
if parameters.get_admin("LDA") == "postfix":
relpath = "%s/%s" % (self.domain.name, self.address)
abspath = os.path.join(parameters.get_admin("STORAGE_PATH"), relpath)
if os.path.exists(abspath):
return True
if not exec_as_vuser("mkdir -p %s" % abspath):
return False
if self.mbtype == "mbox":
template = ["Inbox", "Drafts", "Sent", "Trash", "Junk"]
for f in template:
exec_as_vuser("touch %s/%s" % (abspath, f))
else:
template = [".Drafts/", ".Sent/", ".Trash/", ".Junk/"]
for dir in template:
for sdir in ["cur", "new", "tmp"]:
exec_as_vuser("mkdir -p %s/%s/%s/%s" \
% (abspath, self.mdirroot, dir, sdir))
return True
@property
def mail_home(self):
"""
def rename_dir(self, newdomain, newaddress):
if parameters.get_admin("LDA") == "postfix":
if self.domain.name == newdomain and newaddress == self.address:
return True
oldpath = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), self.domain.name)
newpath = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), newdomain)
oldpath = os.path.join(oldpath, self.address)
newpath = os.path.join(newpath, newaddress)
code = exec_as_vuser("mv %s %s" % (oldpath, newpath))
"""
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return None
if self.__mail_home is None:
code, output = exec_cmd("doveadm user %s -f home" % self.full_address,
sudo_user=parameters.get_admin("MAILBOXES_OWNER"))
if code:
self.path = "%s/" % newaddress
return code
else:
self.path ="%s/" % newaddress
raise AdminError(_("Failed to retrieve mailbox location"))
self.__mail_home = output.strip()
return self.__mail_home
return True
def rename_dir(self, old_mail_home):
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return
self.__mail_home = None
if not os.path.exists(old_mail_home):
return
code, output = exec_cmd("mv %s %s" % (old_mail_home, self.mail_home),
sudo_user=parameters.get_admin("MAILBOXES_OWNER"))
if code:
raise AdminError(_("Failed to rename mailbox: %s" % output))
def delete_dir(self):
if parameters.get_admin("LDA") == "postfix":
return exec_as_vuser("rm -r %s/%s/%s" \
% (parameters.get_admin("STORAGE_PATH"),
self.domain.name, self.address))
return True
@staticmethod
def resolve_id(value, idname, errmsg):
if value.isdigit():
return value
try:
rid = getattr(pwd.getpwnam(value), idname)
except KeyError:
raise AdminError(errmsg)
return rid
@staticmethod
def resolve_uid(value):
err = _("%s is not a valid uid/username" % value)
return Mailbox.resolve_id(value, "pw_uid", err)
@staticmethod
def resolve_gid(value):
err = _("%s is not a valid gid/groupname" % value)
return Mailbox.resolve_id(value, "pw_gid", err)
def set_ownership(self):
"""Set uid and gid for this mailbox
Values come from global parameters. They are resolved
(converted to integers) if needed.
"""
self.uid = self.resolve_uid(parameters.get_admin("VIRTUAL_UID"))
self.gid = self.resolve_gid(parameters.get_admin("VIRTUAL_GID"))
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return
if not os.path.exists(self.mail_home):
return
code, output = exec_cmd("rm -r %s" % self.mail_home,
sudo_user=parameters.get_admin("MAILBOXES_OWNER"))
if code:
raise AdminError(_("Failed to remove mailbox: %s" % output))
def set_quota(self, value, override_domain=False):
if value is None or (int(value) > self.domain.quota and not override_domain):
@@ -804,9 +733,6 @@ class Mailbox(DatesAware):
self.address = localpart
self.domain = domain
self.user = user
if not self.create_dir():
raise AdminError(_("Failed to initialise mailbox, check permissions"))
self.set_ownership()
self.set_quota(quota, True if (owner and owner.has_perm("admin.add_domain")) else False)
super(Mailbox, self).save()
+1 -1
View File
@@ -22,4 +22,4 @@ table td[name="actions"] {
width: 85%;
-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;
padding-left: 14px;
}
}
@@ -0,0 +1,10 @@
#settings_content .form-horizontal .control-label {
margin-left: 20px;
text-align: left;
width: 200px;
}
#settings_content > .form-horizontal .controls {
margin-left: 240px;
}
@@ -1,4 +1,4 @@
{% load i18n %}{% load url from future %}
{% load i18n libextras %}{% load url from future %}
{% url "modoboa.admin.views.saveparameters" as action %}
<h2>{% trans "Parameters" %} <small>{% trans "Configure Modoboa" %}</small></h2><hr />
<form id="settings_form" method="POST" action="{% url 'modoboa.admin.views.saveparameters' %}" class="form-horizontal">
@@ -2,6 +2,7 @@
{% load i18n libextras admin_extras %}
{% block pagetitle %}{% trans "Modoboa settings" %}{% endblock %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/settings.css" />
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/twocols_nav.js"></script>
<script type="text/javascript">
+9 -3
View File
@@ -224,6 +224,9 @@ def param(app, definition):
value = definition.has_key("value") \
and definition["value"] or definition["deflt"]
if definition["type"] == "separator":
return "<h5>%s</h5>" % (unicode(definition["label"]))
extratags = ""
if "visible_if" in definition:
vfield, vvalue = definition["visible_if"].split("=")
@@ -270,9 +273,12 @@ def param(app, definition):
"""
if definition.has_key("help"):
result += """
<span class="help-block">%s</span>
""" % _(definition["help"])
result += """<a class="help" rel="popover" data-content="%s" data-original-title="%s">
<i class="icon-info-sign"></i>
</a>""" % (unicode(definition["help"]), _("Help"))
# result += """
# <span class="help-block">%s</span>
# """ % _(definition["help"])
result += """
</div>
+13 -13
View File
@@ -3,8 +3,13 @@
Basic tests for the 'admin' application
=======================================
TODO:
* Add new tests to check domain/mailbox manipulations when
create_directories == yes
"""
import os
from django.test import TestCase
from django.test.client import Client
from django.utils import simplejson
@@ -14,7 +19,6 @@ from modoboa.admin.lib import *
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
import os
class DomainTestCase(TestCase):
fixtures = ["initial_users.json"]
@@ -26,18 +30,12 @@ class DomainTestCase(TestCase):
self.assertEqual(dom.quota, 100)
self.assertEqual(dom.enabled, False)
self.assertEqual(dom.create_dir(), True)
path = os.path.join(parameters.get_admin("STORAGE_PATH"), dom.name)
self.assertEqual(os.path.exists(path), True)
dom.name = "toto.com"
dom.save()
self.assertEqual(dom.name, "toto.com")
path = os.path.join(parameters.get_admin("STORAGE_PATH"), dom.name)
self.assertEqual(os.path.exists(path), True)
dom.delete(User.objects.get(pk=1))
self.assertEqual(os.path.exists(path), False)
class DomainAliasTestCase(ModoTestCase):
fixtures = ["initial_users.json", "test_content.json"]
@@ -68,9 +66,14 @@ class DomainAliasTestCase(ModoTestCase):
self.check_ajax_post(reverse("modoboa.admin.views.editdomain", args=[1]), values)
self.assertEqual(dom.domainalias_set.count(), 1)
class AccountTestCase(ModoTestCase):
fixtures = ["initial_users.json", "test_content.json",]
def setUp(self):
super(AccountTestCase, self).setUp()
from modoboa import admin
def test(self):
values = dict(username="tester@test.com", first_name="Tester", last_name="Toto",
password1="toto", password2="toto", role="SimpleUsers",
@@ -80,7 +83,6 @@ class AccountTestCase(ModoTestCase):
account = User.objects.get(username="tester@test.com")
mb = account.mailbox_set.all()[0]
self.assertEqual(mb.full_address, "tester@test.com")
self.assertEqual(os.path.exists(mb.full_path), True)
self.assertEqual(mb.quota, 100)
self.assertEqual(mb.enabled, True)
self.assertEqual(account.username, mb.full_address)
@@ -93,14 +95,10 @@ class AccountTestCase(ModoTestCase):
self.check_ajax_post(reverse("modoboa.admin.views.editaccount", args=[account.id]), values)
mb = Mailbox.objects.get(pk=mb.id)
self.assertEqual(mb.path, "%s/" % mb.address)
path = mb.full_path
self.assertEqual(mb.full_address, "pouet@test.com")
self.assertEqual(os.path.exists(path), True)
self.check_ajax_get(reverse("modoboa.admin.views.delaccount") + "?selection=%d" \
% account.id, {})
self.assertEqual(os.path.exists(path), False)
def test_update_password(self):
"""Password update
@@ -124,6 +122,7 @@ class AccountTestCase(ModoTestCase):
self.clt.logout()
self.assertEqual(self.clt.login(username="user@test.com", password="tutu"), True)
class AliasTestCase(ModoTestCase):
fixtures = ["initial_users.json", "test_content.json"]
@@ -181,6 +180,7 @@ class AliasTestCase(ModoTestCase):
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
address="forward", domain__name="test.com")
class PermissionsTestCase(ModoTestCase):
fixtures = ["initial_users.json", "test_content.json"]
+7 -6
View File
@@ -481,17 +481,18 @@ def viewparameters(request, tplname='admin/parameters.html'):
return ajax_simple_response({
"status" : "ok",
"left_selection" : "parameters",
"content" : render_to_string(tplname, {"gparams" : parameters.get_all_admin_parameters()})
"content" : render_to_string(tplname, {"forms" : parameters.get_admin_forms})
})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def saveparameters(request):
for pname, v in request.POST.iteritems():
if pname == "update":
for formdef in parameters.get_admin_forms(request.POST):
form = formdef["form"]
if form.is_valid():
form.save()
continue
app, name = pname.split('.')
parameters.save_admin(name, v, app=app)
return ajax_simple_response({"status": "ko", "prefix": form.app, "errors": form.errors})
return ajax_simple_response(dict(status="ok", respmsg=_("Parameters saved")))
@login_required
@@ -510,7 +511,7 @@ def viewextensions(request, tplname='admin/extensions.html'):
dbext.enabled = False
dbext.save()
ext["selection"] = False
tbl = ExtensionsTable(request, exts)
return ajax_simple_response({
"status" : "ok",