From 1c35ce575710708702b3cb5481726ff8e1920343 Mon Sep 17 00:00:00 2001 From: tonio Date: Wed, 9 Jun 2010 15:58:46 +0200 Subject: [PATCH] Ticket #52 From now, users can store their own "preferences" inside MailNG. --- admin/__init__.py | 28 ++++----- admin/models.py | 16 ++--- admin/views.py | 21 +++---- extensions/amavis_quarantine/cleanup.py | 2 +- extensions/amavis_quarantine/lib.py | 8 +-- extensions/amavis_quarantine/main.py | 26 ++++---- extensions/postfix_autoreply/main.py | 5 +- ...002_auto__add_armessage__add_arhistoric.py | 26 +++++++- .../postfix_autoreply/scripts/autoreply.py | 2 +- extensions/stats/grapher.py | 4 +- extensions/stats/logparser.py | 4 +- extensions/stats/main.py | 15 +++-- extensions/stats/views.py | 10 ++-- extensions/webmail/imap_listing.py | 18 +++--- extensions/webmail/main.py | 38 +++++++----- extensions/webmail/views.py | 27 +++++---- lib/__init__.py | 6 +- lib/authbackends.py | 4 +- lib/migrations/0001_initial.py | 55 +++++++++++++++++ lib/models.py | 6 ++ lib/parameters.py | 60 +++++++++++++++---- static/js/main.js | 6 -- static/js/preferences.js | 18 ++++++ static/js/settings.js | 2 +- templates/admin/parameters.html | 13 +--- templates/common/parameters.html | 13 ++++ templates/userprefs/header.html | 2 +- templates/userprefs/preferences.html | 11 ++++ urls.py | 2 +- userprefs/templatetags/uprefs_extras.py | 11 +++- userprefs/urls.py | 2 + userprefs/views.py | 42 ++++++++++--- 32 files changed, 351 insertions(+), 152 deletions(-) delete mode 100644 static/js/main.js create mode 100644 static/js/preferences.js create mode 100644 templates/common/parameters.html create mode 100644 templates/userprefs/preferences.html diff --git a/admin/__init__.py b/admin/__init__.py index 29c710d30..0fa02140d 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -3,17 +3,17 @@ from mailng.lib import parameters from django.utils.translation import ugettext as _ -parameters.register("admin","STORAGE_PATH", "string", "/var/vmail", - help=_("Path to the root directory where messages are stored")) -parameters.register("admin", "VIRTUAL_UID", "string", "vmail", - help=_("UID of the virtual user which owns domains/mailboxes/messages on the filesystem")) -parameters.register("admin", "VIRTUAL_GID", "string", "vmail", - help=_("GID of the virtual user which owns domains/mailboxes/messages on the filesystem")) -parameters.register("admin", "MAILBOX_TYPE", "list", "maildir", - values=[("maildir", "maildir"), ("mbox", "mbox")], - help=_("Mailboxes storage format")) -parameters.register("admin", "MAILDIR_ROOT", "string", ".maildir", - help=_("Sub-directory (inside the mailbox) where messages are stored when using the maildir format")) -parameters.register("admin", "PASSWORD_SCHEME", "list", "crypt", - values=[("crypt", "crypt"), ("md5", "md5"), ("clear", "clear")], - help=_("Scheme used to crypt mailbox passwords")) +parameters.register_admin("admin","STORAGE_PATH", type="string", deflt="/var/vmail", + help=_("Path to the root directory where messages are stored")) +parameters.register_admin("admin", "VIRTUAL_UID", type="string", deflt="vmail", + help=_("UID of the virtual user which owns domains/mailboxes/messages on the filesystem")) +parameters.register_admin("admin", "VIRTUAL_GID", type="string", deflt="vmail", + help=_("GID of the virtual user which owns domains/mailboxes/messages on the filesystem")) +parameters.register_admin("admin", "MAILBOX_TYPE", type="list", deflt="maildir", + values=[("maildir", "maildir"), ("mbox", "mbox")], + help=_("Mailboxes storage format")) +parameters.register_admin("admin", "MAILDIR_ROOT", type="string", deflt=".maildir", + help=_("Sub-directory (inside the mailbox) where messages are stored when using the maildir format")) +parameters.register_admin("admin", "PASSWORD_SCHEME", type="list", deflt="crypt", + values=[("crypt", "crypt"), ("md5", "md5"), ("clear", "clear")], + help=_("Scheme used to crypt mailbox passwords")) diff --git a/admin/models.py b/admin/models.py index 278f792c7..eee5cd2ad 100644 --- a/admin/models.py +++ b/admin/models.py @@ -19,17 +19,17 @@ class Domain(models.Model): ) def create_dir(self): - path = "%s/%s" % (parameters.get("admin", "STORAGE_PATH"), self.name) + path = "%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), self.name) return exec_as_vuser("mkdir -p %s" % path) def rename_dir(self, newname): - stpath = parameters.get("admin", "STORAGE_PATH") + stpath = parameters.get_admin("admin", "STORAGE_PATH") return exec_as_vuser("mv %s/%s %s/%s" \ % (stpath, self.name, stpath, newname)) def delete_dir(self): return exec_as_vuser("rm -r %s/%s" \ - % (parameters.get("admin", "STORAGE_PATH"), + % (parameters.get_admin("admin", "STORAGE_PATH"), self.name)) def __str__(self): @@ -57,14 +57,14 @@ class Mailbox(models.Model): 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") + self.mbtype = parameters.get_admin("admin", "MAILBOX_TYPE") + self.mdirroot = parameters.get_admin("admin", "MAILDIR_ROOT") def __str__(self): return "%s" % (self.address) def create_dir(self, domain): - path = "%s/%s/%s" % (parameters.get("admin", "STORAGE_PATH"), + path = "%s/%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), domain.name, self.address) if os.path.exists(path): return True @@ -86,7 +86,7 @@ class Mailbox(models.Model): return True def rename_dir(self, domain, newaddress): - path = "%s/%s" % (parameters.get("admin", "STORAGE_PATH"), domain) + path = "%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), domain) code = exec_as_vuser("mv %s/%s %s/%s" \ % (path, self.address, path, newaddress)) if code: @@ -95,7 +95,7 @@ class Mailbox(models.Model): def delete_dir(self): return exec_as_vuser("rm -r %s/%s/%s" \ - % (parameters.get("admin", "STORAGE_PATH"), + % (parameters.get_admin("admin", "STORAGE_PATH"), self.domain.name, self.address)) class Alias(models.Model): diff --git a/admin/views.py b/admin/views.py index ca6848ed0..d284db567 100644 --- a/admin/views.py +++ b/admin/views.py @@ -179,8 +179,8 @@ def newmailbox(request, dom_id=None): mb.password = crypt_password(request.POST["password1"]) - mb.uid = pwd.getpwnam(parameters.get("admin", "VIRTUAL_UID")).pw_uid - mb.gid = pwd.getpwnam(parameters.get("admin", "VIRTUAL_GID")).pw_gid + mb.uid = pwd.getpwnam(parameters.get_admin("admin", "VIRTUAL_UID")).pw_uid + mb.gid = pwd.getpwnam(parameters.get_admin("admin", "VIRTUAL_GID")).pw_gid mb.domain = domain mb.quota = request.POST["quota"] if not mb.quota: @@ -415,14 +415,15 @@ def viewparameters(request): gparams = [] for app in apps: tmp = {"name" : app, "params" : []} - for p in sorted(parameters._params[app]): + for p in sorted(parameters._params[app]['A']): + param_def = parameters._params[app]['A'][p] newdef = {"name" : p, - "value" : parameters.get(app, p), - "help" : parameters._params[app][p]["help"], - "default" : parameters._params[app][p]["default"], - "type" : parameters._params[app][p]["type"]} - if "values" in parameters._params[app][p].keys(): - newdef["values"] = parameters._params[app][p]["values"] + "value" : parameters.get_admin(app, p), + "help" : param_def["help"], + "default" : param_def["default"], + "type" : param_def["type"]} + if "values" in param_def.keys(): + newdef["values"] = param_def["values"] tmp["params"] += [newdef] gparams += [tmp] @@ -437,6 +438,6 @@ def saveparameters(request): if pname == "update": continue app, name = pname.split('.') - parameters.save(app, name, v) + parameters.save_admin(app, name, v) request.user.message_set.create(message=_("Configuration saved.")) return HttpResponseRedirect(reverse(admin.views.viewparameters)) diff --git a/extensions/amavis_quarantine/cleanup.py b/extensions/amavis_quarantine/cleanup.py index f293f5b6e..7a5dd6d4f 100755 --- a/extensions/amavis_quarantine/cleanup.py +++ b/extensions/amavis_quarantine/cleanup.py @@ -5,7 +5,7 @@ from mailng.lib import db, parameters if __name__ == "__main__": conn = db.getconnection("amavis_quarantine") - max_messages_age = parameters.get("amavis_quarantine", "MAX_MESSAGES_AGE") + max_messages_age = parameters.get_admin("amavis_quarantine", "MAX_MESSAGES_AGE") print "Deleting messages older than %d days..." % max_messages_age db.execute(conn, """ diff --git a/extensions/amavis_quarantine/lib.py b/extensions/amavis_quarantine/lib.py index 0f8aa9d67..a3ac66f45 100644 --- a/extensions/amavis_quarantine/lib.py +++ b/extensions/amavis_quarantine/lib.py @@ -6,14 +6,14 @@ from mailng.lib import parameters class AMrelease(object): def __init__(self): - mode = parameters.get("amavis_quarantine", "AM_PDP_MODE") + mode = parameters.get_admin("amavis_quarantine", "AM_PDP_MODE") if mode == "inet": - host = parameters.get("amavis_quarantine", 'AM_PDP_HOST') - port = parameters.get("amavis_quarantine", 'AM_PDP_PORT') + host = parameters.get_admin("amavis_quarantine", 'AM_PDP_HOST') + port = parameters.get_admin("amavis_quarantine", 'AM_PDP_PORT') self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, int(port))) else: - path = parameters.get("amavis_quarantine", 'AM_PDP_SOCKET') + path = parameters.get_admin("amavis_quarantine", 'AM_PDP_SOCKET') self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(path) diff --git a/extensions/amavis_quarantine/main.py b/extensions/amavis_quarantine/main.py index f5b734a6b..ef901b62a 100644 --- a/extensions/amavis_quarantine/main.py +++ b/extensions/amavis_quarantine/main.py @@ -16,18 +16,20 @@ def infos(): def init(): events.register("UserMenuDisplay", menu) - parameters.register("amavis_quarantine", "MAX_MESSAGES_AGE", "int", 14, - help=_("Quarantine messages maximum age (in days) before deletion")) - parameters.register("amavis_quarantine", "AM_PDP_MODE", "list", "unix", - values=[("inet", "inet"), ("unix", "unix")], - help="") - parameters.register("amavis_quarantine", "AM_PDP_HOST", "string", "localhost", - help="") - parameters.register("amavis_quarantine", "AM_PDP_PORT", "int", 9998, - help="") - parameters.register("amavis_quarantine", "AM_PDP_SOCKET", "string", - "/var/amavis/amavisd.sock", - help="") + parameters.register_admin("amavis_quarantine", "MAX_MESSAGES_AGE", type="int", + deflt=14, + help=_("Quarantine messages maximum age (in days) before deletion")) + parameters.register_admin("amavis_quarantine", "AM_PDP_MODE", type="list", + deflt="unix", + values=[("inet", "inet"), ("unix", "unix")], + help="") + parameters.register_admin("amavis_quarantine", "AM_PDP_HOST", type="string", + deflt="localhost", help="") + parameters.register_admin("amavis_quarantine", "AM_PDP_PORT", type="int", + deflt=9998, help="") + parameters.register_admin("amavis_quarantine", "AM_PDP_SOCKET", type="string", + deflt="/var/amavis/amavisd.sock", + help="") def destroy(): events.unregister("UserMenuDisplay", menu) diff --git a/extensions/postfix_autoreply/main.py b/extensions/postfix_autoreply/main.py index c386e04ef..60f316992 100644 --- a/extensions/postfix_autoreply/main.py +++ b/extensions/postfix_autoreply/main.py @@ -28,8 +28,9 @@ def init(): events.register("ModifyMailbox", onModifyMailbox) events.register("UserMenuDisplay", menu) - parameters.register("postfix_autoreply", "AUTOREPLIES_TIMEOUT", "int", 86400, - help=_("Timeout in seconds between two auto-replies to the same recipient")) + parameters.register_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT", + type="int", deflt=86400, + help=_("Timeout in seconds between two auto-replies to the same recipient")) def destroy(): events.unregister("CreateDomain", onCreateDomain) diff --git a/extensions/postfix_autoreply/migrations/0002_auto__add_armessage__add_arhistoric.py b/extensions/postfix_autoreply/migrations/0002_auto__add_armessage__add_arhistoric.py index b72632b00..18d1096b7 100644 --- a/extensions/postfix_autoreply/migrations/0002_auto__add_armessage__add_arhistoric.py +++ b/extensions/postfix_autoreply/migrations/0002_auto__add_armessage__add_arhistoric.py @@ -7,13 +7,33 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - # Renaming model 'ARmessage' - db.rename_table('main_armessage', 'postfix_autoreply_armessage') + # Renaming/Creating model 'ARmessage' + try: + db.rename_table('main_armessage', 'postfix_autoreply_armessage') + except Exception: + db.create_table('postfix_autoreply_armessage', ( + ('mbox', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['admin.Mailbox'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('subject', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('content', self.gf('django.db.models.fields.TextField')()), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)) + )) + db.send_create_signal('postfix_autoreply', ['ARmessage']) + db.add_column('postfix_autoreply_armessage', 'untildate', models.DateTimeField(null=True)) # Renaming model 'ARhistoric' - db.rename_table('main_arhistoric', 'postfix_autoreply_arhistoric') + try: + db.rename_table('main_arhistoric', 'postfix_autoreply_arhistoric') + except Exception: + db.create_table('postfix_autoreply_arhistoric', ( + ('armessage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['postfix_autoreply.ARmessage'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('last_sent', self.gf('django.db.models.fields.DateTimeField')(auto_now=True)), + ('sender', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('postfix_autoreply', ['ARhistoric']) def backwards(self, orm): diff --git a/extensions/postfix_autoreply/scripts/autoreply.py b/extensions/postfix_autoreply/scripts/autoreply.py index cb6d346d0..a8ea3f596 100755 --- a/extensions/postfix_autoreply/scripts/autoreply.py +++ b/extensions/postfix_autoreply/scripts/autoreply.py @@ -29,7 +29,7 @@ if __name__ == "__main__": try: lastar = ARhistoric.objects.get(armessage=armessage.id, sender=sender) - parameters.get("postfix_autoreply", "AUTOREPLIES_TIMEOUT") + parameters.get_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT") delta = datetime.timedelta(seconds=timeout) if lastar.last_sent + delta > datetime.datetime.now(): sys.exit(0) diff --git a/extensions/stats/grapher.py b/extensions/stats/grapher.py index 3a2090757..d70ecc6e5 100644 --- a/extensions/stats/grapher.py +++ b/extensions/stats/grapher.py @@ -55,8 +55,8 @@ tpl = {'traffic':traffic_avg_template, class Grapher(object): def __init__(self): - self.rrd_rootdir = parameters.get("stats", "RRD_ROOTDIR") - self.img_rootdir = parameters.get("stats", "IMG_ROOTDIR") + self.rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR") + self.img_rootdir = parameters.get_admin("stats", "IMG_ROOTDIR") def process(self, target, suffix, start, end, tpl=traffic_avg_template): rrdfile = "%s/%s.rrd" % (self.rrd_rootdir, target) diff --git a/extensions/stats/logparser.py b/extensions/stats/logparser.py index f1c2aec56..ddc0899a0 100755 --- a/extensions/stats/logparser.py +++ b/extensions/stats/logparser.py @@ -276,8 +276,8 @@ class LogParser(object): G.make_defaults(dom, tpl=grapher.size_avg_template) if __name__ == "__main__": - log_file = parameters.get("stats", "LOGFILE") - rrd_rootdir = parameters.get("stats", "RRD_ROOTDIR") + log_file = parameters.get_admin("stats", "LOGFILE") + rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR") parser = OptionParser() parser.add_option("-l","--logFile", default=log_file, diff --git a/extensions/stats/main.py b/extensions/stats/main.py index 232cc0142..67eb45b0f 100644 --- a/extensions/stats/main.py +++ b/extensions/stats/main.py @@ -21,12 +21,15 @@ def infos(): def init(): events.register("AdminMenuDisplay", menu) - parameters.register("stats", "LOGFILE", "string", "/var/log/mail.log", - help=_("Path to log file used to collect statistics")) - parameters.register("stats", "RRD_ROOTDIR", "string", "/tmp/mailng", - help=_("Path to directory where RRD files are stored")) - parameters.register("stats", "IMG_ROOTDIR", "string", "/tmp/mailng", - help=_("Path to directory where PNG files are stored")) + parameters.register_admin("stats", "LOGFILE", type="string", + deflt="/var/log/mail.log", + help=_("Path to log file used to collect statistics")) + parameters.register_admin("stats", "RRD_ROOTDIR", type="string", + deflt="/tmp/mailng", + help=_("Path to directory where RRD files are stored")) + parameters.register_admin("stats", "IMG_ROOTDIR", type="string", + deflt="/tmp/mailng", + help=_("Path to directory where PNG files are stored")) def destroy(): events.unregister("AdminMenuDisplay", menu) diff --git a/extensions/stats/views.py b/extensions/stats/views.py index 1b515dddf..783f4b399 100644 --- a/extensions/stats/views.py +++ b/extensions/stats/views.py @@ -53,7 +53,7 @@ def graph_display(request,dom_id,graph_t=graph_types): "page" : "Domain statistics", "graph" : graph_nature, "domain": domain, "domains" : domains, "messages" : errors, "types" : graph_type, "tmp_path" : tmp_path, - "img_rootdir" : parameters.get("stats", "IMG_ROOTDIR")}) + "img_rootdir" : parameters.get_admin("stats", "IMG_ROOTDIR")}) @login_required @user_passes_test(lambda u: u.is_superuser) @@ -75,7 +75,7 @@ def adminindex(request): "graphs" : graph_list, "periods" : ["day", "week", "month", "year","Custom"], "period" : period, "cal" : CH_print, - "img_rootdir" : parameters.get("stats", "IMG_ROOTDIR")}) + "img_rootdir" : parameters.get_admin("stats", "IMG_ROOTDIR")}) @login_required @user_passes_test(lambda u: u.is_superuser) @@ -109,7 +109,7 @@ def custom_period(request): "start" : start_ref, "end" : end_ref, "messages" : ["Custom period not selected"], - "img_rootdir" : parameters.get("stats", "IMG_ROOTDIR")}) + "img_rootdir" : parameters.get_admin("stats", "IMG_ROOTDIR")}) period_name = "%s_%s" %(start.replace('/',''),end.replace('/','')) G = Grapher() @@ -130,7 +130,7 @@ def custom_period(request): "start" : start_ref, "end" : end_ref, "messages" : None, - "img_rootdir" : parameters.get("stats", "IMG_ROOTDIR")}) + "img_rootdir" : parameters.get_admin("stats", "IMG_ROOTDIR")}) @login_required @@ -147,4 +147,4 @@ def index(request, dom_id=None): "graphs" : graph_list, "periods" : ["day", "week", "month", "year","Custom"], "period" : period, - "img_rootdir" : parameters.get("stats", "IMG_ROOTDIR")}) + "img_rootdir" : parameters.get_admin("stats", "IMG_ROOTDIR")}) diff --git a/extensions/webmail/imap_listing.py b/extensions/webmail/imap_listing.py index d6e4c13d0..1f2469935 100644 --- a/extensions/webmail/imap_listing.py +++ b/extensions/webmail/imap_listing.py @@ -140,11 +140,9 @@ class IMAPconnector(object): __metaclass__ = ConnectionsManager def __init__(self, user=None, password=None): - print "IMAP constructor" - self.criterions = [] - self.address = parameters.get("webmail", "IMAP_SERVER") - self.port = int(parameters.get("webmail", "IMAP_PORT")) + self.address = parameters.get_admin("webmail", "IMAP_SERVER") + self.port = int(parameters.get_admin("webmail", "IMAP_PORT")) self.invalid = False status, msg = self.login(user, password) if not status: @@ -153,7 +151,7 @@ class IMAPconnector(object): def login(self, user, passwd): import imaplib try: - secured = parameters.get("webmail", "IMAP_SECURED") + secured = parameters.get_admin("webmail", "IMAP_SECURED") if secured == "yes": self.m = imaplib.IMAP4_SSL(self.address, self.port) else: @@ -321,7 +319,8 @@ class ImapListing(EmailListing): reset_wm_url = False def __init__(self, user, **kwargs): - self.mbc = IMAPconnector(user=user) + self.user = user + self.mbc = IMAPconnector(user=user.username) if kwargs.has_key("pattern"): self.parse_search_parameters(kwargs["criteria"], kwargs["pattern"]) @@ -345,8 +344,11 @@ class ImapListing(EmailListing): md_folders = [{"name" : "INBOX", "icon" : "overview.png"}, {"name" : 'Drafts'}, {"name" : 'Junk'}, - {"name" : 'Sent'}, - {"name" : 'Trash', "icon" : "trash.png"}] + {"name" : parameters.get_user(self.user, "webmail", + "SENT_FOLDER")}, + {"name" : parameters.get_user(self.user, "webmail", + "TRASH_FOLDER"), + "icon" : "trash.png"}] folders = self.mbc.listfolders(md_folders=md_folders) md_folders += self.__parse_folders(folders) for fd in md_folders: diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py index a40993e83..8757c92e7 100644 --- a/extensions/webmail/main.py +++ b/extensions/webmail/main.py @@ -8,20 +8,30 @@ def init(): events.register("UserMenuDisplay", menu) events.register("UserLogin", userlogin) events.register("UserLogout", userlogout) - parameters.register("webmail", "IMAP_SERVER", "string", "127.0.0.1", - help=_("Address of your IMAP server")) - parameters.register("webmail", "IMAP_SECURED", "list_yesno", "no", - help=_("Use a secured connection to access IMAP server")) - parameters.register("webmail", "IMAP_PORT", "int", "143", - help=_("Listening port of your IMAP server")) - parameters.register("webmail", "SMTP_SERVER", "string", "127.0.0.1", - help=_("Address of your SMTP server")) - parameters.register("webmail", "SMTP_PORT", "int", "25", - help=_("Listening port of your SMTP server")) - parameters.register("webmail", "SMTP_AUTHENTICATION", "list_yesno", "no", - help=_("Server needs authentication")) - parameters.register("webmail", "SMTP_SECURED", "list_yesno", "no", - help=_("Use a secured connection to access SMTP server")) + parameters.register_admin("webmail", "IMAP_SERVER", type="string", + deflt="127.0.0.1", + help=_("Address of your IMAP server")) + parameters.register_admin("webmail", "IMAP_SECURED", type="list_yesno", + deflt="no", + help=_("Use a secured connection to access IMAP server")) + parameters.register_admin("webmail", "IMAP_PORT", type="int", deflt="143", + help=_("Listening port of your IMAP server")) + parameters.register_admin("webmail", "SMTP_SERVER", type="string", + deflt="127.0.0.1", + help=_("Address of your SMTP server")) + parameters.register_admin("webmail", "SMTP_PORT", type="int", deflt="25", + help=_("Listening port of your SMTP server")) + parameters.register_admin("webmail", "SMTP_AUTHENTICATION", type="list_yesno", + deflt="no", + help=_("Server needs authentication")) + parameters.register_admin("webmail", "SMTP_SECURED", type="list_yesno", + deflt="no", + help=_("Use a secured connection to access SMTP server")) + + parameters.register_user("webmail", "TRASH_FOLDER", type="string", deflt="Trash", + help=_("Folder where deleted messages go")) + parameters.register_user("webmail", "SENT_FOLDER", type="string", deflt="Sent", + help=_("Folder where copies of sent messages go")) def destroy(): events.unregister("UserMenuDisplay", menu) diff --git a/extensions/webmail/views.py b/extensions/webmail/views.py index 467f53d81..da3da92f7 100644 --- a/extensions/webmail/views.py +++ b/extensions/webmail/views.py @@ -47,7 +47,7 @@ def folder(request, name, updatenav=True): optparams["criteria"] = request.session["criteria"] else: optparams["reset"] = True - lst = ImapListing(request.user.username, + lst = ImapListing(request.user, baseurl=name, folder=name, order=order, **optparams) page = lst.paginator.getpage(request.session["page"]) @@ -67,14 +67,14 @@ def folder(request, name, updatenav=True): @login_required @is_not_localadmin() def index(request): - #try: - navp = request.session.has_key("navparams") \ - and request.session["navparams"] or {} - lst = ImapListing(request.user.username, - baseurl="INBOX", navparams=navp, folder="INBOX", - empty=True) - #except Exception, exp: - # return _render_error(request, user_context={"error" : exp}) + try: + navp = request.session.has_key("navparams") \ + and request.session["navparams"] or {} + lst = ImapListing(request.user, + baseurl="INBOX", navparams=navp, folder="INBOX", + empty=True) + except Exception, exp: + return _render_error(request, user_context={"error" : exp}) return lst.render(request) def fetchmail(request, folder, mail_id, all=False): @@ -211,18 +211,19 @@ def send_mail(request, withctx=False, origmsg=None): msg["Cc"] = request.POST["cc"] rcpts += msg["Cc"].split(",") try: - s = smtplib.SMTP(parameters.get("webmail", "SMTP_SERVER")) - if parameters.get("webmail", "SMTP_SECURED") == "yes": + s = smtplib.SMTP(parameters.get_admin("webmail", "SMTP_SERVER")) + if parameters.get_admin("webmail", "SMTP_SECURED") == "yes": s.starttls() except (smtplib.SMTPException, ssl.SSLError), error: print error # Prévoir la remontée de cette erreur au niveau du client!! - if parameters.get("webmail", "SMTP_AUTHENTICATION") == "yes": + if parameters.get_admin("webmail", "SMTP_AUTHENTICATION") == "yes": s.login(request.user.username, request.session["password"]) s.sendmail(msg['From'], rcpts, msg.as_string()) s.quit() - IMAPconnector(user=request.user.username).push_mail("Sent", msg) + sentfolder = parameters.get_user(request.user, "webmail", "SENT_FOLDER") + IMAPconnector(user=request.user.username).push_mail(sentfolder, msg) ctx = getctx("ok", url=__get_current_url(request)) else: ctx = getctx("ko", level=2, listing=render_to_string("webmail/compose.html", diff --git a/lib/__init__.py b/lib/__init__.py index b5f2dbed2..1e474f520 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -25,7 +25,7 @@ def _render_error(request, errortpl="error", user_context={}): def exec_as_vuser(cmd): code, output = exec_cmd("sudo -u %s %s" \ - % (parameters.get("admin", "VIRTUAL_UID"), cmd)) + % (parameters.get_admin("admin", "VIRTUAL_UID"), cmd)) if code: exec_cmd("echo '%s' >> /tmp/vmail.log" % output) return False @@ -57,13 +57,13 @@ def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')): def is_not_localadmin(errortpl="error"): def dec(f): - def wrapped_f(request, **kwargs): + def wrapped_f(request, *args, **kwargs): if request.user.id == 1: return _render_error(request, errortpl, { "error" : _("Invalid action, %s is a local user" \ % request.user.username) }) - return f(request, **kwargs) + return f(request, *args, **kwargs) return wrapped_f return dec diff --git a/lib/authbackends.py b/lib/authbackends.py index 8acbe397a..e5a9af7b0 100644 --- a/lib/authbackends.py +++ b/lib/authbackends.py @@ -27,7 +27,7 @@ class SimpleBackend: return None def check_password(password, crypted): - scheme = parameters.get("admin", "PASSWORD_SCHEME") + scheme = parameters.get_admin("admin", "PASSWORD_SCHEME") if scheme == "crypt": return crypt.crypt(password, crypted) == crypted if scheme == "md5": @@ -35,7 +35,7 @@ def check_password(password, crypted): return password def crypt_password(password): - scheme = parameters.get("admin", "PASSWORD_SCHEME") + scheme = parameters.get_admin("admin", "PASSWORD_SCHEME") if scheme == "crypt": salt = ''.join(Random().sample(string.letters + string.digits, 2)) return crypt.crypt(password, salt) diff --git a/lib/migrations/0001_initial.py b/lib/migrations/0001_initial.py index 232f6a4cc..f35a2fdf9 100644 --- a/lib/migrations/0001_initial.py +++ b/lib/migrations/0001_initial.py @@ -15,20 +15,75 @@ class Migration(SchemaMigration): ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), )) db.send_create_signal('lib', ['Parameter']) + + # Adding model 'UserParameter' + db.create_table('lib_userparameter', ( + ('value', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + )) + db.send_create_signal('lib', ['UserParameter']) def backwards(self, orm): # Deleting model 'Parameter' db.delete_table('lib_parameter') + + # Deleting model 'UserParameter' + db.delete_table('lib_userparameter') 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']", 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'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']", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + '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']", 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'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'}) } } diff --git a/lib/models.py b/lib/models.py index 3268c719b..03098b980 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,5 +1,11 @@ from django.db import models +from django.contrib.auth.models import User class Parameter(models.Model): name = models.CharField(max_length=255) value = models.CharField(max_length=255) + +class UserParameter(models.Model): + user = models.ForeignKey(User) + name = models.CharField(max_length=255) + value = models.CharField(max_length=255) diff --git a/lib/parameters.py b/lib/parameters.py index 41ed6a529..d914c6ed4 100644 --- a/lib/parameters.py +++ b/lib/parameters.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import string -from mailng.lib.models import Parameter +from mailng.lib.models import * """ This interface provides a simple way to declare and store parameters @@ -14,6 +14,7 @@ Only super users will be able to access this part of the web interface. """ _params = {} +_levels = {'A' : 'admin', 'U' : 'user'} class NotDefined(Exception): def __init__(self, app, name): @@ -24,16 +25,32 @@ class NotDefined(Exception): return "Application '%s' and/or parameter '%s' not defined" \ % (self.app, self.name) -def register(app, name, type="string", deflt=None, help=None, **kwargs): +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(): + raise NotDefined(app, name) + +def __register(app, level, name, type="string", deflt=None, help=None, **kwargs): """Register a new parameter. app corresponds to a core component (admin, main) or an extension. """ if not app in _params.keys(): _params[app] = {} - _params[app][name] = {"type" : type, "default" : deflt, "help" : help} + for lvl in _levels.keys(): + _params[app][lvl] = {} + if not level in _levels.keys(): + return + _params[app][level][name] = {"type" : type, "default" : deflt, "help" : help} for k, v in kwargs.iteritems(): - _params[app][name][k] = v + _params[app][level][name][k] = v + +def register_admin(app, name, **kwargs): + return __register(app, 'A', name, **kwargs) + +def register_user(app, name, **kwargs): + return __register(app, 'U', name, **kwargs) def unregister_app(app): if not _params.has_key(app): @@ -41,9 +58,9 @@ def unregister_app(app): del _params[app] return True -def save(app, name, value): - if not app in _params.keys() or not name in _params[app].keys(): - raise NotDefined(app, name) +def save_admin(app, name, value): + __is_defined(app, 'A', name) + fullname = "%s.%s" % (app, name) try: p = Parameter.objects.get(name=fullname) @@ -55,11 +72,32 @@ def save(app, name, value): p.save() return True -def get(app, name): - if not app in _params.keys() or not name in _params[app].keys(): - raise NotDefined(app, name) +def save_user(user, app, name, value): + __is_defined(app, 'U', name) + fullname = "%s.%s" % (app, name) + try: + p = UserParameter.objects.get(user=user, name=fullname) + except UserParameter.DoesNotExist: + p = UserParameter() + p.user = user + p.name = fullname + if p.value != value: + p.value = value + p.save() + return True + +def get_admin(app, name): + __is_defined(app, "A", name) try: p = Parameter.objects.get(name="%s.%s" % (app, name)) except Parameter.DoesNotExist: - return _params[app][name]["default"] + return _params[app]["A"][name]["default"] + return p.value + +def get_user(user, app, name): + __is_defined(app, "U", name) + try: + p = UserParameter.objects.get(user=user, name="%s.%s" % (app, name)) + except UserParameter.DoesNotExist: + return _params[app]["U"][name]["default"] return p.value diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index f8a8f324a..000000000 --- a/static/js/main.js +++ /dev/null @@ -1,6 +0,0 @@ -window.addEvent('domready', function() { - new DatePicker("#untildate", { - pickerClass: "datepicker_vista", - allowEmpty: true - }); -}); diff --git a/static/js/preferences.js b/static/js/preferences.js new file mode 100644 index 000000000..6f948c92a --- /dev/null +++ b/static/js/preferences.js @@ -0,0 +1,18 @@ +window.addEvent('domready', function() { + var tip = new Tips($$(".Tips")); + var accordion = new Accordion('h3.atStart', 'div.atStart', { + opacity: true, + onActive: function(toggler, element){ + toggler.setStyle('color', '#ff3300'); + }, + + onBackground: function(toggler, element){ + toggler.setStyle('color', '#222'); + } + }, $('accordion')); + + new DatePicker("#untildate", { + pickerClass: "datepicker_vista", + allowEmpty: true + }); +}); diff --git a/static/js/settings.js b/static/js/settings.js index e61c6b4bb..acdb6d31b 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -8,7 +8,7 @@ window.addEvent('domready', function() { var tip = new Tips($$(".Tips")); var accordion = new Accordion('h3.atStart', 'div.atStart', { - opacity: false, + opacity: true, onActive: function(toggler, element){ toggler.setStyle('color', '#ff3300'); }, diff --git a/templates/admin/parameters.html b/templates/admin/parameters.html index 7f5d2b518..5a14fff4f 100644 --- a/templates/admin/parameters.html +++ b/templates/admin/parameters.html @@ -4,19 +4,8 @@ {% settings_menu "parameters" user %} {% endblock %} {% block setcontent %} -{% load i18n %} {% url admin.views.saveparameters as action %}
-
- {% for app in gparams %} -

