From 809be7c01785d0c841a7d6a6ca1ef4a36e00f895 Mon Sep 17 00:00:00 2001 From: tonio Date: Fri, 22 Jan 2010 18:55:36 +0100 Subject: [PATCH] Against : webmail navigation improvement... It's now possible to write and send text emails. --- extensions/amavis_quarantine/sql_listing.py | 10 +- extensions/webmail/forms.py | 8 ++ extensions/webmail/imap_listing.py | 11 ++- extensions/webmail/main.py | 97 +++++++++++++------ extensions/webmail/templates/compose.html | 48 +++++++++ extensions/webmail/templates/headers.html | 2 +- extensions/webmail/templates/index.html | 76 +++++++++------ extensions/webmail/templatetags/webextras.py | 21 +++- extensions/webmail/urls.py | 1 + lib/email_listing.py | 15 ++- static/js/common.js | 4 +- static/pics/send-receive.png | Bin 0 -> 540 bytes templates/common/email_listing.html | 6 +- 13 files changed, 226 insertions(+), 73 deletions(-) create mode 100644 extensions/webmail/forms.py create mode 100644 extensions/webmail/templates/compose.html create mode 100644 static/pics/send-receive.png diff --git a/extensions/amavis_quarantine/sql_listing.py b/extensions/amavis_quarantine/sql_listing.py index f4744adef..5e29fc3e7 100644 --- a/extensions/amavis_quarantine/sql_listing.py +++ b/extensions/amavis_quarantine/sql_listing.py @@ -16,6 +16,12 @@ class Qtable(tables.Table): def parse_date(self, value): return datetime.fromtimestamp(value) + def parse(self, header, value): + try: + return getattr(self, "parse_%s" % header)(value) + except AttributeError: + return value + class SQLconnector(MBconnector): def __init__(self, filter=None): self.conn = db.getconnection("amavis_quarantine") @@ -76,10 +82,8 @@ LIMIT %d,%d class SQLlisting(EmailListing): tpl = "amavis_quarantine/index.html" + tbltype = Qtable def __init__(self, filter, **kwargs): self.mbc = SQLconnector(filter) EmailListing.__init__(self, **kwargs) - - def fetch(self, id_start, id_stop): - return Qtable(self.mbc.fetch(start=id_start, stop=id_stop)) diff --git a/extensions/webmail/forms.py b/extensions/webmail/forms.py new file mode 100644 index 000000000..21c2bd86f --- /dev/null +++ b/extensions/webmail/forms.py @@ -0,0 +1,8 @@ +from django import forms +from django.utils.translation import ugettext as _ + +class ComposeMailForm(forms.Form): + from_ = forms.CharField(label=_("From")) + to = forms.CharField(label=_("To"), widget=forms.Textarea()) + subject = forms.CharField(label=_("Subject"), max_length=255) + diff --git a/extensions/webmail/imap_listing.py b/extensions/webmail/imap_listing.py index 2ee488a29..b5a2d33ce 100644 --- a/extensions/webmail/imap_listing.py +++ b/extensions/webmail/imap_listing.py @@ -119,6 +119,10 @@ class IMAPconnector(MBconnector): result += [name] return sorted(result) + def msgseen(self, imapid): + folder, id = imapid.split("/") + self.m.store(id, "+FLAGS", "\\Seen") + def fetch(self, start=None, stop=None, folder=None, all=False): if not start and not stop: return [] @@ -155,9 +159,10 @@ class IMAPconnector(MBconnector): class ImapListing(EmailListing): tpl = "webmail/index.html" + tbltype = WMtable def __init__(self, user, password, baseurl=None, **kwargs): - self.mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143) + self.mbc = IMAPconnector(parameters.get("webmail", "IMAP_SERVER"), 143) status, text = self.mbc.login(user, password) if not status: print "Login error: %s" % text @@ -175,7 +180,3 @@ class ImapListing(EmailListing): for fd in folders: md_folders += [{"name" : fd}] return md_folders - - def fetch(self, id_start, id_stop): - return WMtable(self.mbc.fetch(start=id_start, stop=id_stop, - folder=self.folder)) diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py index 444d05008..a3e7b18e6 100644 --- a/extensions/webmail/main.py +++ b/extensions/webmail/main.py @@ -11,12 +11,18 @@ from mailng.admin.models import Mailbox from mailng.lib import events, parameters from mailng.lib import _render from imap_listing import * +from forms import * +from templatetags.webextras import * + +mbc = None def init(): events.register("UserMenuDisplay", menu) events.register("UserLogin", userlogin) - parameters.register("webmail", "SERVER_ADDRESS", "string", "127.0.0.1", - help=_("Mailboxes server address")) + parameters.register("webmail", "IMAP_SERVER", "string", "127.0.0.1", + help=_("Address of your IMAP server")) + parameters.register("webmail", "SMTP_SERVER", "string", "127.0.0.1", + help=_("Address of your SMTP server")) def urls(): return (r'^mailng/webmail/', @@ -34,32 +40,40 @@ def menu(**kwargs): def userlogin(**kwargs): kwargs["request"].session["password"] = kwargs["password"] +def folder_ajax(request, name, pageid, menu=False): + lst = ImapListing(request.user.username, request.session["password"], + reverse(folder, args=[name]), folder=name) + page = lst.paginator.getpage(pageid) + if page: + content = lst.fetch(request, page.id_start, page.id_stop) + navbar = lst.render_navbar(page) + else: + content = "Empty folder" + navbar = "" + ctx = {"status" : "ok", "listing" : content, "navbar" : navbar} + if menu or "menu" in request.GET.keys(): + from templatetags.webextras import listing_menu + ctx["menu"] = listing_menu("", request.user.get_all_permissions()) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + @login_required def folder(request, name=None): if not name: name = "INBOX" + pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1 + request.session["folder"] = name + request.session["page"] = pageid + if "mode" in request.GET.keys() and request.GET["mode"] == "ajax": + return folder_ajax(request, name, pageid) lst = ImapListing(request.user.username, request.session["password"], reverse(folder, args=[name]), folder=name) - pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1 - if "mode" in request.GET.keys() and request.GET["mode"] == "ajax": - page = lst.paginator.getpage(pageid) - if page: - content = lst.fetch(page.id_start, page.id_stop).render(request) - navbar = lst.render_navbar(page) - else: - content = "Empty folder" - navbar = "" - ctx = {"status" : "ok", "listing" : content, "navbar" : navbar, - "menu" : "", "current_page" : pageid} - if "menu" in request.GET.keys(): - from templatetags.webextras import listing_menu - ctx["menu"] = listing_menu("", request.user.get_all_permissions()) - return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") return lst.render(request, pageid=int(pageid)) def fetchmail(request, folder, mail_id, all=False): - mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143) - mbc.login(request.user.username, request.session["password"]) + global mbc + if not mbc: + mbc = IMAPconnector(parameters.get("webmail", "IMAP_SERVER"), 143) + mbc.login(request.user.username, request.session["password"]) res = mbc.fetch(start=mail_id, folder=folder, all=all) if len(res): return res[0] @@ -68,26 +82,55 @@ def fetchmail(request, folder, mail_id, all=False): @login_required def viewmail(request, folder, mail_id=None): header = fetchmail(request, folder, mail_id) - pageid = request.GET.has_key("page") and request.GET["page"] or "1" if header: from templatetags.webextras import viewm_menu - menu = viewm_menu("", folder, mail_id, pageid, + menu = viewm_menu("", request.session["folder"], mail_id, + request.session["page"], request.user.get_all_permissions()) folder, imapid = header["imapid"].split("/") mailcontent = render_to_string("webmail/viewmail.html", { "header" : header, "folder" : folder, "imapid" : imapid }) - ctx = {"status" : "ok", "menu" : menu, "mailcontent" : mailcontent} + ctx = {"status" : "ok", "menu" : menu, "listing" : mailcontent} return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") -# return _render(request, "webmail/viewmail.html", { -# "header" : header, "folder" : folder, "pageid" : pageid, -# "menu" : menu -# }) @login_required def getmailcontent(request, folder, mail_id): from mailng.lib.email_listing import Email msg = fetchmail(request, folder, mail_id, True) - email = Email(msg) + if "class" in msg.keys() and msg["class"] == "unseen": + global mbc + mbc.msgseen(msg["imapid"]) + + email = Email(msg, mode="html", links="1") return email.render(request) + +@login_required +def compose(request): + if request.method == "POST": + form = ComposeMailForm(request.POST) + if form.is_valid(): + from email.mime.text import MIMEText + import smtplib + + msg = MIMEText(request.POST["id_body"].encode("utf-8"), + _charset="utf-8") + msg["Subject"] = request.POST["subject"] + msg["From"] = request.POST["from_"] + msg["to"] = request.POST["to"] + s = smtplib.SMTP(parameters.get("webmail", "SMTP_SERVER")) + s.sendmail(msg['From'], [msg['To']], msg.as_string()) + s.quit() + return folder_ajax(request, request.session["folder"], + request.session["page"], True) + + form = ComposeMailForm() + form.fields["from_"].initial = request.user.username + menu = compose_menu("", request.session["folder"], request.session["page"], + request.user.get_all_permissions()) + content = render_to_string("webmail/compose.html", { + "form" : form + }) + ctx = {"status" : "ok", "menu" : menu, "listing" : content} + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") diff --git a/extensions/webmail/templates/compose.html b/extensions/webmail/templates/compose.html new file mode 100644 index 000000000..86450e4da --- /dev/null +++ b/extensions/webmail/templates/compose.html @@ -0,0 +1,48 @@ +{% load i18n %} + + +{% url extensions.webmail.main.compose as compurl %} +
+
+
+ {% include "common/genericform.html" %} +
+ +
+
diff --git a/extensions/webmail/templates/headers.html b/extensions/webmail/templates/headers.html index fa570da53..244d108df 100644 --- a/extensions/webmail/templates/headers.html +++ b/extensions/webmail/templates/headers.html @@ -1,5 +1,5 @@ {% for hdr in headers %} - + {% endfor %}
{{ hdr.name }}{{ hdr.value }}
{{ hdr.name }}{{ hdr.value }}
diff --git a/extensions/webmail/templates/index.html b/extensions/webmail/templates/index.html index 68c7b95e7..0a9c16af8 100644 --- a/extensions/webmail/templates/index.html +++ b/extensions/webmail/templates/index.html @@ -46,59 +46,81 @@ } {% endblock %} {% block js %} + updatelisting = function(resp, overflow) { + if ($defined(resp.menu)) { + $("menubar").set("html", resp.menu); + } + $("listing").set("html", resp.listing); + $("listing").setStyle("overflow", overflow); + if ($defined(resp.navbar)) { + $("navbar").set("html", resp.navbar); + $("navbar").getChildren("a").addEvent("click", navbarcallback); + } + $$("a[name=compose]").addEvent("click", loadCompose); + } + {% url extensions.webmail.main.folder as view_url %} loadMail = function(event) { event.stop(); if (event.target.parentNode.id) { - var url = "{{ view_url }}" + event.target.parentNode.id + "?page=" - + $("pagenum").value; + var url = "{{ view_url }}" + event.target.parentNode.id; event.target.parentNode.setStyle("background", "#ffffcc"); new Request.JSON({url: url, onSuccess: function(resp) { - $("menubar").set("html", resp.menu); - $("listing").set("html", resp.mailcontent); - $("listing").setStyle("overflow", "hidden"); - $$("a[name=back]").addEvent("click", loadfolder); + updatelisting(resp, "hidden"); + $$("a[name=back]").addEvent("click", loadFolder); setDivHeight("mailcontent", $("table-container").getSize().y, 0); }}).get(); } } - updatelisting = function(resp) { - if (resp.menu != "") { - $("menubar").set("html", resp.menu); - } - $("pagenum").value = resp.current_page; - $("listing").set("html", resp.listing); - $("listing").setStyle("overflow", "auto"); - $$("tr").addEvent("click", loadMail); - $("navbar").set("html", resp.navbar); - $("navbar").getChildren("a").addEvent("click", navbarcallback); - } - navbarcallback = function(event) { + + loadCompose = function(event) { event.stop(); - new Request.JSON({url: event.target.getParent().get("href"), onSuccess: function(resp) { - updatelisting(resp); + new Request.JSON({url: event.target, onSuccess: function(resp) { + updatelisting(resp, "hidden"); + $$("a[name=back]").addEvent("click", loadFolder); + $$("a[name=sendmail]").addEvent("click", function(evt) { + evt.stop(); + $("composemail").set("send", { + onSuccess: function(res) { + updatelisting(JSON.decode(res)); + $$("tr").addEvent("click", loadMail); + } + }); + $("composemail").send(); + }); + setDivHeight("id_body", $("mailheader").getSize().y + 20, 0); + }}).get(); + } + + loadFolder = function(event, obj) { + obj = typeof(obj) != "undefined" ? obj : event.target; + event.stop(); + new Request.JSON({url: obj.get("href"), onSuccess: function(resp) { + updatelisting(resp, "auto"); + $$("tr").addEvent("click", loadMail); }}).get({"mode" : "ajax"}); } + + navbarcallback = function(event) { + loadFolder(event, event.target.getParent()); + } + select = function(obj) { $$("a[name=loadfolder]").each(function(item) { item.getParent().setStyle("background", "white"); }); obj.getParent().setStyle("background", "#FFFFCC"); } - loadfolder = function(event) { - event.stop(); - new Request.JSON({url: event.target.get("href"), onSuccess: function(resp) { - updatelisting(resp); - }}).get({"mode" : "ajax"}); - } + foldercallback = function(event) { select(event.target); - loadfolder(event); + loadFolder(event, event.target); } $$("a[name=loadfolder]").addEvent("click", foldercallback); $("navbar").getChildren("a").addEvent("click", navbarcallback); $$("tr").addEvent("click", loadMail); + $$("a[name=compose]").addEvent("click", loadCompose); {% endblock %} {% block middletitle %}{% trans "Webmail" %}{% endblock %} {% block menu %} diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py index d948db506..6e9c86a9d 100644 --- a/extensions/webmail/templatetags/webextras.py +++ b/extensions/webmail/templatetags/webextras.py @@ -21,11 +21,28 @@ def viewm_menu(selection, folder, mail_id, page_id, perms): {"selection" : selection, "entries" : entries, "perms" : perms}) +@register.simple_tag +def compose_menu(selection, folder, page_id, perms): + entries = [ + {"name" : "back", + "url" : reverse(webmail.main.folder, args=[folder]) \ + + "?page=%s" % page_id + "&menu=1", + "img" : "/static/pics/back.png", + "label" : _("Back to list")}, + {"name" : "sendmail", + "url" : "", + "img" : "/static/pics/send-receive.png", + "label" : _("Send")}, + ] + return render_to_string('main/menu.html', + {"selection" : selection, "entries" : entries, + "perms" : perms}) + @register.simple_tag def listing_menu(selection, perms): entries = [ - {"name" : "newmail", - "url" : "", + {"name" : "compose", + "url" : reverse(webmail.main.compose), "img" : "/static/pics/edit.png", "label" : _("New message")} ] diff --git a/extensions/webmail/urls.py b/extensions/webmail/urls.py index 6997d4b7c..5dc10bfb4 100644 --- a/extensions/webmail/urls.py +++ b/extensions/webmail/urls.py @@ -2,6 +2,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('mailng.extensions.webmail.main', url(r'^$', "folder", name="index"), + (r'^compose/$', "compose"), (r'^(?P.+)/(?P[\w\-\+]+)/content/$', 'getmailcontent'), (r'^(?P.+)/(?P[\w\-\+]+)/$', 'viewmail'), url(r'^(?P.+)/$', "folder", name="folder"), diff --git a/lib/email_listing.py b/lib/email_listing.py index 71ae04db3..8b8f96352 100644 --- a/lib/email_listing.py +++ b/lib/email_listing.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- import time +import re import lxml +import lxml.html from django.template.loader import render_to_string -from django.template import Template +from django.template import Template, Context from mailng.lib import _render, decode attached_map = {} @@ -89,12 +91,21 @@ class EmailListing: def getfolders(self): return None + def fetch(self, request, id_start, id_stop): + table = self.tbltype(self.mbc.fetch(start=id_start, stop=id_stop, + folder=self.folder)).render(request) + tpl = Template(""" +
+ {{ table }} +
""") + return tpl.render(Context({"table" : table})) + def render(self, request, pageid=1, **kwargs): page = self.paginator.getpage(pageid) if not page: listing = "Empty folder" else: - listing = self.fetch(page.id_start, page.id_stop).render(request) + listing = self.fetch(request, page.id_start, page.id_stop) elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0 return _render(request, self.tpl, { "listing" : listing, "elapsed" : elapsed, diff --git a/static/js/common.js b/static/js/common.js index 081fe2a54..9940cb67f 100644 --- a/static/js/common.js +++ b/static/js/common.js @@ -75,9 +75,11 @@ setDivHeight = function(id, extrah, modulo) { var contentsize = $(document.body).getSize().y - $("topmenubar").getSize().y - $("header").getSize().y - - ($("navbar").getSize().y) - extrah; + if ($("navbar")) { + contentsize -= $("navbar").getSize().y; + } if (modulo) { contentsize -= contentsize % modulo; } diff --git a/static/pics/send-receive.png b/static/pics/send-receive.png new file mode 100644 index 0000000000000000000000000000000000000000..3eb6a9ce3f05fef318648a3e317ac3f438dd0f2e GIT binary patch literal 540 zcmV+%0^|LOP)*Y>8!Nbe_rV5w`+@g{%~_O!X=kJZzh!1^&`{Vi{T2qWCt>OBAT_pH!7 z{L)-W*T6!`=O{p+$L$Q?+-Ze1wt~c<##X@EzGU`WD?l(+105j4qVoA;18ch%XvC`fDYmjCIGA5tpmuA zSBTfBR=ZmtAMIgrYOMkA86Z~(!ffi8SYTSytjSzj)_6LVIeQHH$BDFJ3ke_sdx - -
{{ listing }}
- +
{{ listing }}
{% endblock %}