From c01d6de7d8739a80957ba4b5ad69ef8acf346080 Mon Sep 17 00:00:00 2001 From: tonio Date: Fri, 17 Sep 2010 09:23:35 +0200 Subject: [PATCH] fix: return receipts are now displayed correctly. new: parameters module refactored. The API is more simple now, there is no need to provide a module name anymore, this last is automatically guessed. Also, a new 'label' field can be provide when declaring a new parameter. It will be used instead of the name. new: email parsing refactored. My first attempt was really ugly. I did not really studied the various RFC describing how messages are formatted or what IMAP fetch queries can do. Now, I hope this new code will face better unknown types. At least, It will be simpler to update. --- admin/__init__.py | 12 +- admin/models.py | 16 +- admin/templatetags/admin_extras.py | 5 +- admin/views.py | 10 +- extensions/amavis_quarantine/cleanup.py | 2 +- extensions/amavis_quarantine/lib.py | 8 +- extensions/amavis_quarantine/main.py | 10 +- extensions/amavis_quarantine/views.py | 3 +- extensions/postfix_autoreply/main.py | 2 +- .../postfix_autoreply/scripts/autoreply.py | 3 +- extensions/stats/grapher.py | 4 +- extensions/stats/main.py | 6 +- extensions/stats/scripts/logparser.py | 4 +- extensions/stats/views.py | 2 +- extensions/webmail/lib.py | 157 ++++++++++++++--- extensions/webmail/main.py | 36 ++-- extensions/webmail/templates/headers.html | 4 +- extensions/webmail/templatetags/webextras.py | 2 +- extensions/webmail/views.py | 138 ++++++--------- lib/__init__.py | 7 +- lib/authbackends.py | 4 +- lib/email_listing.py | 160 +++++++++++------- lib/parameters.py | 42 +++-- templates/common/getmailcontent.html | 1 - templates/common/viewmail.html | 2 +- urls.py | 2 +- userprefs/views.py | 13 +- 27 files changed, 398 insertions(+), 257 deletions(-) delete mode 100644 templates/common/getmailcontent.html diff --git a/admin/__init__.py b/admin/__init__.py index 198800420..2e5ab907b 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -3,17 +3,17 @@ from modoboa.lib import parameters from django.utils.translation import ugettext as _ -parameters.register_admin("admin","STORAGE_PATH", type="string", deflt="/var/vmail", +parameters.register_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", +parameters.register_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", +parameters.register_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", +parameters.register_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", +parameters.register_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", +parameters.register_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 9a80433ea..92c2b1618 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("admin", "STORAGE_PATH"), self.name) + path = "%s/%s" % (parameters.get_admin("STORAGE_PATH"), self.name) return exec_as_vuser("mkdir -p %s" % path) def rename_dir(self, newname): - stpath = parameters.get_admin("admin", "STORAGE_PATH") + stpath = parameters.get_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("admin", "STORAGE_PATH"), + % (parameters.get_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("admin", "MAILBOX_TYPE") - self.mdirroot = parameters.get_admin("admin", "MAILDIR_ROOT") + self.mbtype = parameters.get_admin("MAILBOX_TYPE") + self.mdirroot = parameters.get_admin("MAILDIR_ROOT") def __str__(self): return "%s" % (self.address) def create_dir(self, domain): - path = "%s/%s/%s" % (parameters.get_admin("admin", "STORAGE_PATH"), + path = "%s/%s/%s" % (parameters.get_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("admin", "STORAGE_PATH"), domain) + path = "%s/%s" % (parameters.get_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("admin", "STORAGE_PATH"), + % (parameters.get_admin("STORAGE_PATH"), self.domain.name, self.address)) class Alias(models.Model): diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py index c6448e6be..fcba18626 100644 --- a/admin/templatetags/admin_extras.py +++ b/admin/templatetags/admin_extras.py @@ -97,10 +97,11 @@ def loadadminextmenu(user): @register.simple_tag def param(app, definition): result = """
- """ % definition["name"] + """ % (definition.has_key("label") \ + and definition["label"] or definition["name"]) name = "%s.%s" % (app, definition["name"]) value = definition.has_key("value") \ - and definition["value"] or definition["default"] + and definition["value"] or definition["deflt"] if definition["type"] in ["string", "int"]: result += """ diff --git a/admin/views.py b/admin/views.py index 6be08a981..65edc96c3 100644 --- a/admin/views.py +++ b/admin/views.py @@ -182,8 +182,8 @@ def newmailbox(request, dom_id=None): mb.password = crypt_password(request.POST["password1"]) - 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.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid + mb.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid mb.domain = domain mb.quota = request.POST["quota"] if not mb.quota: @@ -422,9 +422,9 @@ def viewparameters(request): for p in sorted(parameters._params[app]['A']): param_def = parameters._params[app]['A'][p] newdef = {"name" : p, - "value" : parameters.get_admin(app, p), + "value" : parameters.get_admin(p, app=app), "help" : param_def["help"], - "default" : param_def["default"], + "default" : param_def["deflt"], "type" : param_def["type"]} if "values" in param_def.keys(): newdef["values"] = param_def["values"] @@ -442,6 +442,6 @@ def saveparameters(request): if pname == "update": continue app, name = pname.split('.') - parameters.save_admin(app, name, v) + parameters.save_admin(name, v, app=app) ctx = getctx("ok") return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") diff --git a/extensions/amavis_quarantine/cleanup.py b/extensions/amavis_quarantine/cleanup.py index f1fe261b1..13c0adab3 100755 --- a/extensions/amavis_quarantine/cleanup.py +++ b/extensions/amavis_quarantine/cleanup.py @@ -8,7 +8,7 @@ if __name__ == "__main__": main.init() conn = db.getconnection("amavis_quarantine") - max_messages_age = int(parameters.get_admin("amavis_quarantine", "MAX_MESSAGES_AGE")) + max_messages_age = int(parameters.get_admin("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 96909f12f..afc5e29d5 100644 --- a/extensions/amavis_quarantine/lib.py +++ b/extensions/amavis_quarantine/lib.py @@ -6,14 +6,14 @@ from modoboa.lib import parameters class AMrelease(object): def __init__(self): - mode = parameters.get_admin("amavis_quarantine", "AM_PDP_MODE") + mode = parameters.get_admin("AM_PDP_MODE") if mode == "inet": - host = parameters.get_admin("amavis_quarantine", 'AM_PDP_HOST') - port = parameters.get_admin("amavis_quarantine", 'AM_PDP_PORT') + host = parameters.get_admin('AM_PDP_HOST') + port = parameters.get_admin('AM_PDP_PORT') self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, int(port))) else: - path = parameters.get_admin("amavis_quarantine", 'AM_PDP_SOCKET') + path = parameters.get_admin('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 3b5b7f3c7..35632f1f9 100644 --- a/extensions/amavis_quarantine/main.py +++ b/extensions/amavis_quarantine/main.py @@ -16,20 +16,20 @@ def infos(): def init(): events.register("UserMenuDisplay", menu) - parameters.register_admin("amavis_quarantine", "MAX_MESSAGES_AGE", type="int", + parameters.register_admin("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", + parameters.register_admin("AM_PDP_MODE", type="list", deflt="unix", values=[("inet", "inet"), ("unix", "unix")], help=_("Mode used to access the PDP server")) - parameters.register_admin("amavis_quarantine", "AM_PDP_HOST", type="string", + parameters.register_admin("AM_PDP_HOST", type="string", deflt="localhost", help=_("PDP server address (if inet mode)")) - parameters.register_admin("amavis_quarantine", "AM_PDP_PORT", type="int", + parameters.register_admin("AM_PDP_PORT", type="int", deflt=9998, help=_("PDP server port (if inet mode)")) - parameters.register_admin("amavis_quarantine", "AM_PDP_SOCKET", type="string", + parameters.register_admin("AM_PDP_SOCKET", type="string", deflt="/var/amavis/amavisd.sock", help=_("Path to the PDP server socket (if unix mode)")) diff --git a/extensions/amavis_quarantine/views.py b/extensions/amavis_quarantine/views.py index 9305287aa..5fb2ca2d5 100644 --- a/extensions/amavis_quarantine/views.py +++ b/extensions/amavis_quarantine/views.py @@ -89,8 +89,7 @@ WHERE quarantine.mail_id='%s' mail = SQLemail(msg, mode, links) return _render(request, "common/viewmail.html", { "headers" : mail.render_headers(), - "mailbody" : mail.body, - "pre" : mail.pre + "mailbody" : mail.body }) @login_required diff --git a/extensions/postfix_autoreply/main.py b/extensions/postfix_autoreply/main.py index b21eea112..49de5f02d 100644 --- a/extensions/postfix_autoreply/main.py +++ b/extensions/postfix_autoreply/main.py @@ -28,7 +28,7 @@ def init(): events.register("ModifyMailbox", onModifyMailbox) events.register("UserMenuDisplay", menu) - parameters.register_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT", + parameters.register_admin("AUTOREPLIES_TIMEOUT", type="int", deflt=86400, help=_("Timeout in seconds between two auto-replies to the same recipient")) diff --git a/extensions/postfix_autoreply/scripts/autoreply.py b/extensions/postfix_autoreply/scripts/autoreply.py index 506ab3a6e..759766b89 100755 --- a/extensions/postfix_autoreply/scripts/autoreply.py +++ b/extensions/postfix_autoreply/scripts/autoreply.py @@ -31,7 +31,8 @@ if __name__ == "__main__": try: lastar = ARhistoric.objects.get(armessage=armessage.id, sender=sender) main.init() - timeout = parameters.get_admin("postfix_autoreply", "AUTOREPLIES_TIMEOUT") + timeout = parameters.get_admin("AUTOREPLIES_TIMEOUT", + app="postfix_autoreply") delta = datetime.timedelta(seconds=int(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 5b8006fa2..b71c7fa84 100644 --- a/extensions/stats/grapher.py +++ b/extensions/stats/grapher.py @@ -73,8 +73,8 @@ def str2Time(y, M, d, h ="0", m ="0", s="0"): class Grapher(object): def __init__(self): - self.rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR") - self.img_rootdir = parameters.get_admin("stats", "IMG_ROOTDIR") + self.rrd_rootdir = parameters.get_admin("RRD_ROOTDIR") + self.img_rootdir = parameters.get_admin("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/main.py b/extensions/stats/main.py index 653ec23a1..99703afbd 100644 --- a/extensions/stats/main.py +++ b/extensions/stats/main.py @@ -21,13 +21,13 @@ def infos(): def init(): events.register("AdminMenuDisplay", menu) - parameters.register_admin("stats", "LOGFILE", type="string", + parameters.register_admin("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", + parameters.register_admin("RRD_ROOTDIR", type="string", deflt="/tmp/modoboa", help=_("Path to directory where RRD files are stored")) - parameters.register_admin("stats", "IMG_ROOTDIR", type="string", + parameters.register_admin("IMG_ROOTDIR", type="string", deflt="/tmp/modoboa", help=_("Path to directory where PNG files are stored")) diff --git a/extensions/stats/scripts/logparser.py b/extensions/stats/scripts/logparser.py index ab581922e..9c1258dd4 100755 --- a/extensions/stats/scripts/logparser.py +++ b/extensions/stats/scripts/logparser.py @@ -261,8 +261,8 @@ if __name__ == "__main__": main.init() - log_file = parameters.get_admin("stats", "LOGFILE") - rrd_rootdir = parameters.get_admin("stats", "RRD_ROOTDIR") + log_file = parameters.get_admin("LOGFILE") + rrd_rootdir = parameters.get_admin("RRD_ROOTDIR") parser = OptionParser() parser.add_option("-l","--logfile", default=log_file, diff --git a/extensions/stats/views.py b/extensions/stats/views.py index 2453f15e5..7a1308214 100644 --- a/extensions/stats/views.py +++ b/extensions/stats/views.py @@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from django.utils import simplejson from django.template.loader import render_to_string -from modoboa.lib import parameters, _render, getctx +from modoboa.lib import _render, getctx from modoboa.admin.views import good_domain from modoboa.admin.models import Domain, Mailbox from django.contrib.auth.decorators \ diff --git a/extensions/webmail/lib.py b/extensions/webmail/lib.py index 3e4e993b8..a7bbe25a1 100644 --- a/extensions/webmail/lib.py +++ b/extensions/webmail/lib.py @@ -154,8 +154,8 @@ class IMAPconnector(object): def __init__(self, user=None, password=None): self.criterions = [] - self.address = parameters.get_admin("webmail", "IMAP_SERVER") - self.port = int(parameters.get_admin("webmail", "IMAP_PORT")) + self.address = parameters.get_admin("IMAP_SERVER") + self.port = int(parameters.get_admin("IMAP_PORT")) status, msg = self.login(user, password) if not status: raise Exception(msg) @@ -177,7 +177,7 @@ class IMAPconnector(object): def login(self, user, passwd): import imaplib try: - secured = parameters.get_admin("webmail", "IMAP_SECURED") + secured = parameters.get_admin("IMAP_SECURED") if secured == "yes": self.m = imaplib.IMAP4_SSL(self.address, self.port) else: @@ -278,10 +278,8 @@ class IMAPconnector(object): md_folders = [{"name" : "INBOX", "class" : "inbox"}, {"name" : 'Drafts'}, {"name" : 'Junk'}, - {"name" : parameters.get_user(user, "webmail", - "SENT_FOLDER")}, - {"name" : parameters.get_user(user, "webmail", - "TRASH_FOLDER"), + {"name" : parameters.get_user(user, "SENT_FOLDER")}, + {"name" : parameters.get_user(user, "TRASH_FOLDER"), "class" : "trash"}] md_folders += self._listfolders(md_folders=md_folders) for fd in md_folders: @@ -341,6 +339,15 @@ class IMAPconnector(object): self.quota_limit = int(m.group(2)) self.quota_actual = int(m.group(1)) + def fetchpart(self, uid, folder, part): + self.m.select(self._encodefolder(folder), True) + typ, data = self.m.fetch(uid, "(BODY[%(p)s.MIME] BODY[%(p)s])" \ + % {"p" : part}) + if typ != "OK": + return None + msg = email.message_from_string(data[0][1] + data[1][1]) + return msg + def fetch(self, start=None, stop=None, folder=None, all=False): if not start and not stop: return [] @@ -360,6 +367,7 @@ class IMAPconnector(object): if not folder: folder = "INBOX" tmpdict = {} + for response_part in data: if isinstance(response_part, tuple): imapid = response_part[0].split()[0] @@ -392,7 +400,7 @@ class ImapListing(EmailListing): self.mbc.criterions = [] EmailListing.__init__(self, **kwargs) self.extravars["refreshrate"] = \ - int(parameters.get_user(user, "webmail", "REFRESH_INTERVAL")) * 1000 + int(parameters.get_user(user, "REFRESH_INTERVAL")) * 1000 def parse_search_parameters(self, criterion, pattern): def or_criterion(old, c): @@ -422,8 +430,11 @@ class ImapListing(EmailListing): class ImapEmail(Email): - def __init__(self, msg, addrfull=False, *args, **kwargs): - Email.__init__(self, msg, *args, **kwargs) + def __init__(self, msg, user, dformat="DISPLAYMODE", addrfull=False, + *args, **kwargs): + mformat = parameters.get_user(user, "DISPLAYMODE") + dformat = parameters.get_user(user, dformat) + Email.__init__(self, msg, mformat, dformat, *args, **kwargs) fields = ["Subject", "From", "To", "Reply-To", "Cc", "Date"] for f in fields: @@ -447,19 +458,124 @@ class ImapEmail(Email): def render_headers(self, **kwargs): from django.template.loader import render_to_string - attachments = [] - for part in self.attachments: - decoded = decode_header(part.get_filename()) - if decoded[0][1] is None: - attachments += [decoded[0][0]] - else: - attachments += [unicode(decoded[0][0], decoded[0][1])] return render_to_string("webmail/headers.html", { "headers" : self.headers, "folder" : kwargs["folder"], "mail_id" : kwargs["mail_id"], - "attachments" : attachments != [] and attachments or None + "attachments" : self.attachments != {} and self.attachments or None }) +class ReplyModifier(ImapEmail): + def __init__(self, msg, user, form, all=False, **kwargs): + ImapEmail.__init__(self, msg, user, dformat="EDITOR", **kwargs) + + self.textheader = "%s %s" % (self.From, _("wrote:")) + getattr(self, "_modify_%s" % self.dformat)() + + form.fields["from_"].initial = user.username + if not "Reply-To" in msg.keys(): + form.fields["to"].initial = self.From + else: + form.fields["to"].initial = self.Reply_To + if all: + form.fields["cc"].initial = "" + toparse = msg["To"].split(",") + if "Cc" in msg.keys(): + toparse += msg["Cc"].split(",") + for addr in toparse: + tmp = EmailAddress(addr) + if tmp.address and tmp.address == request.user.username: + continue + if form.fields["cc"].initial != "": + form.fields["cc"].initial += ", " + form.fields["cc"].initial += tmp.fulladdress + m = re.match("re\s*:\s*.+", self.Subject.lower()) + if m: + form.fields["subject"].initial = self.Subject + else: + form.fields["subject"].initial = "Re: %s" % self.Subject + + def _modify_plain(self): + lines = self.body.split('\n') + body = "" + for l in lines: + if body != "": + body += "\n" + body += ">%s" % l + self.body = body + + def _modify_html(self): + pass + +class ForwardModifier(ImapEmail): + def __init__(self, msg, user, form, **kwargs): + ImapEmail.__init__(self, msg, user, dformat="EDITOR", **kwargs) + + self._header(msg) + form.fields["from_"].initial = user.username + form.fields["subject"].initial = "Fwd: %s" % self.Subject + + def __getfunc(self, name): + return getattr(self, "%s_%s" % (name, self.dformat)) + + def _header(self, msg): + self.textheader = self.__getfunc("_header_begin")() + "\n" + self.textheader += \ + self.__getfunc("_header_line")(_("Subject"), self.Subject) + "\n" + self.textheader += \ + self.__getfunc("_header_line")(_("Date"), msg["Date"]) + "\n" + for hdr in ["From", "To", "Reply-To"]: + try: + key = re.sub("-", "_", hdr) + value = getattr(self, key) + self.textheader += \ + self.__getfunc("_header_line")(_(hdr), value) + "\n" + except: + pass + self.textheader += self.__getfunc("_header_end")() + + def _header_begin_plain(self): + return "----- %s -----" % _("Original message") + + def _header_begin_html(self): + return """----- %s -----
+""" % _("Original message") + + def _header_line_plain(self, key, value): + return "%s: %s" % (key, value) + + def _header_line_html(self, key, value): + return " " % (key, value) + + def _header_end_plain(self): + return "\n" + + def _header_end_html(self): + return "
%s%s
" + +class EmailSignature(object): + """User signature + + :param user: User object + """ + def __init__(self, user): + dformat = parameters.get_user(user, "EDITOR") + content = parameters.get_user(user, "SIGNATURE") + getattr(self, "_format_sig_%s" % dformat)(content) + + def _format_sig_plain(self, content): + self._sig = """ +--- +%s""" % content + + def _format_sig_html(self, content): + content = re.sub("\n", "
", content) + self._sig = """
+---
+%s""" % content + + def __repr__(self): + return self._sig + def find_images_in_body(body): """Looks for images inside a HTML body @@ -497,7 +613,7 @@ def encrypt(clear): from Crypto.Cipher import AES import base64 - obj = AES.new(parameters.get_admin("webmail", "SECRET_KEY"), + obj = AES.new(parameters.get_admin("SECRET_KEY"), AES.MODE_ECB) if len(clear) % AES.block_size: clear += "\0" * (AES.block_size - len(clear) % AES.block_size) @@ -509,8 +625,7 @@ def decrypt(ciph): from Crypto.Cipher import AES import base64 - obj = AES.new(parameters.get_admin("webmail", "SECRET_KEY"), - AES.MODE_ECB) + obj = AES.new(parameters.get_admin("SECRET_KEY"), AES.MODE_ECB) ciph = base64.b64decode(ciph) clear = obj.decrypt(ciph) return clear.rstrip('\0') diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py index b81733ca6..82308ebb7 100644 --- a/extensions/webmail/main.py +++ b/extensions/webmail/main.py @@ -9,40 +9,48 @@ def init(): events.register("UserLogin", userlogin) events.register("UserLogout", userlogout) - parameters.register_admin("webmail", "SECRET_KEY", type="string", - deflt="abcdefghijklmnop", + parameters.register_admin("SECRET_KEY", type="string", deflt="abcdefghijklmnop", help=_("Key used to encrypt/decrypt passwords")) - parameters.register_admin("webmail", "IMAP_SERVER", type="string", + parameters.register_admin("IMAP_SERVER", type="string", deflt="127.0.0.1", help=_("Address of your IMAP server")) - parameters.register_admin("webmail", "IMAP_SECURED", type="list_yesno", + parameters.register_admin("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", + parameters.register_admin("IMAP_PORT", type="int", deflt="143", help=_("Listening port of your IMAP server")) - parameters.register_admin("webmail", "SMTP_SERVER", type="string", + parameters.register_admin("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", + parameters.register_admin("SMTP_PORT", type="int", deflt="25", help=_("Listening port of your SMTP server")) - parameters.register_admin("webmail", "SMTP_AUTHENTICATION", type="list_yesno", + parameters.register_admin("SMTP_AUTHENTICATION", type="list_yesno", deflt="no", help=_("Server needs authentication")) - parameters.register_admin("webmail", "SMTP_SECURED", type="list_yesno", + parameters.register_admin("SMTP_SECURED", type="list_yesno", deflt="no", help=_("Use a secured connection to access SMTP server")) - parameters.register_user("webmail", "REFRESH_INTERVAL", type="int", deflt=300, + parameters.register_user("REFRESH_INTERVAL", type="int", deflt=300, + label=_("Listing refresh rate"), help=_("Automatic folder refresh rate (in seconds)")) - parameters.register_user("webmail", "TRASH_FOLDER", type="string", deflt="Trash", + parameters.register_user("TRASH_FOLDER", type="string", deflt="Trash", + label=_("Trash folder"), help=_("Folder where deleted messages go")) - parameters.register_user("webmail", "SENT_FOLDER", type="string", deflt="Sent", + parameters.register_user("SENT_FOLDER", type="string", deflt="Sent", + label=_("Sent folder"), help=_("Folder where copies of sent messages go")) - parameters.register_user("webmail", "SIGNATURE", type="text", default="", + parameters.register_user("SIGNATURE", type="text", deflt="", + label=_("Signature text"), help=_("User defined email signature")) - parameters.register_user("webmail", "EDITOR", type="list", deflt="text", + parameters.register_user("EDITOR", type="list", deflt="plain", + label=_("Default editor"), values=[("html", "html"), ("plain", "text")], help=_("The default editor to use when composing a message")) + parameters.register_user("DISPLAYMODE", type="list", deflt="plain", + label=_("Default message display mode"), + values=[("html", "html"), ("plain", "text")], + help=_("The default mode used when displaying a message")) def destroy(): events.unregister("UserMenuDisplay", menu) diff --git a/extensions/webmail/templates/headers.html b/extensions/webmail/templates/headers.html index 1dc5c534f..2afddd1da 100644 --- a/extensions/webmail/templates/headers.html +++ b/extensions/webmail/templates/headers.html @@ -7,8 +7,8 @@ {% trans "Attachments" %} {% url extensions.webmail.views.getattachment folder mail_id as test %} - {% for attachment in attachments %} - {{ attachment }} + {% for key, fname in attachments.items %} + {{ fname }} {% endfor %} diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py index 90939ab57..7f434dc96 100644 --- a/extensions/webmail/templatetags/webextras.py +++ b/extensions/webmail/templatetags/webextras.py @@ -87,7 +87,7 @@ def listing_menu(selection, folder, user): ] } ] - if folder == parameters.get_user(user, "webmail", "TRASH_FOLDER"): + if folder == parameters.get_user(user, "TRASH_FOLDER"): entries[2]["menu"] += [ {"name" : "fdaction", "label" : _("Empty folder"), diff --git a/extensions/webmail/views.py b/extensions/webmail/views.py index fcd5b3b31..7f3d2c0a3 100644 --- a/extensions/webmail/views.py +++ b/extensions/webmail/views.py @@ -116,43 +116,48 @@ def getmailcontent(request, folder, mail_id): if "class" in msg.keys() and msg["class"] == "unseen": IMAPconnector(user=request.user.username, password=request.session["password"]).msg_read(folder, mail_id) - email = ImapEmail(msg, mode="html", links="1") + email = ImapEmail(msg, request.user, links="1") try: pageid = request.session["page"] except KeyError: pageid = "1" return _render(request, "common/viewmail.html", { "headers" : email.render_headers(folder=folder, mail_id=mail_id), - "folder" : folder, "imapid" : mail_id, "mailbody" : email.body, - "pre" : email.pre + "folder" : folder, "imapid" : mail_id, "mailbody" : email.body }) @login_required @is_not_localadmin() def getattachment(request, folder, mail_id): - msg = fetchmail(request, folder, mail_id, True) - for part in msg.walk(): - fname = part.get_filename() - if fname is None: - continue - decoded = decode_header(fname) - if decoded[0][1] is None: - fname = unicode(decoded[0][0]) - else: - fname = unicode(decoded[0][0], decoded[0][1]) - if fname == request.GET["fname"]: - resp = HttpResponse(part.get_payload(decode=1)) - for hdr in ["Content-Type", "Content-Transfer-Encoding"]: + if request.GET.has_key("partnumber"): + headers = {"Content-Type" : "text/plain", + "Content-Transfer-Encoding" : None} + icon = IMAPconnector(user=request.user.username, + password=request.session["password"]) + part = icon.fetchpart(mail_id, folder, request.GET["partnumber"]) + if part is not None: + if part.get_content_maintype() == "message": + payload = part.get_payload(0) + else: + payload = part.get_payload(decode=True) + resp = HttpResponse(payload) + for hdr, default in headers.iteritems(): if not part.has_key(hdr): - continue + if default is not None: + resp[hdr] = default + else: + continue resp[hdr] = re.sub("\s", "", part[hdr]) + # I would add this part into the previous loop if was able + # to use functions as default values... but I'm a bit lazy + # :p if part.has_key("Content-Disposition"): resp["Content-Disposition"] = \ re.sub("\s", "", part["Content-Disposition"]) else: resp["Content-Disposition"] = \ - "attachment; filename=%s" % fname - resp["Content-Length"] = len(resp.content) + "attachment; filename=%s" % request.GET["fname"] + resp["Content-Length"] = len(payload) return resp raise Http404 @@ -172,8 +177,7 @@ def move(request): def delete(request, fdname, mail_id): mbc = IMAPconnector(user=request.user.username, password=request.session["password"]) - mbc.move(mail_id, fdname, parameters.get_user(request.user, "webmail", - "TRASH_FOLDER")) + mbc.move(mail_id, fdname, parameters.get_user(request.user, "TRASH_FOLDER")) ctx = getctx("ok", next=__get_current_url(request)) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") @@ -193,7 +197,7 @@ def mark(request, name): @login_required @is_not_localadmin() def empty(request, name): - if name == parameters.get_user(request.user, "webmail", "TRASH_FOLDER"): + if name == parameters.get_user(request.user, "TRASH_FOLDER"): mbc = IMAPconnector(user=request.user.username, password=request.session["password"]) mbc.empty(name) @@ -207,22 +211,23 @@ def compact(request, name): mbc.compact(name) return folder(request, name, False) -def render_compose(request, form, posturl, bodyheader=None, body=None, - insert_signature=False): +def render_compose(request, form, posturl, email=None, insert_signature=False): menu = compose_menu("", __get_current_url(request), request.user.get_all_permissions()) - editor = parameters.get_user(request.user, "webmail", "EDITOR") + editor = parameters.get_user(request.user, "EDITOR") + if email is None: + body = "" + textheader = "" + else: + body = email.body + textheader = email.textheader if insert_signature: - if body == None: - body = "" - signature = parameters.get_user(request.user, "webmail", "SIGNATURE") - if signature != "": - body += editor == "plain" and "\n---\n%s" % signature \ - or "
---
%s" % re.sub("\n", "
", signature) + signature = EmailSignature(request.user) + body += str(signature) content = render_to_string("webmail/compose.html", { - "form" : form, "bodyheader" : bodyheader, "body" : body, - "posturl" : posturl + "form" : form, "bodyheader" : textheader, + "body" : body, "posturl" : posturl }) mbc = IMAPconnector(user=request.user.username, password=request.session["password"]) @@ -240,7 +245,7 @@ def send_mail(request, withctx=False, origmsg=None, posturl=None): from email.utils import make_msgid, formatdate import smtplib - subtype = parameters.get_user(request.user, "webmail", "EDITOR") + subtype = parameters.get_user(request.user, "EDITOR") body = request.POST["id_body"] if subtype == "html": from email.mime.multipart import MIMEMultipart @@ -267,17 +272,17 @@ def send_mail(request, withctx=False, origmsg=None, posturl=None): rcpts += msg["Cc"].split(",") error = None try: - s = smtplib.SMTP(parameters.get_admin("webmail", "SMTP_SERVER")) - if parameters.get_admin("webmail", "SMTP_SECURED") == "yes": + s = smtplib.SMTP(parameters.get_admin("SMTP_SERVER")) + if parameters.get_admin("SMTP_SECURED") == "yes": s.starttls() except (smtplib.SMTPException, ssl.SSLError), text: error = text if error is None: - if parameters.get_admin("webmail", "SMTP_AUTHENTICATION") == "yes": + if parameters.get_admin("SMTP_AUTHENTICATION") == "yes": s.login(request.user.username, decrypt(request.session["password"])) s.sendmail(msg['From'], rcpts, msg.as_string()) s.quit() - sentfolder = parameters.get_user(request.user, "webmail", "SENT_FOLDER") + sentfolder = parameters.get_user(request.user, "SENT_FOLDER") IMAPconnector(user=request.user.username, password=request.session["password"]).push_mail(sentfolder, msg) ctx = getctx("ok", url=__get_current_url(request)) @@ -315,39 +320,12 @@ def reply(request, folder, mail_id): password=request.session["password"]).msg_answered(folder, mail_id) return r - email = ImapEmail(msg, True) - lines = email.body.split('\n') - body = "" - for l in lines: - if body != "": - body += "\n" - body += ">%s" % l - form = ComposeMailForm() - form.fields["from_"].initial = request.user.username - if not "Reply-To" in msg.keys(): - form.fields["to"].initial = email.From - else: - form.fields["to"].initial = email.Reply_To - if request.GET.has_key("all"): - form.fields["cc"].initial = "" - toparse = msg["To"].split(",") - if "Cc" in msg.keys(): - toparse += msg["Cc"].split(",") - for addr in toparse: - tmp = EmailAddress(addr) - if tmp.address and tmp.address == request.user.username: - continue - if form.fields["cc"].initial != "": - form.fields["cc"].initial += ", " - form.fields["cc"].initial += tmp.fulladdress - m = re.match("re\s*:\s*.+", email.Subject.lower()) - if m: - form.fields["subject"].initial = email.Subject - else: - form.fields["subject"].initial = "Re: %s" % email.Subject - textheader = "%s %s" % (email.From, _("wrote:")) + + form = ComposeMailForm() + email = ReplyModifier(msg, request.user, form, request.GET.has_key("full"), + addrfull=True) return render_compose(request, form, reverse(reply, args=[folder, mail_id]), - textheader, body) + email) @login_required @is_not_localadmin() @@ -360,23 +338,9 @@ def forward(request, folder, mail_id): password=request.session["password"]).msgforwarded(folder, mail_id) return response - + msg = fetchmail(request, folder, mail_id, True) - email = ImapEmail(msg, True) - textheader = "----- %s -----" % _("Original message") - textheader += "\n%s: %s" % (_("Subject"), email.Subject) - textheader += "\n%s: %s" % (_("Date"), msg["Date"]) - for hdr in ["From", "To", "Reply-To"]: - try: - key = re.sub("-", "_", hdr) - value = getattr(email, key) - textheader += "\n%s: %s" % (_(hdr), value) - except: - pass - textheader += "\n" - form = ComposeMailForm() - form.fields["from_"].initial = request.user.username - form.fields["subject"].initial = "Fwd: %s" % email.Subject + email = ForwardModifier(msg, request.user, form, addrfull=True) return render_compose(request, form, reverse(forward, args=[folder, mail_id]), - textheader, email.body) + email) diff --git a/lib/__init__.py b/lib/__init__.py index a74d4ef36..bb8dd1c3f 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -26,7 +26,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("admin", "VIRTUAL_UID"), cmd)) + % (parameters.get_admin("VIRTUAL_UID", app="admin"), cmd)) if code: exec_cmd("echo '%s' >> /tmp/vmail.log" % output) return False @@ -57,7 +57,10 @@ def getctx(status, level=1, callback=None, **kwargs): ctx[kw] = v return ctx -def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')): +def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii'), charset=None): + if charset is not None: + return s.decode(charset, 'ignore') + for encoding in encodings: try: return s.decode(encoding) diff --git a/lib/authbackends.py b/lib/authbackends.py index 4505d563b..b63c9f943 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("admin", "PASSWORD_SCHEME") + scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") 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("admin", "PASSWORD_SCHEME") + scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") if scheme == "crypt": salt = ''.join(Random().sample(string.letters + string.digits, 2)) return crypt.crypt(password, salt) diff --git a/lib/email_listing.py b/lib/email_listing.py index b1cb9c326..25da6cb0d 100644 --- a/lib/email_listing.py +++ b/lib/email_listing.py @@ -140,67 +140,109 @@ class EmailListing: return _render(request, self.tpl, context) class Email(object): - def __init__(self, msg, mode="plain", links="0"): + def __init__(self, msg, mformat="plain", dformat="plain", links="0"): self.attached_map = {} - contents = {"html" : "", "plain" : ""} + self.contents = {"html" : "", "plain" : ""} self.headers = [] - self.attachments = [] - for part in msg.walk(): - if part.get_content_maintype() == 'multipart': - continue - if part.has_key("Content-Disposition"): - if re.match("^attachment", part["Content-Disposition"]): - self.attachments += [part] - continue - if part.get_content_type() == "text/calendar": - contents["plain"] += part.get_payload(decode=True) - continue - if part.get_content_type() in ("text/html", "text/plain"): - contents[part.get_content_subtype()] += part.get_payload(decode=True) - if mode != "html" or links == "0": - continue - if part.get_content_maintype() == "application": - self.attachments += [part] - continue + self.attachments = {} + self.mformat = mformat + self.dformat = dformat + self.links = links - if part.get_content_maintype() == "image": - m = re.match("<(.+)>", part["Content-ID"]) - cid = m is not None and m.group(1) or part["Content-ID"] + self.__parse(msg) - if part.has_key("Content-Location"): - fname = part["Content-Location"] - elif part.has_key("Content-Disposition"): - if not part["Content-Disposition"].startswith("inline"): - continue - params = part["Content-Disposition"].split(";") - fname = None - for p in params: - lst = p.strip().split("=") - if len(p) and p[0] == "filename": - fname = p[1] - break - if fname is None: + if not self.contents.has_key(mformat) or self.contents[mformat] == "": + # Fallback + self.mformat = mformat == "html" and "plain" or "html" + + self.body = \ + getattr(self, "viewmail_%s" % self.mformat) \ + (self.contents[self.mformat], links=links) + + def __parse_default(self, msg, level): + """Default parser + + All parts handled by this parser will be consireded as + attachments. + """ + fname = msg.get_filename() + if fname is not None: + decoded = decode_header(fname) + value = decoded[0][1] is None and decoded[0][0] \ + or unicode(decoded[0][0], decoded[0][1]) + else: + value = "part_%s" % level + self.attachments[level] = value + + def _parse_text(self, msg, level): + """Displayable content parser + + text, html, calendar, etc. All those contents can be displayed + inside a navigator. + """ + if msg.get_content_subtype() == "calendar": + target = "plain" + else: + target = msg.get_content_subtype() + self.contents[target] += decode(msg.get_payload(decode=True), + charset=msg.get_content_charset()) + + def _parse_image(self, msg, level): + """image/* parser + + The only reason to make a specific parser for images is that, + sometimes, messages embark inline images, which means they + must be displayed and not attached. + """ + if self.dformat == "html" and self.links != "0" \ + and msg.has_key("Content-Disposition"): + if msg["Content-Disposition"].startswith("inline"): + cid = None + if msg.has_key("Content-ID"): + m = re.match("<(.+)>", msg["Content-ID"]) + cid = m is not None and m.group(1) or msg["Content-ID"] + fname = msg.get_filename() + if fname is None: + if msg.has_key("Content-Location"): + fname = msg["Content-Location"] + elif cid is not None: fname = cid - elif part.has_key("Content-Type"): - m = re.match(".*name=[\"'](.+)[\"']", part["Content-Type"]) - if m is None: - continue - fname = m.group(1) - else: - continue - + else: + # I give up for now :p + return self.attached_map[cid] = re.match("^http:", fname) and fname \ - or self.__save_image(fname, part) + or self.__save_image(fname, msg) + return + self.__parse_default(msg, level) + def __parse(self, msg, level=None): + """Recursive email parser - if not contents.has_key(mode) or contents[mode] == "": - if mode == "html": - mode = "plain" - else: - mode = "html" + A message structure can be complex. To correctly handle + unknown MIME types, a simple rule is applied : if I don't know + how to display a specific part, it becomes an attachment! If + no name is specified for an attachment, the part number + described in the RFC 3501 (which retrieves BODY sections) is + used to build a file name (like part_1.1). - self.pre, self.body = \ - getattr(self, "viewmail_%s" % mode)(contents[mode], links=links) + :param msg: message (or part) to parse + :param level: current part number + """ + if msg.is_multipart() and msg.get_content_maintype() != "message": + cpt = 1 + for part in msg.get_payload(): + nlevel = level is None and ("%d" % cpt) \ + or "%s.%d" % (level, cpt) + self.__parse(part, nlevel) + cpt += 1 + return + + if level is None: + level = "1" + try: + getattr(self, "_parse_%s" % msg.get_content_maintype())(msg, level) + except AttributeError: + self.__parse_default(msg, level) def __save_image(self, fname, part): """Save an inline image on the filesystem. @@ -226,7 +268,6 @@ class Email(object): m = re.match(".*cid:(.+)", url) if m: - print self.attached_map if self.attached_map.has_key(m.group(1)): return self.attached_map[m.group(1)] return url @@ -236,14 +277,9 @@ class Email(object): "headers" : self.headers, }) - def render(self, request): - return _render(request, 'common/getmailcontent.html', { - "body" : self.body, "pre" : self.pre - }) - def viewmail_plain(self, content, **kwargs): - body = decode(content) - return (True, body) + return self.dformat == "html" and "
%s
" % content \ + or content def viewmail_html(self, content, **kwargs): if content is None or content == "": @@ -260,8 +296,8 @@ class Email(object): else: body = lxml.html.tostring(body) body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body) - body = Template(decode(body)).render(Context({})) - return (False, body) + body = Template(body).render(Context({})) + return body def parse_search_parameters(request): if request.GET.has_key("pattern"): diff --git a/lib/parameters.py b/lib/parameters.py index dbf567505..040778464 100644 --- a/lib/parameters.py +++ b/lib/parameters.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import string +import inspect +import re from modoboa.lib.models import * """ @@ -31,25 +33,34 @@ def __is_defined(app, level, name): or not name in _params[app][level].keys(): raise NotDefined(app, name) -def __register(app, level, name, type="string", deflt=None, help=None, **kwargs): +def __register(app, level, name, **kwargs): """Register a new parameter. app corresponds to a core component (admin, main) or an extension. - """ + """ if not app in _params.keys(): _params[app] = {} 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} + _params[app][level][name] = kwargs for k, v in kwargs.iteritems(): _params[app][level][name][k] = v -def register_admin(app, name, **kwargs): +def __guess_extension(): + modname = inspect.getmodule(inspect.stack()[2][0]).__name__ + m = re.match("(?:modoboa\.)?(?:extensions\.)?([^\.$]+)", modname) + if m: + return m.group(1) + return None + +def register_admin(name, **kwargs): + app = kwargs.has_key("app") and kwargs["app"] or __guess_extension() return __register(app, 'A', name, **kwargs) -def register_user(app, name, **kwargs): +def register_user(name, **kwargs): + app = kwargs.has_key("app") and kwargs["app"] or __guess_extension() return __register(app, 'U', name, **kwargs) def unregister_app(app): @@ -58,9 +69,10 @@ def unregister_app(app): del _params[app] return True -def save_admin(app, name, value): +def save_admin(name, value, app=None): + if app is None: + app = __guess_extension() __is_defined(app, 'A', name) - fullname = "%s.%s" % (app, name) try: p = Parameter.objects.get(name=fullname) @@ -72,7 +84,9 @@ def save_admin(app, name, value): p.save() return True -def save_user(user, app, name, value): +def save_user(user, name, value, app=None): + if app is None: + app = __guess_extension() __is_defined(app, 'U', name) fullname = "%s.%s" % (app, name) try: @@ -86,18 +100,22 @@ def save_user(user, app, name, value): p.save() return True -def get_admin(app, name): +def get_admin(name, app=None): + if app is None: + app = __guess_extension() __is_defined(app, "A", name) try: p = Parameter.objects.get(name="%s.%s" % (app, name)) except Parameter.DoesNotExist: - return _params[app]["A"][name]["default"] + return _params[app]["A"][name]["deflt"] return p.value.decode("string_escape") -def get_user(user, app, name): +def get_user(user, name, app=None): + if app is None: + app = __guess_extension() __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 _params[app]["U"][name]["deflt"] return p.value.decode("string_escape") diff --git a/templates/common/getmailcontent.html b/templates/common/getmailcontent.html deleted file mode 100644 index 872bdaf88..000000000 --- a/templates/common/getmailcontent.html +++ /dev/null @@ -1 +0,0 @@ -{% if pre %}
{% endif %}{{ body }}{% if pre %}
{% endif %} diff --git a/templates/common/viewmail.html b/templates/common/viewmail.html index fff25b5d3..7f64d2539 100644 --- a/templates/common/viewmail.html +++ b/templates/common/viewmail.html @@ -49,6 +49,6 @@
{{ headers }}
- {% if pre %}
{% endif %}{{ mailbody }}{% if pre %}
{% endif %} + {{ mailbody|safe }} diff --git a/urls.py b/urls.py index 389021b66..f7f00963c 100644 --- a/urls.py +++ b/urls.py @@ -25,7 +25,7 @@ if settings.DEBUG: urlpatterns += patterns( '', (r'^img/stats/(?P.*)/$', 'django.views.static.serve', - {'document_root': parameters.get_admin("stats", "IMG_ROOTDIR")}) + {'document_root': parameters.get_admin("IMG_ROOTDIR", app="stats")}) ) diff --git a/userprefs/views.py b/userprefs/views.py index 22658e1a1..3131dc7e4 100644 --- a/userprefs/views.py +++ b/userprefs/views.py @@ -1,3 +1,4 @@ +import copy from django.shortcuts import get_object_or_404, render_to_response from django.template.loader import render_to_string from django.template import RequestContext @@ -55,13 +56,9 @@ def preferences(request): 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"] + newdef = copy.deepcopy(param_def) + newdef["name"] = p + newdef["value"] = parameters.get_user(request.user, p, app=app) tmp["params"] += [newdef] gparams += [tmp] @@ -75,7 +72,7 @@ def savepreferences(request): if pname == "update": continue app, name = pname.split('.') - parameters.save_user(request.user, app, name, v) + parameters.save_user(request.user, name, v, app=app) ctx = getctx("ok") return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")