{{ app.name }}

-
- {% for p in app.params %} - {% param app.name p %} - {% endfor %} -
- {% endfor %} -
-

+ {% include "common/parameters.html" %}
{% endblock %} diff --git a/templates/common/parameters.html b/templates/common/parameters.html new file mode 100644 index 000000000..494f41cde --- /dev/null +++ b/templates/common/parameters.html @@ -0,0 +1,13 @@ +{% load i18n %} +{% load admin_extras %} +

+
+ {% for app in gparams %} +

{{ app.name }}

+
+ {% for p in app.params %} + {% param app.name p %} + {% endfor %} +
+ {% endfor %} +
diff --git a/templates/userprefs/header.html b/templates/userprefs/header.html index a9bdddaf9..e7fbcdcb8 100644 --- a/templates/userprefs/header.html +++ b/templates/userprefs/header.html @@ -2,7 +2,7 @@ {% load i18n %} {% block html_head %} - + {% endblock %} {% block middletitle %}{% trans "General" %}{% endblock %} diff --git a/templates/userprefs/preferences.html b/templates/userprefs/preferences.html new file mode 100644 index 000000000..d19161820 --- /dev/null +++ b/templates/userprefs/preferences.html @@ -0,0 +1,11 @@ +{% extends "nlayout.html" %} +{% load i18n %} +{% block html_head %} + +{% endblock %} +{% block middletitle %}{% trans "Preferences" %}{% endblock %} +{% block content %} +
+ {% include "common/parameters.html" %} +
+{% endblock %} diff --git a/urls.py b/urls.py index c585a472f..a53119662 100644 --- a/urls.py +++ b/urls.py @@ -40,7 +40,7 @@ if settings.DEBUG: urlpatterns += patterns( '', (r'^img/stats/(?P.*)/$', 'django.views.static.serve', - {'document_root': parameters.get("stats", "IMG_ROOTDIR")}) + {'document_root': parameters.get_admin("stats", "IMG_ROOTDIR")}) ) diff --git a/userprefs/templatetags/uprefs_extras.py b/userprefs/templatetags/uprefs_extras.py index ec51ecdff..f1ca0f19e 100644 --- a/userprefs/templatetags/uprefs_extras.py +++ b/userprefs/templatetags/uprefs_extras.py @@ -12,7 +12,7 @@ def uprefs_menu(user): entries = [ {"name" : "userprefs", "img" : "/static/pics/user.png", - "label" : _("Preferences"), + "label" : _("Options"), "class" : "topdropdown", "menu" : [ {"name" : "changepwd", @@ -20,9 +20,14 @@ def uprefs_menu(user): "img" : "/static/pics/edit.png", "label" : _("Change password"), "class" : "boxed", - "rel" : "{handler:'iframe',size:{x:350,y:220}}"} + "rel" : "{handler:'iframe',size:{x:350,y:220}}"}, + {"name" : "preferences", + "img" : "/static/pics/user.png", + "label" : _("Preferences"), + "url" : reverse(userprefs.views.preferences), + } ] - } + }, ] entries[0]["menu"] += events.raiseQueryEvent("UserMenuDisplay", target="user_menu_bar") diff --git a/userprefs/urls.py b/userprefs/urls.py index f5d683abe..abde3d01d 100644 --- a/userprefs/urls.py +++ b/userprefs/urls.py @@ -2,6 +2,8 @@ from django.conf.urls.defaults import * urlpatterns = patterns('mailng.userprefs.views', url(r'^$', 'index'), + (r'^preferences/$', 'preferences'), + (r'^preferences/save/$', 'savepreferences'), (r'^changepassword/$', 'changepassword'), (r'^confirm/$', 'confirm'), ) diff --git a/userprefs/views.py b/userprefs/views.py index ff343717e..f3b2bb253 100644 --- a/userprefs/views.py +++ b/userprefs/views.py @@ -8,7 +8,7 @@ from django.contrib.auth.decorators \ import login_required from django.utils import simplejson from django.utils.translation import ugettext as _ -from mailng.lib import _render, _ctx_ok, _ctx_ko +from mailng.lib import _render, _ctx_ok, _ctx_ko, parameters from mailng.lib.authbackends import crypt_password from forms import ChangePasswordForm from mailng.admin.models import Mailbox @@ -17,12 +17,6 @@ from mailng.admin.models import Mailbox def index(request): mb = Mailbox.objects.get(user=request.user.id) return _render(request, "userprefs/index.html", {}) - # try: - # arm = ARmessage.objects.filter(mbox=mb.id, enabled=True) - # except ARmessage.DoesNotExist: - # arm = None - # return _render(request, "userprefs/index.html", {"arm" : arm}) - @login_required def changepassword(request): @@ -46,6 +40,40 @@ def changepassword(request): "form" : form }) +@login_required +def preferences(request): + apps = sorted(parameters._params.keys()) + gparams = [] + for app in apps: + if not len(parameters._params[app]['U']): + continue + tmp = {"name" : app, "params" : []} + for p in sorted(parameters._params[app]['U']): + param_def = parameters._params[app]['U'][p] + newdef = {"name" : p, + "value" : parameters.get_user(request.user, app, p), + "help" : param_def["help"], + "default" : param_def["default"], + "type" : param_def["type"]} + if "values" in param_def.keys(): + newdef["values"] = param_def["values"] + tmp["params"] += [newdef] + gparams += [tmp] + + return _render(request, 'userprefs/preferences.html', { + "gparams" : gparams + }) + +@login_required +def savepreferences(request): + for pname, v in request.POST.iteritems(): + if pname == "update": + continue + app, name = pname.split('.') + parameters.save_user(request.user, app, name, v) + + return HttpResponseRedirect(reverse(preferences)) + @login_required def confirm(request): return render_to_response('userprefs/confirm.html', {