mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
* Improved parameters management (see #392)
* Supporting mailboxes rename and removal only with Dovecot 2.x (see #389)
This commit is contained in:
+93
-97
@@ -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 @@
|
||||
[{"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
@@ -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()
|
||||
|
||||
+126
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# coding: utf-8
|
||||
from modoboa.lib import parameters
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
parameters.register_admin("SECRET_KEY", type="string", deflt="abcdefghijklmnop",
|
||||
help=ugettext_lazy("Key used to encrypt/decrypt passwords"))
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import base64
|
||||
from modoboa.lib import parameters
|
||||
|
||||
def encrypt(clear):
|
||||
key = parameters.get_admin("SECRET_KEY")
|
||||
key = parameters.get_admin("SECRET_KEY", app="admin")
|
||||
obj = AES.new(key, AES.MODE_ECB)
|
||||
clear = str(clear)
|
||||
if len(clear) % AES.block_size:
|
||||
@@ -14,7 +14,7 @@ def encrypt(clear):
|
||||
return ciph
|
||||
|
||||
def decrypt(ciph):
|
||||
obj = AES.new(parameters.get_admin("SECRET_KEY"), AES.MODE_ECB)
|
||||
obj = AES.new(parameters.get_admin("SECRET_KEY", app="admin"), AES.MODE_ECB)
|
||||
ciph = base64.b64decode(ciph)
|
||||
clear = obj.decrypt(ciph)
|
||||
return clear.rstrip(' ')
|
||||
|
||||
@@ -40,50 +40,13 @@ class Amavis(ModoExtension):
|
||||
priority=7, policy=p)
|
||||
|
||||
def load(self):
|
||||
parameters.register_admin(
|
||||
"MAX_MESSAGES_AGE", type="int",
|
||||
deflt=14,
|
||||
help=_("Quarantine messages maximum age (in days) before deletion")
|
||||
)
|
||||
parameters.register_admin(
|
||||
"RELEASED_MSGS_CLEANUP", type="list_yesno", deflt="no",
|
||||
help=_("Remove messages marked as released while cleaning up the database")
|
||||
)
|
||||
parameters.register_admin("AM_PDP_MODE", type="list",
|
||||
deflt="unix",
|
||||
values=[("inet", "inet"), ("unix", "unix")],
|
||||
help=ugettext_lazy("Mode used to access the PDP server"))
|
||||
parameters.register_admin("AM_PDP_HOST", type="string",
|
||||
deflt="localhost",
|
||||
visible_if="AM_PDP_MODE=inet",
|
||||
help=ugettext_lazy("PDP server address (if inet mode)"))
|
||||
parameters.register_admin("AM_PDP_PORT", type="int",
|
||||
deflt=9998,
|
||||
visible_if="AM_PDP_MODE=inet",
|
||||
help=ugettext_lazy("PDP server port (if inet mode)"))
|
||||
parameters.register_admin("AM_PDP_SOCKET", type="string",
|
||||
deflt="/var/amavis/amavisd.sock",
|
||||
visible_if="AM_PDP_MODE=unix",
|
||||
help=ugettext_lazy("Path to the PDP server socket (if unix mode)"))
|
||||
parameters.register_admin("CHECK_REQUESTS_INTERVAL", type="int",
|
||||
deflt=30,
|
||||
help=ugettext_lazy("Interval between two release requests checks"))
|
||||
parameters.register_admin("USER_CAN_RELEASE", type="list_yesno", deflt="no",
|
||||
help=ugettext_lazy("Allow users to directly release their messages"))
|
||||
parameters.register_admin("SELF_SERVICE", type="list_yesno", deflt="no",
|
||||
help=ugettext_lazy("Activate the 'self-service' mode"))
|
||||
parameters.register_admin("NOTIFICATIONS_SENDER", type="string", deflt="notification@modoboa.org",
|
||||
help=ugettext_lazy("The e-mail address used to send notitications"))
|
||||
|
||||
parameters.register_user(
|
||||
"MESSAGES_PER_PAGE", type="int", deflt=40,
|
||||
label="Number of displayed emails per page",
|
||||
help=ugettext_lazy("Sets the maximum number of messages displayed in a page")
|
||||
)
|
||||
|
||||
from app_settings import ParametersForm, UserSettings
|
||||
parameters.register(ParametersForm, "Amavis")
|
||||
parameters.register(UserSettings, _("Quarantine"))
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
parameters.unregister_app("amavis")
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(Amavis)
|
||||
|
||||
|
||||
@@ -1,35 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django import forms
|
||||
from models import Policy, Users
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.forms.widgets import RadioSelect, RadioInput
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
class CustomRadioInput(RadioInput):
|
||||
def __unicode__(self):
|
||||
if 'id' in self.attrs:
|
||||
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
|
||||
else:
|
||||
label_for = ''
|
||||
choice_label = conditional_escape(force_unicode(self.choice_label))
|
||||
return mark_safe(u'<label class="radio inline" %s>%s %s</label>' % (label_for, self.tag(), choice_label))
|
||||
|
||||
|
||||
class InlineRadioRenderer(RadioSelect.renderer):
|
||||
def __iter__(self):
|
||||
for i, choice in enumerate(self.choices):
|
||||
yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)
|
||||
|
||||
def render(self):
|
||||
return mark_safe(u'\n'.join([u'%s\n' % force_unicode(w) for w in self]))
|
||||
|
||||
|
||||
class InlineRadioSelect(RadioSelect):
|
||||
renderer = InlineRadioRenderer
|
||||
|
||||
|
||||
class DomainPolicyForm(forms.ModelForm):
|
||||
|
||||
@@ -12,6 +12,7 @@ from modoboa.lib import events, parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
from models import *
|
||||
|
||||
|
||||
class PostfixAutoreply(ModoExtension):
|
||||
name = "postfix_autoreply"
|
||||
label = "Postfix autoreply"
|
||||
@@ -20,7 +21,7 @@ class PostfixAutoreply(ModoExtension):
|
||||
|
||||
def init(self):
|
||||
from modoboa.admin.models import Domain
|
||||
|
||||
|
||||
for dom in Domain.objects.all():
|
||||
try:
|
||||
trans = Transport.objects.get(domain="autoreply.%s" % dom.name)
|
||||
@@ -36,11 +37,8 @@ class PostfixAutoreply(ModoExtension):
|
||||
onCreateMailbox(None, mb)
|
||||
|
||||
def load(self):
|
||||
parameters.register_admin(
|
||||
"AUTOREPLIES_TIMEOUT",
|
||||
type="int", deflt=86400,
|
||||
help=ugettext_lazy("Timeout in seconds between two auto-replies to the same recipient")
|
||||
)
|
||||
from app_settings import ParametersForm
|
||||
parameters.register(ParametersForm, _("Automatic replies"))
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("CreateDomain", onCreateDomain)
|
||||
@@ -49,14 +47,16 @@ class PostfixAutoreply(ModoExtension):
|
||||
events.unregister("DeleteMailbox", onDeleteMailbox)
|
||||
events.unregister("ModifyMailbox", onModifyMailbox)
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
parameters.unregister_app("postfix_autoreply")
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(PostfixAutoreply)
|
||||
|
||||
|
||||
@events.observe("ExtraUprefsRoutes")
|
||||
def extra_routes():
|
||||
return [(r'^autoreply/$', 'modoboa.extensions.postfix_autoreply.views.autoreply'),]
|
||||
|
||||
|
||||
@events.observe("ExtraUprefsJS")
|
||||
def extra_js():
|
||||
return ["""function autoreply_cb() {
|
||||
@@ -64,20 +64,20 @@ def extra_js():
|
||||
}
|
||||
""" % 'en']
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
import views
|
||||
|
||||
if target != "uprefs_menu":
|
||||
return []
|
||||
if not user.has_mailbox:
|
||||
return []
|
||||
return [
|
||||
{"name" : "autoreply",
|
||||
"class" : "ajaxlink",
|
||||
"url" : "autoreply/",
|
||||
"label" : ugettext_lazy("Auto-reply message")}
|
||||
]
|
||||
{"name": "autoreply",
|
||||
"class": "ajaxlink",
|
||||
"url": "autoreply/",
|
||||
"label": ugettext_lazy("Auto-reply message")}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("CreateDomain")
|
||||
def onCreateDomain(user, domain):
|
||||
@@ -86,11 +86,13 @@ def onCreateDomain(user, domain):
|
||||
transport.method = "autoreply:"
|
||||
transport.save()
|
||||
|
||||
|
||||
@events.observe("DeleteDomain")
|
||||
def onDeleteDomain(domain):
|
||||
trans = Transport.objects.get(domain="autoreply.%s" % domain.name)
|
||||
trans.delete()
|
||||
|
||||
|
||||
@events.observe("CreateMailbox")
|
||||
def onCreateMailbox(user, mailbox):
|
||||
alias = Alias()
|
||||
@@ -99,6 +101,7 @@ def onCreateMailbox(user, mailbox):
|
||||
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
|
||||
alias.save()
|
||||
|
||||
|
||||
@events.observe("DeleteMailbox")
|
||||
def onDeleteMailbox(mailboxes):
|
||||
from modoboa.admin.models import Mailbox
|
||||
@@ -113,6 +116,7 @@ def onDeleteMailbox(mailboxes):
|
||||
else:
|
||||
alias.delete()
|
||||
|
||||
|
||||
@events.observe("ModifyMailbox")
|
||||
def onModifyMailbox(mailbox, oldmailbox):
|
||||
if oldmailbox.full_address == mailbox.full_address:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django import forms
|
||||
from modoboa.lib.parameters import AdminParametersForm
|
||||
|
||||
|
||||
class ParametersForm(AdminParametersForm):
|
||||
app = "postfix_autoreply"
|
||||
|
||||
autoreplies_timeout = forms.IntegerField(
|
||||
label=ugettext_lazy("Automatic reply timeout"),
|
||||
initial=86400,
|
||||
help_text=ugettext_lazy("Timeout in seconds between two auto-replies to the same recipient")
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
from sievelib.managesieve import SUPPORTED_AUTH_MECHS
|
||||
|
||||
|
||||
class SieveFilters(ModoExtension):
|
||||
name = "sievefilters"
|
||||
@@ -13,32 +13,13 @@ class SieveFilters(ModoExtension):
|
||||
url = "sfilters"
|
||||
|
||||
def load(self):
|
||||
parameters.register_app(uparams_opts=dict(needs_mailbox=True))
|
||||
|
||||
parameters.register_admin("SERVER", type="string",
|
||||
deflt="127.0.0.1",
|
||||
help=ugettext_lazy("Address of your MANAGESIEVE server"))
|
||||
parameters.register_admin("PORT", type="int", deflt="2000",
|
||||
help=ugettext_lazy("Listening port of your MANAGESIEVE server"))
|
||||
parameters.register_admin("STARTTLS", type="list_yesno", deflt="no",
|
||||
help=ugettext_lazy("Use the STARTTLS extension"))
|
||||
from app_settings import ParametersForm, UserSettings
|
||||
parameters.register(ParametersForm, _("Sieve filters"))
|
||||
parameters.register(UserSettings, _("Message filters"))
|
||||
|
||||
values = [('AUTO', 'auto')]
|
||||
for m in SUPPORTED_AUTH_MECHS:
|
||||
values += [(m, m.lower())]
|
||||
parameters.register_admin("AUTHENTICATION_MECH", type="list", deflt="auto",
|
||||
values=values,
|
||||
help=ugettext_lazy("Prefered authentication mechanism"))
|
||||
|
||||
# User parameters
|
||||
parameters.register_user("EDITOR_MODE", type="list", deflt="raw",
|
||||
label=ugettext_lazy("Editor mode"),
|
||||
values=[("raw", "raw"), ("gui", "simplified")],
|
||||
help=ugettext_lazy("Select the mode you want the editor to work in"))
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
parameters.unregister_app("sievefilters")
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(SieveFilters)
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django import forms
|
||||
from sievelib.managesieve import SUPPORTED_AUTH_MECHS
|
||||
from modoboa.lib.parameters import AdminParametersForm, UserParametersForm
|
||||
from modoboa.lib.formutils import SeparatorField, YesNoField
|
||||
|
||||
|
||||
def supported_auth_mechs():
|
||||
values = [('AUTO', 'auto')]
|
||||
for m in SUPPORTED_AUTH_MECHS:
|
||||
values += [(m, m.lower())]
|
||||
return values
|
||||
|
||||
|
||||
class ParametersForm(AdminParametersForm):
|
||||
app = "sievefilters"
|
||||
|
||||
sep1 = SeparatorField(label=_("ManageSieve settings"))
|
||||
|
||||
server = forms.CharField(
|
||||
label=_("Server address"),
|
||||
initial="127.0.0.1",
|
||||
help_text=_("Address of your MANAGESIEVE server")
|
||||
)
|
||||
|
||||
port = forms.IntegerField(
|
||||
label=_("Server port"),
|
||||
initial=2000,
|
||||
help_text=_("Listening port of your MANAGESIEVE server")
|
||||
)
|
||||
|
||||
starttls = YesNoField(
|
||||
label=_("Connect using STARTTLS"),
|
||||
initial="no",
|
||||
help_text=_("Use the STARTTLS extension")
|
||||
)
|
||||
|
||||
authentication_mech = forms.ChoiceField(
|
||||
label=_("Authentication mechanism"),
|
||||
choices=supported_auth_mechs(),
|
||||
initial="auto",
|
||||
help_text=_("Prefered authentication mechanism")
|
||||
)
|
||||
|
||||
|
||||
class UserSettings(UserParametersForm):
|
||||
app = "sievefilters"
|
||||
|
||||
editor_mode = forms.ChoiceField(
|
||||
initial="raw",
|
||||
label=_("Editor mode"),
|
||||
choices=[("raw", "raw"), ("gui", "simplified")],
|
||||
help_text=_("Select the mode you want the editor to work in")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def has_access(user):
|
||||
return user.has_mailbox
|
||||
@@ -12,6 +12,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
|
||||
|
||||
class Stats(ModoExtension):
|
||||
name = "stats"
|
||||
label = "Statistics"
|
||||
@@ -20,32 +21,26 @@ class Stats(ModoExtension):
|
||||
needs_media = True
|
||||
|
||||
def load(self):
|
||||
from app_settings import ParametersForm
|
||||
events.registerEvent("GetGraphSets")
|
||||
|
||||
parameters.register_admin("LOGFILE", type="string",
|
||||
deflt="/var/log/mail.log",
|
||||
help=ugettext_lazy("Path to log file used to collect statistics"))
|
||||
parameters.register_admin("RRD_ROOTDIR", type="string",
|
||||
deflt="/tmp/modoboa",
|
||||
help=ugettext_lazy("Path to directory where RRD files are stored"))
|
||||
parameters.register_admin("IMG_ROOTDIR", type="string",
|
||||
deflt="/tmp/modoboa",
|
||||
help=ugettext_lazy("Path to directory where PNG files are stored"))
|
||||
parameters.register(ParametersForm, ugettext_lazy("Graphical statistics"))
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("AdminMenuDisplay", menu)
|
||||
|
||||
exts_pool.register_extension(Stats)
|
||||
|
||||
|
||||
@events.observe("UserMenuDisplay")
|
||||
def menu(target, user):
|
||||
if target != "top_menu" or user.group == "SimpleUsers":
|
||||
return []
|
||||
return [
|
||||
{"name" : "stats",
|
||||
"label" : _("Statistics"),
|
||||
"url" : reverse('fullindex')}
|
||||
]
|
||||
{"name": "stats",
|
||||
"label": _("Statistics"),
|
||||
"url": reverse('fullindex')}
|
||||
]
|
||||
|
||||
|
||||
@events.observe("GetGraphSets")
|
||||
def get_default_graph_sets():
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django import forms
|
||||
from modoboa.lib.parameters import AdminParametersForm
|
||||
|
||||
|
||||
class ParametersForm(AdminParametersForm):
|
||||
app = "stats"
|
||||
|
||||
logfile = forms.CharField(
|
||||
label=ugettext_lazy("Path to the log file"),
|
||||
initial="/var/log/mail.log",
|
||||
help_text=ugettext_lazy("Path to log file used to collect statistics")
|
||||
)
|
||||
|
||||
rrd_rootdir = forms.CharField(
|
||||
label=ugettext_lazy("Directory to store RRD files"),
|
||||
initial="/tmp/modoboa",
|
||||
help_text=ugettext_lazy("Path to directory where RRD files are stored")
|
||||
)
|
||||
|
||||
img_rootdir = forms.CharField(
|
||||
label=ugettext_lazy("Directory to store PNG files"),
|
||||
initial="/tmp/modoboa",
|
||||
help_text=ugettext_lazy("Path to directory where PNG files are stored")
|
||||
)
|
||||
@@ -4,6 +4,7 @@ from django.core.urlresolvers import reverse
|
||||
from modoboa.lib import events, parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
|
||||
|
||||
class Webmail(ModoExtension):
|
||||
name = "webmail"
|
||||
label = "Webmail"
|
||||
@@ -12,67 +13,14 @@ class Webmail(ModoExtension):
|
||||
needs_media = True
|
||||
|
||||
def load(self):
|
||||
parameters.register_app(uparams_opts=dict(needs_mailbox=True))
|
||||
from app_settings import ParametersForm, UserSettings
|
||||
|
||||
parameters.register_admin("IMAP_SERVER", type="string",
|
||||
deflt="127.0.0.1",
|
||||
help=ugettext_lazy("Address of your IMAP server"))
|
||||
parameters.register_admin("IMAP_SECURED", type="list_yesno",
|
||||
deflt="no",
|
||||
help=ugettext_lazy("Use a secured connection to access IMAP server"))
|
||||
parameters.register_admin("IMAP_PORT", type="int", deflt="143",
|
||||
help=ugettext_lazy("Listening port of your IMAP server"))
|
||||
parameters.register_admin("SMTP_SERVER", type="string",
|
||||
deflt="127.0.0.1",
|
||||
help=ugettext_lazy("Address of your SMTP server"))
|
||||
parameters.register_admin("SMTP_SECURED_MODE", type="list",
|
||||
values=[("none", ugettext_lazy("None")),
|
||||
("starttls", "STARTTLS"),
|
||||
("ssl", "SSL/TLS")],
|
||||
deflt="none",
|
||||
help=ugettext_lazy("Use a secured connection to access SMTP server"))
|
||||
parameters.register_admin("SMTP_PORT", type="int", deflt="25",
|
||||
help=ugettext_lazy("Listening port of your SMTP server"))
|
||||
parameters.register_admin("SMTP_AUTHENTICATION", type="list_yesno",
|
||||
deflt="no",
|
||||
help=ugettext_lazy("Server needs authentication"))
|
||||
parameters.register_admin("MAX_ATTACHMENT_SIZE", type="int", deflt=2048,
|
||||
help=ugettext_lazy("Maximum attachment size in bytes (or KB, MB, GB if specified)"))
|
||||
|
||||
parameters.register_user("MESSAGES_PER_PAGE", type="int", deflt=40,
|
||||
label=ugettext_lazy("Number of displayed emails per page"),
|
||||
help=ugettext_lazy("Sets the maximum number of messages displayed in a page"))
|
||||
parameters.register_user("REFRESH_INTERVAL", type="int", deflt=300,
|
||||
label=ugettext_lazy("Listing refresh rate"),
|
||||
help=ugettext_lazy("Automatic folder refresh rate (in seconds)"))
|
||||
parameters.register_user("TRASH_FOLDER", type="string", deflt="Trash",
|
||||
label=ugettext_lazy("Trash folder"),
|
||||
help=ugettext_lazy("Folder where deleted messages go"))
|
||||
parameters.register_user("SENT_FOLDER", type="string", deflt="Sent",
|
||||
label=ugettext_lazy("Sent folder"),
|
||||
help=ugettext_lazy("Folder where copies of sent messages go"))
|
||||
parameters.register_user("DRAFTS_FOLDER", type="string", deflt="Drafts",
|
||||
label=ugettext_lazy("Drafts folder"),
|
||||
help=ugettext_lazy("Folder where drafts go"))
|
||||
parameters.register_user("SIGNATURE", type="text", deflt="",
|
||||
label=ugettext_lazy("Signature text"),
|
||||
help=ugettext_lazy("User defined email signature"))
|
||||
parameters.register_user("EDITOR", type="list", deflt="plain",
|
||||
label=ugettext_lazy("Default editor"),
|
||||
values=[("html", "html"), ("plain", "text")],
|
||||
help=ugettext_lazy("The default editor to use when composing a message"))
|
||||
parameters.register_user("DISPLAYMODE", type="list", deflt="plain",
|
||||
label=ugettext_lazy("Default message display mode"),
|
||||
values=[("html", "html"), ("plain", "text")],
|
||||
help=ugettext_lazy("The default mode used when displaying a message"))
|
||||
parameters.register_user("ENABLE_LINKS", type="list_yesno", deflt="no",
|
||||
label=ugettext_lazy("Enable HTML links display"),
|
||||
visible_if="DISPLAYMODE=html",
|
||||
help=ugettext_lazy("Enable/Disable HTML links display"))
|
||||
parameters.register(ParametersForm, "Webmail")
|
||||
parameters.register(UserSettings, "Webmail")
|
||||
|
||||
def destroy(self):
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
parameters.unregister_app("webmail")
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(Webmail)
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# coding: utf-8
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from modoboa.lib.parameters import AdminParametersForm, UserParametersForm
|
||||
from modoboa.lib.formutils import SeparatorField, YesNoField
|
||||
|
||||
|
||||
class ParametersForm(AdminParametersForm):
|
||||
app = "webmail"
|
||||
|
||||
sep3 = SeparatorField(label=_("General"))
|
||||
|
||||
max_attachment_size = forms.CharField(
|
||||
label=_("Maximum attachment size"),
|
||||
initial="2048",
|
||||
help_text=_("Maximum attachment size in bytes (or KB, MB, GB if specified)")
|
||||
)
|
||||
|
||||
sep1 = SeparatorField(label=_("IMAP settings"))
|
||||
|
||||
imap_server = forms.CharField(
|
||||
label=_("Server address"),
|
||||
initial="127.0.0.1",
|
||||
help_text=_("Address of your IMAP server")
|
||||
)
|
||||
|
||||
imap_secured = YesNoField(
|
||||
label=_("Use a secured connection"),
|
||||
initial="no",
|
||||
help_text=_("Use a secured connection to access IMAP server")
|
||||
)
|
||||
|
||||
imap_port = forms.IntegerField(
|
||||
label=_("Server port"),
|
||||
initial=143,
|
||||
help_text=_("Listening port of your IMAP server")
|
||||
)
|
||||
|
||||
sep2 = SeparatorField(label=_("SMTP settings"))
|
||||
|
||||
smtp_server = forms.CharField(
|
||||
label=_("Server address"),
|
||||
initial="127.0.0.1",
|
||||
help_text=_("Address of your SMTP server")
|
||||
)
|
||||
|
||||
smtp_secured_mode = forms.ChoiceField(
|
||||
label=_("Secured connection mode"),
|
||||
choices=[("none", _("None")),
|
||||
("starttls", "STARTTLS"),
|
||||
("ssl", "SSL/TLS")],
|
||||
initial="none",
|
||||
help_text=_("Use a secured connection to access SMTP server")
|
||||
)
|
||||
|
||||
smtp_port = forms.IntegerField(
|
||||
label=_("Server port"),
|
||||
initial=25,
|
||||
help_text=_("Listening port of your SMTP server")
|
||||
)
|
||||
|
||||
smtp_authentication = YesNoField(
|
||||
label=_("Authentication required"),
|
||||
initial="no",
|
||||
help_text=_("Server needs authentication")
|
||||
)
|
||||
|
||||
|
||||
class UserSettings(UserParametersForm):
|
||||
app = "webmail"
|
||||
|
||||
sep1 = SeparatorField(label=_("Display"))
|
||||
|
||||
displaymode = forms.ChoiceField(
|
||||
initial="plain",
|
||||
label=_("Default message display mode"),
|
||||
choices=[("html", "html"), ("plain", "text")],
|
||||
help_text=_("The default mode used when displaying a message")
|
||||
)
|
||||
|
||||
enable_links = YesNoField(
|
||||
initial="no",
|
||||
label=_("Enable HTML links display"),
|
||||
help_text=_("Enable/Disable HTML links display")
|
||||
)
|
||||
|
||||
messages_per_page = forms.IntegerField(
|
||||
initial=40,
|
||||
label=_("Number of displayed emails per page"),
|
||||
help_text=_("Sets the maximum number of messages displayed in a page")
|
||||
)
|
||||
|
||||
refresh_interval = forms.IntegerField(
|
||||
initial=300,
|
||||
label=_("Listing refresh rate"),
|
||||
help_text=_("Automatic folder refresh rate (in seconds)")
|
||||
)
|
||||
|
||||
sep2 = SeparatorField(label=_("Mailboxes"))
|
||||
|
||||
trash_folder = forms.CharField(
|
||||
initial="Trash",
|
||||
label=_("Trash folder"),
|
||||
help_text=_("Folder where deleted messages go")
|
||||
)
|
||||
|
||||
sent_folder = forms.CharField(
|
||||
initial="Sent",
|
||||
label=_("Sent folder"),
|
||||
help_text=_("Folder where copies of sent messages go")
|
||||
)
|
||||
|
||||
drafts_folder = forms.CharField(
|
||||
initial="Drafts",
|
||||
label=_("Drafts folder"),
|
||||
help_text=_("Folder where drafts go")
|
||||
)
|
||||
|
||||
sep3 = SeparatorField(label=_("Composing messages"))
|
||||
|
||||
editor = forms.ChoiceField(
|
||||
initial="plain",
|
||||
label=_("Default editor"),
|
||||
choices=[("html", "html"), ("plain", "text")],
|
||||
help_text=_("The default editor to use when composing a message")
|
||||
)
|
||||
|
||||
signature = forms.CharField(
|
||||
initial="",
|
||||
label=_("Signature text"),
|
||||
help_text=_("User defined email signature"),
|
||||
widget=forms.widgets.Textarea,
|
||||
required=False
|
||||
)
|
||||
|
||||
def visibility_enable_links(self):
|
||||
return "displaymode=html"
|
||||
|
||||
@staticmethod
|
||||
def has_access(user):
|
||||
return user.has_mailbox
|
||||
@@ -2,6 +2,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class ComposeMailForm(forms.Form):
|
||||
from_ = forms.CharField(label=_("From"))
|
||||
to = forms.CharField(label=_("To"))
|
||||
|
||||
@@ -554,7 +554,7 @@ class IMAPconnector(object):
|
||||
return
|
||||
|
||||
quotadef = data[1][0]
|
||||
m = re.match("[^\s]+ \(STORAGE (\d+) (\d+)\)", quotadef)
|
||||
m = re.search("\(STORAGE (\d+) (\d+)\)", quotadef)
|
||||
if not m:
|
||||
print "Problem while parsing quota def"
|
||||
return
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# coding: utf-8
|
||||
import re
|
||||
from django.utils.translation import ugettext as _
|
||||
from django import forms
|
||||
from django.forms.widgets import MultiWidget, TextInput
|
||||
from django.forms.fields import MultiValueField
|
||||
from django.forms.widgets import RadioSelect, RadioInput
|
||||
from django.forms.fields import CharField, Field, ChoiceField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
@@ -149,7 +148,6 @@ def is_valid_host(host):
|
||||
"""
|
||||
host = host.encode('idna').lower()
|
||||
if not hasattr(is_valid_host, '_re'):
|
||||
import re
|
||||
is_valid_host._re = re.compile(r'^([0-9a-z][-\w]*[0-9a-z]\.)+[a-z0-9\-]{2,15}$')
|
||||
return bool(is_valid_host._re.match(host))
|
||||
|
||||
@@ -157,7 +155,8 @@ def validate_domain_name(value):
|
||||
if not is_valid_host(value):
|
||||
raise ValidationError(_('Enter a valid domain name'), 'invalid')
|
||||
|
||||
class DomainNameField(forms.CharField):
|
||||
|
||||
class DomainNameField(CharField):
|
||||
"""
|
||||
A subclass of CharField that only accepts a valid domain name.
|
||||
"""
|
||||
@@ -170,3 +169,39 @@ class DomainNameField(forms.CharField):
|
||||
def clean(self, value):
|
||||
value = self.to_python(value).strip()
|
||||
return super(DomainNameField, self).clean(value)
|
||||
|
||||
|
||||
class CustomRadioInput(RadioInput):
|
||||
def __unicode__(self):
|
||||
if 'id' in self.attrs:
|
||||
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
|
||||
else:
|
||||
label_for = ''
|
||||
choice_label = conditional_escape(force_unicode(self.choice_label))
|
||||
return mark_safe(u'<label class="radio inline" %s>%s %s</label>' % (label_for, self.tag(), choice_label))
|
||||
|
||||
|
||||
class InlineRadioRenderer(RadioSelect.renderer):
|
||||
def __iter__(self):
|
||||
for i, choice in enumerate(self.choices):
|
||||
yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)
|
||||
|
||||
def render(self):
|
||||
return mark_safe(u'\n'.join([u'%s\n' % force_unicode(w) for w in self]))
|
||||
|
||||
|
||||
class InlineRadioSelect(RadioSelect):
|
||||
renderer = InlineRadioRenderer
|
||||
|
||||
|
||||
class SeparatorField(Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["required"] = False
|
||||
super(SeparatorField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class YesNoField(ChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["choices"] = [("yes", _("Yes")), ("no", _("No"))]
|
||||
kwargs["widget"] = InlineRadioSelect
|
||||
super(YesNoField, self).__init__(*args, **kwargs)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
"Write your forwards methods here."
|
||||
orm["lib.Parameter"].objects.filter(name__in=[
|
||||
"admin.STORAGE_PATH", "admin.VIRTUAL_GID", "admin.MAILBOX_TYPE",
|
||||
"admin.MAILDIR_ROOT"
|
||||
]).delete()
|
||||
try:
|
||||
orm["lib.parameter"].objects.filter(name="general.DEFAULT_TOP_REDICTION").update(name="admin.DEFAULT_TOP_REDICTION")
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
try:
|
||||
orm["lib.parameter"].objects.filter(name="admin.CREATE_DIRECTORIES").update(name="admin.HANDLE_MAILBOXES")
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
try:
|
||||
orm["lib.parameter"].objects.filter(name="admin.VIRTUAL_UID").update(name="admin.MAILBOXES_OWNER")
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
try:
|
||||
orm["lib.parameter"].objects.filter(name="auth.SECRET_KEY").update(name="admin.SECRET_KEY")
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
raise RuntimeError("Cannot revert this migration")
|
||||
|
||||
models = {
|
||||
'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'})
|
||||
},
|
||||
'lib.parameter': {
|
||||
'Meta': {'object_name': 'Parameter'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'lib.userparameter': {
|
||||
'Meta': {'object_name': 'UserParameter'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['lib']
|
||||
symmetrical = True
|
||||
+204
-192
@@ -12,12 +12,14 @@ Only super users will be able to access this part of the web interface.
|
||||
import inspect
|
||||
import re
|
||||
import copy
|
||||
from django import forms
|
||||
from exceptions import ModoboaException
|
||||
|
||||
_params = {}
|
||||
_params_order = {}
|
||||
_levels = {'A' : 'admin', 'U' : 'user'}
|
||||
|
||||
class NotDefined(Exception):
|
||||
_params = {'A': {}, 'U': {}}
|
||||
|
||||
|
||||
class NotDefined(ModoboaException):
|
||||
def __init__(self, app, name):
|
||||
self.app = app
|
||||
self.name = name
|
||||
@@ -26,37 +28,142 @@ class NotDefined(Exception):
|
||||
return "Application '%s' and/or parameter '%s' not defined" \
|
||||
% (self.app, self.name)
|
||||
|
||||
def register_app(app=None, aparams_opts=None, uparams_opts=None):
|
||||
"""Manually register an application
|
||||
|
||||
You don't need to call this function unless you want to provide
|
||||
specific options for your application parameters.
|
||||
class GenericParametersForm(forms.Form):
|
||||
app = None
|
||||
|
||||
For example, you can indicate that user-level parameters are only
|
||||
accessible for users that own a mailbox.
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self.app is None:
|
||||
raise NotImplementedError
|
||||
|
||||
:param app: the application's name
|
||||
:param aparams_opts: a dict containing options applicable to admin-level params
|
||||
:param uparams_opts: a dict containing options applicable to user-level params
|
||||
kwargs["prefix"] = self.app
|
||||
super(GenericParametersForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.visirules = {}
|
||||
for param in self.fields.keys():
|
||||
fname = "visibility_%s" % param
|
||||
if not hasattr(self, fname):
|
||||
continue
|
||||
field, value = getattr(self, fname)().split("=")
|
||||
visibility = {"field": "id_%s-%s" % (self.app, field), "value": value}
|
||||
self.visirules["%s-%s" % (self.app, param)] = visibility
|
||||
|
||||
if not args:
|
||||
self._load_initial_values()
|
||||
|
||||
def _load_initial_values(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _save_parameter(self, p, name, value):
|
||||
if p.value == value:
|
||||
return
|
||||
name = name.lower()
|
||||
if hasattr(self, "update_%s" % name):
|
||||
getattr(self, "update_%s" % name)(value)
|
||||
if type(value) is unicode:
|
||||
p.value = value.encode("unicode_escape").strip()
|
||||
else:
|
||||
p.value = str(value)
|
||||
p.save()
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AdminParametersForm(GenericParametersForm):
|
||||
def _load_initial_values(self):
|
||||
from models import Parameter
|
||||
|
||||
for name in self.fields.keys():
|
||||
fullname = "%s.%s" % (self.app, name.upper())
|
||||
try:
|
||||
p = Parameter.objects.get(name=fullname)
|
||||
except Parameter.DoesNotExist:
|
||||
continue
|
||||
self.fields[name].initial = p.value
|
||||
|
||||
def save(self):
|
||||
from models import Parameter
|
||||
from modoboa.lib.formutils import SeparatorField
|
||||
|
||||
for name, value in self.cleaned_data.items():
|
||||
if type(self.fields[name]) is SeparatorField:
|
||||
continue
|
||||
fullname = "%s.%s" % (self.app, name.upper())
|
||||
try:
|
||||
p = Parameter.objects.get(name=fullname)
|
||||
except Parameter.DoesNotExist:
|
||||
p = Parameter()
|
||||
p.name = fullname
|
||||
self._save_parameter(p, name, value)
|
||||
|
||||
|
||||
class UserParametersForm(GenericParametersForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop("user") if "user" in kwargs else None
|
||||
super(UserParametersForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def _load_initial_values(self):
|
||||
if self.user is None:
|
||||
return
|
||||
from models import UserParameter
|
||||
|
||||
for name in self.fields.keys():
|
||||
fullname = "%s.%s" % (self.app, name.upper())
|
||||
try:
|
||||
p = UserParameter.objects.get(user=self.user, name=fullname)
|
||||
except UserParameter.DoesNotExist:
|
||||
continue
|
||||
self.fields[name].initial = p.value
|
||||
|
||||
@staticmethod
|
||||
def has_access(user):
|
||||
return True
|
||||
|
||||
def save(self):
|
||||
from models import UserParameter
|
||||
from modoboa.lib.formutils import SeparatorField
|
||||
|
||||
for name, value in self.cleaned_data.items():
|
||||
if type(self.fields[name]) is SeparatorField:
|
||||
continue
|
||||
fullname = "%s.%s" % (self.app, name.upper())
|
||||
try:
|
||||
p = UserParameter.objects.get(user=self.user, name=fullname)
|
||||
except UserParameter.DoesNotExist:
|
||||
p = UserParameter()
|
||||
p.user = self.user
|
||||
p.name = fullname
|
||||
self._save_parameter(p, name, value)
|
||||
|
||||
|
||||
def register(formclass, label):
|
||||
"""Register a form class containing parameters
|
||||
|
||||
formclass must inherit from ``AdminParametersForm`` of
|
||||
``UserParametersForm``.
|
||||
|
||||
:param formclass: a form class
|
||||
:param label:
|
||||
"""
|
||||
if app is None:
|
||||
app = __guess_extension()
|
||||
from models import Parameter
|
||||
from modoboa.lib.formutils import SeparatorField
|
||||
|
||||
if _params.has_key(app):
|
||||
return
|
||||
_params[app] = {}
|
||||
_params[app]['options'] = {}
|
||||
_params_order[app] = {}
|
||||
for lvl in _levels.keys():
|
||||
_params[app][lvl] = {}
|
||||
_params_order[app][lvl] = []
|
||||
if issubclass(formclass, AdminParametersForm):
|
||||
level = 'A'
|
||||
elif issubclass(formclass, UserParametersForm):
|
||||
level = 'U'
|
||||
else:
|
||||
raise RuntimeError("Unknown parameter class")
|
||||
_params[level][formclass.app] = {"label": label, "form": formclass, "defaults": {}}
|
||||
form = formclass()
|
||||
for name, field in form.fields.items():
|
||||
if type(field) is SeparatorField:
|
||||
continue
|
||||
_params[level][formclass.app]["defaults"][name.upper()] = field.initial
|
||||
|
||||
if aparams_opts:
|
||||
_params[app]['options']['A'] = aparams_opts
|
||||
if uparams_opts:
|
||||
_params[app]['options']['U'] = uparams_opts
|
||||
|
||||
def unregister_app(app):
|
||||
def unregister(app=None):
|
||||
"""Unregister an application
|
||||
|
||||
All parameters associated to this application will also be
|
||||
@@ -64,51 +171,19 @@ def unregister_app(app):
|
||||
|
||||
:param app: the application's name (string)
|
||||
"""
|
||||
if not _params.has_key(app):
|
||||
return False
|
||||
del _params[app]
|
||||
return True
|
||||
if app is None:
|
||||
app = __guess_extension()
|
||||
for lvlparams in _params.values():
|
||||
if app in lvlparams:
|
||||
del lvlparams[app]
|
||||
|
||||
|
||||
def __is_defined(app, level, name):
|
||||
if not level in _levels.keys() \
|
||||
or not app in _params.keys() \
|
||||
or not name in _params[app][level].keys():
|
||||
if not level in ['A', 'U'] \
|
||||
or not app in _params[level] \
|
||||
or not name in _params[level][app]["defaults"]:
|
||||
raise NotDefined(app, name)
|
||||
|
||||
def __register(app, level, name, **kwargs):
|
||||
"""Register a new parameter.
|
||||
|
||||
``app`` corresponds to a core component (admin, main) or to an
|
||||
extension.
|
||||
|
||||
:param name: the application's name
|
||||
:param level: the level this parameter is available from
|
||||
:param name: the parameter's name
|
||||
"""
|
||||
if not app in _params.keys():
|
||||
register_app(app)
|
||||
|
||||
if not level in _levels.keys():
|
||||
return
|
||||
if _params[app][level].has_key(name):
|
||||
return
|
||||
_params[app][level][name] = {}
|
||||
_params_order[app][level] += [name]
|
||||
for k, v in kwargs.iteritems():
|
||||
_params[app][level][name][k] = v
|
||||
|
||||
def __update(app, level, name, **kwargs):
|
||||
"""Update a parameter's definition
|
||||
|
||||
:param app: the application's name
|
||||
:param level: the level this parameter is available from
|
||||
:param name: the parameter's name
|
||||
"""
|
||||
if not app in _params.keys() or not level in _levels.keys() \
|
||||
or not _params[app][level].has_key(name):
|
||||
return
|
||||
for k, v in kwargs.iteritems():
|
||||
_params[app][level][name][k] = v
|
||||
|
||||
def __guess_extension():
|
||||
"""Tries to guess the application's name by inspecting the stack
|
||||
@@ -121,58 +196,6 @@ def __guess_extension():
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
def register_admin(name, **kwargs):
|
||||
"""Register a new parameter (admin level)
|
||||
|
||||
Each parameter is associated to one application. If no application
|
||||
is provided, the function tries to guess the appropriate one.
|
||||
|
||||
:param name: the parameter's name
|
||||
"""
|
||||
if kwargs.has_key("app"):
|
||||
app = kwargs["app"]
|
||||
del kwargs["app"]
|
||||
else:
|
||||
app = __guess_extension()
|
||||
return __register(app, 'A', name, **kwargs)
|
||||
|
||||
def update_admin(name, **kwargs):
|
||||
"""Update a parameter's definition (admin level)
|
||||
|
||||
Each parameter is associated to one application. If no application
|
||||
is provided, the function tries to guess the appropriate one.
|
||||
|
||||
:param name: the parameter's name
|
||||
"""
|
||||
if kwargs.has_key("app"):
|
||||
app = kwargs["app"]
|
||||
del kwargs["app"]
|
||||
else:
|
||||
app = __guess_extension()
|
||||
return __update(app, 'A', name, **kwargs)
|
||||
|
||||
def register_user(name, **kwargs):
|
||||
"""Register a new user-level parameter
|
||||
|
||||
:param name: the parameter's name
|
||||
"""
|
||||
if kwargs.has_key("app"):
|
||||
app = kwargs["app"]
|
||||
del kwargs["app"]
|
||||
else:
|
||||
app = __guess_extension()
|
||||
return __register(app, 'U', name, **kwargs)
|
||||
|
||||
def get_param_def(app, level, name):
|
||||
"""Return the definition of a given parameter
|
||||
|
||||
:param app: the application's name
|
||||
:param level: the required level (A or U)
|
||||
:param name: the parameter's name
|
||||
:return: a dictionnary
|
||||
"""
|
||||
__is_defined(app, level, name)
|
||||
return _params[app][level][name]
|
||||
|
||||
def save_admin(name, value, app=None):
|
||||
from models import Parameter
|
||||
@@ -186,13 +209,10 @@ def save_admin(name, value, app=None):
|
||||
except Parameter.DoesNotExist:
|
||||
p = Parameter()
|
||||
p.name = fullname
|
||||
if p.value != value:
|
||||
pdef = get_param_def(app, 'A', name)
|
||||
if "modify_cb" in pdef:
|
||||
pdef["modify_cb"](value)
|
||||
p.value = value.encode("unicode_escape").strip()
|
||||
p.save()
|
||||
return True
|
||||
p.value = None
|
||||
f = get_parameter_form('A', name, app)
|
||||
f()._save_parameter(p, name, value)
|
||||
|
||||
|
||||
def save_user(user, name, value, app=None):
|
||||
from models import UserParameter
|
||||
@@ -207,96 +227,88 @@ def save_user(user, name, value, app=None):
|
||||
p = UserParameter()
|
||||
p.user = user
|
||||
p.name = fullname
|
||||
if p.value != value:
|
||||
pdef = get_param_def(app, 'U', name)
|
||||
if "modify_cb" in pdef:
|
||||
pdef["modify_cb"](value)
|
||||
p.value = value.encode("unicode_escape").strip()
|
||||
p.save()
|
||||
return True
|
||||
f = get_parameter_form('U', name, app)
|
||||
f()._save_parameter(p, name, value)
|
||||
|
||||
def get_admin(name, app=None):
|
||||
|
||||
def get_admin(name, app=None, raise_error=True):
|
||||
"""Return an administrative parameter
|
||||
|
||||
A ``NotDefined`` exception if the parameter doesn't exist.
|
||||
|
||||
:param name: the parameter's name
|
||||
:param app: the application owning the parameter
|
||||
:return: the corresponding value as a string
|
||||
"""
|
||||
from models import Parameter
|
||||
|
||||
if app is None:
|
||||
app = __guess_extension()
|
||||
__is_defined(app, "A", name)
|
||||
try:
|
||||
__is_defined(app, "A", name)
|
||||
except NotDefined:
|
||||
if raise_error:
|
||||
raise
|
||||
return None
|
||||
try:
|
||||
p = Parameter.objects.get(name="%s.%s" % (app, name))
|
||||
except Parameter.DoesNotExist:
|
||||
return _params[app]["A"][name]["deflt"]
|
||||
return _params["A"][app]["defaults"][name]
|
||||
return p.value.decode("unicode_escape")
|
||||
|
||||
def get_all_admin_parameters():
|
||||
"""Retrieve all administrative parameters
|
||||
|
||||
Returned parameters are sorted by application
|
||||
|
||||
:return: a list of dictionaries
|
||||
"""
|
||||
result = []
|
||||
for app in sorted(_params.keys()):
|
||||
tmp = {"name" : app, "params" : []}
|
||||
for p in _params_order[app]['A']:
|
||||
newdef = copy.deepcopy(_params[app]['A'][p])
|
||||
newdef["name"] = p
|
||||
newdef["value"] = get_admin(p, app=app)
|
||||
tmp["params"] += [newdef]
|
||||
result += [tmp]
|
||||
return result
|
||||
|
||||
def get_user(user, name, app=None):
|
||||
"""Return a parameter for a specific user
|
||||
|
||||
A ``NotDefined`` exception if the parameter doesn't exist.
|
||||
|
||||
:param ``User`` user: the desired user
|
||||
:param name: the parameter's name
|
||||
:param app: the application owning the parameter
|
||||
:return: the corresponding value as a string
|
||||
"""
|
||||
from models import UserParameter
|
||||
|
||||
if app is None:
|
||||
app = __guess_extension()
|
||||
__is_defined(app, "U", name)
|
||||
try:
|
||||
__is_defined(app, "U", name)
|
||||
except NotDefined:
|
||||
if raise_error:
|
||||
raise
|
||||
return None
|
||||
try:
|
||||
p = UserParameter.objects.get(user=user, name="%s.%s" % (app, name))
|
||||
except UserParameter.DoesNotExist:
|
||||
return _params[app]["U"][name]["deflt"]
|
||||
return _params["U"][app]["defaults"][name]
|
||||
return p.value.decode("unicode_escape")
|
||||
|
||||
def get_all_user_params(user):
|
||||
"""Retrieve all the parameters of the given user
|
||||
|
||||
Returned parameters are sorted by application.
|
||||
def get_admin_forms(*args, **kwargs):
|
||||
for formdef in _params['A'].values():
|
||||
yield {"label": formdef["label"], "form": formdef["form"](*args, **kwargs)}
|
||||
|
||||
:param user: a ``User`` object
|
||||
:return: a list of dictionaries
|
||||
"""
|
||||
result = []
|
||||
for app in sorted(_params.keys()):
|
||||
if not len(_params[app]['U']):
|
||||
continue
|
||||
if get_app_option('U', 'needs_mailbox', False, app=app) \
|
||||
and not user.has_mailbox:
|
||||
continue
|
||||
tmp = {"name" : app, "params" : []}
|
||||
for p in _params_order[app]['U']:
|
||||
param_def = _params[app]['U'][p]
|
||||
newdef = copy.deepcopy(param_def)
|
||||
newdef["name"] = p
|
||||
newdef["value"] = get_user(user, p, app=app)
|
||||
tmp["params"] += [newdef]
|
||||
result += [tmp]
|
||||
return result
|
||||
|
||||
def get_app_option(lvl, name, dflt, app=None):
|
||||
"""Retrieve a specific option for a given application
|
||||
def get_user_forms(user, *args, **kwargs):
|
||||
kwargs["user"] = user
|
||||
def realfunc():
|
||||
for formdef in _params['U'].values():
|
||||
if not formdef["form"].has_access(user):
|
||||
continue
|
||||
yield {"label": formdef["label"], "form": formdef["form"](*args, **kwargs)}
|
||||
|
||||
If the option is not found, a default value is returned.
|
||||
return realfunc
|
||||
|
||||
:param lvl: the option's level (U or A)
|
||||
:param name: the option's name
|
||||
:param dflt: the default value to return
|
||||
:param app: the application name
|
||||
:return: the option's value
|
||||
|
||||
def get_parameter_form(level, name, app=None):
|
||||
"""Return the form containing a specific parameter
|
||||
|
||||
:param string level: associated level
|
||||
:param string name: paremeter's name
|
||||
:param string app: parameter's application
|
||||
:return: a form class
|
||||
"""
|
||||
if app is None:
|
||||
app = __guess_extension()
|
||||
if not lvl in _params[app]['options']:
|
||||
return dflt
|
||||
if not _params[app]['options'][lvl].has_key(name):
|
||||
return dflt
|
||||
return _params[app]['options'][lvl][name]
|
||||
__is_defined(app, level, name)
|
||||
return _params[level][app]["form"]
|
||||
|
||||
@@ -8,16 +8,19 @@ import logging
|
||||
import logging.handlers
|
||||
from modoboa.lib import parameters
|
||||
|
||||
def exec_cmd(cmd, **kwargs):
|
||||
def exec_cmd(cmd, sudo_user=None, **kwargs):
|
||||
import subprocess
|
||||
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, **kwargs)
|
||||
if sudo_user is not None:
|
||||
cmd = "sudo -u %s %s" % (sudo_user, cmd)
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, **kwargs)
|
||||
output = p.communicate()[0]
|
||||
return p.returncode, output
|
||||
|
||||
def exec_as_vuser(cmd):
|
||||
code, output = exec_cmd("sudo -u %s %s" \
|
||||
% (parameters.get_admin("VIRTUAL_UID", app="admin"), cmd))
|
||||
% (parameters.get_admin("MAILBOXES_OWNER", app="admin"), cmd))
|
||||
if code:
|
||||
exec_cmd("echo '%s' >> /tmp/vmail.log" % output)
|
||||
return False
|
||||
|
||||
@@ -57,17 +57,18 @@ class SelectionColumn(Column):
|
||||
A 'selection' column contains only checkboxes (one per row). It is
|
||||
usefull to select rows for grouped actions (modify, delete, etc.)
|
||||
"""
|
||||
def __str__(self):
|
||||
def __unicode__(self):
|
||||
if self.header:
|
||||
return "<input type='checkbox' name='selectall' id='selectall' />"
|
||||
return ""
|
||||
|
||||
def render(self, value, selection):
|
||||
return "<input type='checkbox' id='%s' name='select_%s' value='%s' %s/>" \
|
||||
% (self.name, value, value, selection and "checked" or "")
|
||||
return "<input type='checkbox' id='%(id)s' name='select_%(value)s' value='%(value)s' %(checked)s/>" \
|
||||
% {'id': self.name, 'value': value,
|
||||
"checked": "checked=''" if selection else ""}
|
||||
|
||||
def transform(self, row, col, table):
|
||||
selection = row.has_key(self.name) and row[self.name] or False
|
||||
selection = row[self.name] if self.name in row else False
|
||||
col["value"] = self.render(row[table.idkey], selection)
|
||||
col["safe"] = True
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
from modoboa.lib.webutils import static_url
|
||||
from modoboa.lib.formutils import SeparatorField
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -90,6 +91,9 @@ def render_form(form, tpl=None):
|
||||
|
||||
@register.simple_tag
|
||||
def render_field(field, help_display_mode="tooltip"):
|
||||
if type(field.form.fields[field.name]) is SeparatorField:
|
||||
return "<h5>%s</h5>" % unicode(field.label)
|
||||
|
||||
return render_to_string("common/generic_field.html", dict(
|
||||
field=field, help_display_mode=help_display_mode
|
||||
))
|
||||
@@ -175,6 +179,16 @@ def progress_color(value):
|
||||
def fromunix(value):
|
||||
return datetime.datetime.fromtimestamp(int(value))
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def visirule(field):
|
||||
if not hasattr(field.form, "visirules") or not field.html_name in field.form.visirules:
|
||||
return ""
|
||||
rule = field.form.visirules[field.html_name]
|
||||
return " data-visibility-field='%s' data-visibility-value='%s' " \
|
||||
% (rule["field"], rule["value"])
|
||||
|
||||
|
||||
class ConnectedUsers(template.Node):
|
||||
def __init__(self, varname):
|
||||
self.varname = varname
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# coding: utf-8
|
||||
import unittest
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.utils import simplejson
|
||||
from django import forms
|
||||
import parameters
|
||||
|
||||
|
||||
class ModoTestCase(TestCase):
|
||||
|
||||
@@ -24,6 +28,7 @@ class ModoTestCase(TestCase):
|
||||
def check_ajax_get(self, *args, **kwargs):
|
||||
self.check_ajax_request("get", *args, **kwargs)
|
||||
|
||||
|
||||
class ExtTestCase(ModoTestCase):
|
||||
name = None
|
||||
|
||||
@@ -32,3 +37,66 @@ class ExtTestCase(ModoTestCase):
|
||||
self.clt.get("/modoboa/admin/settings/extensions/")
|
||||
self.clt.post("/modoboa/admin/settings/extensions/save/",
|
||||
{"select_%s" % self.name : "1"})
|
||||
|
||||
|
||||
class TestParams(parameters.AdminParametersForm):
|
||||
app = "test"
|
||||
|
||||
param1 = forms.CharField(label="Test", initial="toto")
|
||||
param2 = forms.IntegerField(label="Int", initial=42)
|
||||
|
||||
def update_param2(self, value):
|
||||
raise RuntimeError("new value %d" % value)
|
||||
|
||||
|
||||
class TestUserParams(parameters.UserParametersForm):
|
||||
app = "test"
|
||||
|
||||
param1 = forms.CharField(label="Test", initial="titi")
|
||||
param2 = forms.IntegerField(label="Int", initial=42)
|
||||
|
||||
def update_param2(self, value):
|
||||
raise RuntimeError("new value %d" % value)
|
||||
|
||||
|
||||
class ParameterTestCase(TestCase):
|
||||
"""Simple test cases for ``modoboa.lib.parameters`` module.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
from modoboa.admin.models import User
|
||||
parameters.register(TestParams, "Test")
|
||||
parameters.register(TestUserParams, "TestUser")
|
||||
self.user = User.objects.create(username="tester")
|
||||
|
||||
def test_register_form(self):
|
||||
self.assertIn("test", parameters._params['A'])
|
||||
|
||||
def test_unregister(self):
|
||||
parameters.unregister("test")
|
||||
self.assertNotIn("test", parameters._params['A'])
|
||||
|
||||
def test_get_parameter_form(self):
|
||||
self.assertIs(parameters.get_parameter_form('A', 'PARAM1', 'test'), TestParams)
|
||||
|
||||
def test_save_admin(self):
|
||||
parameters.save_admin("PARAM1", "45", app="test")
|
||||
self.assertEqual(parameters.get_admin("PARAM1", app="test"), "45")
|
||||
|
||||
def test_update_param(self):
|
||||
with self.assertRaisesRegexp(RuntimeError, "new value 35"):
|
||||
parameters.save_admin("PARAM2", 35, app="test")
|
||||
|
||||
def test_get_admin(self):
|
||||
self.assertEqual(parameters.get_admin("PARAM1", "test"), "toto")
|
||||
with self.assertRaises(parameters.NotDefined):
|
||||
parameters.get_admin("TOTO", "test")
|
||||
|
||||
def test_get_user(self):
|
||||
self.assertEqual(parameters.get_user(self.user, "PARAM1", "test"), "titi")
|
||||
with self.assertRaises(parameters.NotDefined):
|
||||
parameters.get_user(self.user, "TOTO", "test")
|
||||
|
||||
def test_save_user(self):
|
||||
parameters.save_user(self.user, "PARAM1", "pouet", "test")
|
||||
self.assertEqual(parameters.get_user(self.user, "PARAM1", "test"), "pouet")
|
||||
|
||||
@@ -93,14 +93,15 @@ def ajax_response(request, status="ok", respmsg=None,
|
||||
jsonctx["norefresh"] = norefresh
|
||||
return HttpResponse(simplejson.dumps(jsonctx), mimetype="application/json")
|
||||
|
||||
def ajax_simple_response(content):
|
||||
def ajax_simple_response(content, **response_kwargs):
|
||||
"""Simple AJAX response
|
||||
|
||||
No extra formatting is done. The content is passed directly to simplejon.
|
||||
|
||||
:param content: the response's content (list, dict, string)
|
||||
"""
|
||||
return HttpResponse(simplejson.dumps(content), mimetype="application/json")
|
||||
response_kwargs["content_type"] = "application/json"
|
||||
return HttpResponse(simplejson.dumps(content), **response_kwargs)
|
||||
|
||||
def static_url(path):
|
||||
"""Returns the correct static url for a given file
|
||||
@@ -150,7 +151,7 @@ def topredirection(request):
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.extensions import exts_pool
|
||||
|
||||
topredir = parameters.get_admin("DEFAULT_TOP_REDIRECTION", app="general")
|
||||
topredir = parameters.get_admin("DEFAULT_TOP_REDIRECTION", app="admin")
|
||||
if not topredir in ["admin", "userprefs"]:
|
||||
infos = exts_pool.get_extension_infos(topredir)
|
||||
path = infos["url"] if infos["url"] else infos["name"]
|
||||
|
||||
@@ -37,7 +37,8 @@ body {
|
||||
float: left;
|
||||
padding-top: 5px;
|
||||
text-align: left;
|
||||
width: 200px;
|
||||
width: 250px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.form-horizontal .param-controls {
|
||||
|
||||
@@ -26,12 +26,35 @@ TwocolsNav.prototype = {
|
||||
formid: this.options.formid,
|
||||
modal: false,
|
||||
reload_on_success: false,
|
||||
error_cb: $.proxy(this.update_content, this),
|
||||
error_cb: $.proxy(this.display_errors, this),
|
||||
success_cb: $.proxy(this.save_cb, this)
|
||||
});
|
||||
}, this));
|
||||
},
|
||||
|
||||
display_errors: function(data) {
|
||||
$.each(data.errors, function(id, value) {
|
||||
var fullid = "id_" + (data.prefix ? data.prefix + "-" : "") + id;
|
||||
var $widget = $("#" + fullid);
|
||||
var spanid = fullid + "-error";
|
||||
var $span = $("#" + spanid);
|
||||
|
||||
if (!$widget.parents(".control-group").hasClass("error")) {
|
||||
$widget.parents(".control-group").addClass("error");
|
||||
}
|
||||
if (!$span.length) {
|
||||
$span = $("<span />", {
|
||||
"class": "help-inline",
|
||||
"html": value[0],
|
||||
"id": spanid
|
||||
});
|
||||
$widget.parents(".controls").append($span);
|
||||
} else {
|
||||
$span.html(value[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
update_content: function(data) {
|
||||
if (data.content) {
|
||||
$('#' + this.options.divid).html(data.content);
|
||||
@@ -43,15 +66,37 @@ TwocolsNav.prototype = {
|
||||
$("body").notify("error", data.respmsg);
|
||||
}
|
||||
$('#' + this.options.divid + ' select').change($.proxy(this.select_change, this));
|
||||
$(".help").popover().click(function(e) {e.preventDefault();});
|
||||
$('#' + this.options.divid + ' input[type=radio]').click($.proxy(this.radio_clicked, this));
|
||||
$(".help").popover({
|
||||
placement: 'bottom',
|
||||
trigger: 'click'
|
||||
}).click(function(e) {e.preventDefault();});
|
||||
$('#' + this.options.divid + ' select').change();
|
||||
$('#' + this.options.divid + ' input[type=radio]:checked').click();
|
||||
},
|
||||
|
||||
propagate_change: function($node) {
|
||||
var $select = $node.find("select");
|
||||
var $radio = $node.find("input[type=radio]");
|
||||
if ($select.length) {
|
||||
$select.change();
|
||||
}
|
||||
if ($radio.length) {
|
||||
$radio.click();
|
||||
}
|
||||
},
|
||||
|
||||
toggle_field_visibility: function($field, $parent, value) {
|
||||
if ($parent.attr("idsabled") === undefined &&
|
||||
$field.attr("data-visibility-value") == value) {
|
||||
$field.attr("disabled", null);
|
||||
$field.show();
|
||||
this.propagate_change($field);
|
||||
} else {
|
||||
$field.hide();
|
||||
$field.attr("disabled", "disabled");
|
||||
this.propagate_change($field);
|
||||
}
|
||||
},
|
||||
|
||||
select_change: function(e) {
|
||||
@@ -60,18 +105,19 @@ TwocolsNav.prototype = {
|
||||
var $parent = $target.parents("div.control-group");
|
||||
|
||||
$('div[data-visibility-field="' + $target.attr("id") + '"]').each(function(idx) {
|
||||
var $this = $(this);
|
||||
instance.toggle_field_visibility($(this), $parent, $target.attr("value"));
|
||||
});
|
||||
},
|
||||
|
||||
if ($parent.attr("disabled") === undefined &&
|
||||
$this.attr("data-visibility-value") == $target.attr("value")) {
|
||||
$this.attr("disabled", null);
|
||||
$this.show();
|
||||
instance.propagate_change($this)
|
||||
} else {
|
||||
$this.hide();
|
||||
$this.attr("disabled", "disabled");
|
||||
instance.propagate_change($this);
|
||||
}
|
||||
radio_clicked: function(e) {
|
||||
var instance = this;
|
||||
var $target = $(e.target);
|
||||
var $parent = $target.parents("div.control-group");
|
||||
var realid = $target.attr("id");
|
||||
|
||||
realid = realid.substr(0, realid.length - 2);
|
||||
$('div[data-visibility-field="' + realid + '"]').each(function(idx) {
|
||||
instance.toggle_field_visibility($(this), $parent, $target.attr("value"));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}"{% if hidden %}style="display: none"{% endif %}>
|
||||
{% load i18n libextras %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}"{% visirule field %}{% if hidden %}style="display: none"{% endif %}>
|
||||
{% if field.label %}<label class="control-label" for="id_{{ field.html_name }}">{{ field.label }}</label>{% endif %}{% if help_display_mode == "tooltip" and field.help_text %}<a href="#" class="help pull-left" data-original-title="{% trans 'Help:' %} {{ field.label }}" data-content="{{ field.help_text }}" rel="popover">?</a>{% endif %}
|
||||
<div class="controls spaced">
|
||||
{% if appended_text %}<div class="input-append">{% endif %}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
{% load i18n admin_extras %}
|
||||
{% if gparams %}
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs">
|
||||
{% for app in gparams %}<li{% if forloop.first %} class="active"{% endif %}><a href="#{{ forloop.counter }}" data-toggle="tab">{{ app.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="tab-content">{% for app in gparams %}
|
||||
<div class="tab-pane{% if forloop.first %} active{% endif %}" id="{{ forloop.counter }}">
|
||||
{% for p in app.params %}{% param app.name p %}{% endfor %}
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
{% load libextras %}
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs">
|
||||
{% for form in forms %}<li{% if forloop.first %} class="active"{% endif %}><a href="#{{ forloop.counter }}" data-toggle="tab">{{ form.label }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="tab-content">{% for form in forms %}
|
||||
<div class="tab-pane{% if forloop.first %} active{% endif %}" id="{{ forloop.counter }}">
|
||||
{% render_form form.form %}
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="info">{% trans "No parameters available" %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ from modoboa.lib import parameters
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', 'modoboa.lib.webutils.topredirection'),
|
||||
(r'^admin/', include('modoboa.admin.urls')),
|
||||
(r'^userprefs/', include('modoboa.userprefs.urls')),
|
||||
(r'^accounts/login/$', 'modoboa.auth.views.dologin'),
|
||||
(r'^accounts/logout/$', 'modoboa.auth.views.dologout'),
|
||||
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog',
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
# coding: utf-8
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.extensions import ModoExtension, exts_pool
|
||||
|
||||
parameters.register_user("LANG", type="list", deflt="en",
|
||||
label=ugettext_lazy("Prefered language"),
|
||||
values=[("de", "deutsch"), ("en", "english"),
|
||||
("es", "español"), ("fr", "français"),
|
||||
("pt", "português"), ("sv", "svenska"),],
|
||||
help=ugettext_lazy("Prefered language to display pages"),
|
||||
app="general")
|
||||
|
||||
class UserPreferences(ModoExtension):
|
||||
name = "userprefs"
|
||||
label = ugettext_lazy("User preferences")
|
||||
version = "1.0"
|
||||
description = ugettext_lazy("Per-user settings")
|
||||
always_active = True
|
||||
|
||||
def load(self):
|
||||
from modoboa.lib import parameters
|
||||
from app_settings import UserSettings
|
||||
parameters.register(UserSettings, ugettext_lazy("General"))
|
||||
|
||||
def destroy(self):
|
||||
parameters.unregister()
|
||||
|
||||
exts_pool.register_extension(UserPreferences, show=False)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# coding: utf-8
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from modoboa.lib.parameters import UserParametersForm
|
||||
|
||||
|
||||
class UserSettings(UserParametersForm):
|
||||
app = "general"
|
||||
|
||||
lang = forms.ChoiceField(
|
||||
initial="en",
|
||||
label=ugettext_lazy("Prefered language"),
|
||||
choices=[("de", "deutsch"), ("en", "english"),
|
||||
("es", "español"), ("fr", "français"),
|
||||
("pt", "português"), ("sv", "svenska"),],
|
||||
help_text=ugettext_lazy("Prefered language to display pages")
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
#uprefs_content .form-horizontal .control-label {
|
||||
margin-left: 20px;
|
||||
text-align: left;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#uprefs_content > .form-horizontal .controls {
|
||||
margin-left: 240px;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "twocols.html" %}{% load i18n uprefs_extras libextras %}
|
||||
{% block pagetitle %}{% trans "Settings" %}{% endblock %}
|
||||
{% block html_head %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}userprefs/css/userprefs.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">
|
||||
|
||||
@@ -102,19 +102,22 @@ def profile(request, tplname='userprefs/profile.html'):
|
||||
@login_required
|
||||
def preferences(request):
|
||||
if request.method == "POST":
|
||||
for pname, v in request.POST.iteritems():
|
||||
if pname == "update":
|
||||
for formdef in parameters.get_user_forms(request.user, request.POST)():
|
||||
form = formdef["form"]
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
continue
|
||||
app, name = pname.split('.')
|
||||
parameters.save_user(request.user, name, v, app=app)
|
||||
return ajax_simple_response({
|
||||
"status": "ko", "prefix": form.app, "errors": form.errors
|
||||
})
|
||||
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", respmsg=_("Preferences saved")
|
||||
))
|
||||
return ajax_simple_response({
|
||||
"status": "ok", "respmsg": _("Preferences saved")
|
||||
})
|
||||
|
||||
return ajax_simple_response({
|
||||
"status" : "ok",
|
||||
"content" : render_to_string("userprefs/preferences.html", {
|
||||
"gparams" : parameters.get_all_user_params(request.user)
|
||||
"forms" : parameters.get_user_forms(request.user)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user