mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-20 06:08:35 +02:00
Many webmail improvements.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import re
|
||||
import time
|
||||
import imaplib
|
||||
from datetime import datetime, timedelta
|
||||
from email.header import decode_header
|
||||
import email.utils
|
||||
@@ -10,34 +11,69 @@ from mailng.lib import decode, tables, imap_utf7
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing
|
||||
from mailng.lib import tables, imap_utf7, parameters
|
||||
|
||||
|
||||
class WMtable(tables.Table):
|
||||
idkey = "imapid"
|
||||
selection = tables.SelectionColumn("selection", first=True)
|
||||
subject = tables.Column("subject", label=_("Subject"))
|
||||
from_ = tables.Column("from", label=_("From"))
|
||||
date = tables.Column("date", label=_("Date"))
|
||||
from_exp = re.compile("([^<]+)<([^>]+)>")
|
||||
flags = tables.ImgColumn("flags")
|
||||
|
||||
def parse(self, header, value):
|
||||
try:
|
||||
return getattr(IMAPheader, "parse_%s" % header)(value)
|
||||
except AttributeError:
|
||||
return value
|
||||
|
||||
class IMAPheader(object):
|
||||
addr_exp = re.compile("([^<\(]+)<|\(([^>\)]+)>|\)")
|
||||
name_exp = re.compile("\"(.+)\"")
|
||||
|
||||
def parse_from(self, value):
|
||||
m = self.from_exp.match(value)
|
||||
@staticmethod
|
||||
def parse_address(value):
|
||||
m = IMAPheader.addr_exp.match(value)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
name = name.strip()
|
||||
m2 = self.name_exp.match(name)
|
||||
m2 = IMAPheader.name_exp.match(name)
|
||||
if m2:
|
||||
return m2.group(1)
|
||||
return m.group(1)
|
||||
return IMAPheader.parse_subject(m2.group(1))
|
||||
return IMAPheader.parse_subject(name)
|
||||
return value
|
||||
|
||||
def parse_date(self, value):
|
||||
@staticmethod
|
||||
def parse_address_list(values):
|
||||
lst = values.split(",")
|
||||
result = ""
|
||||
for addr in lst:
|
||||
if result != "":
|
||||
result += ", "
|
||||
result += IMAPheader.parse_address(addr)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def parse_from(value):
|
||||
return IMAPheader.parse_address(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_to(value):
|
||||
return IMAPheader.parse_address_list(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_cc(value):
|
||||
return IMAPheader.parse_address_list(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_date(value):
|
||||
ndate = datetime(*(email.utils.parsedate_tz(value))[:7])
|
||||
now = datetime.now()
|
||||
if now - ndate > timedelta(7):
|
||||
return ndate.strftime("%d.%m.%Y %H:%M")
|
||||
return ndate.strftime("%a %H:%M")
|
||||
|
||||
def parse_subject(self, value):
|
||||
@staticmethod
|
||||
def parse_subject(value):
|
||||
res = ""
|
||||
dcd = decode_header(value)
|
||||
for part in dcd:
|
||||
@@ -45,8 +81,7 @@ class WMtable(tables.Table):
|
||||
res += " "
|
||||
res += part[0].strip(" ")
|
||||
return decode(res)
|
||||
|
||||
|
||||
|
||||
class IMAPconnector(MBconnector):
|
||||
def login(self, user, passwd):
|
||||
import imaplib
|
||||
@@ -65,7 +100,9 @@ class IMAPconnector(MBconnector):
|
||||
|
||||
def messages_count(self, folder=None):
|
||||
(status, data) = self.m.select(self._encodefolder(folder))
|
||||
return int(data[0])
|
||||
(status, data) = self.m.sort("(REVERSE DATE)", "UTF-8", "(NOT DELETED)")
|
||||
self.messages = data[0].split()
|
||||
return len(self.messages)
|
||||
|
||||
def listfolders(self, topfolder='INBOX', md_folders=[]):
|
||||
(status, data) = self.m.list()
|
||||
@@ -86,34 +123,47 @@ class IMAPconnector(MBconnector):
|
||||
if not start and not stop:
|
||||
return []
|
||||
result = []
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
if start and stop:
|
||||
range = "%d:%d" % (start, stop)
|
||||
submessages = self.messages[start - 1:stop]
|
||||
range = ",".join(submessages)
|
||||
else:
|
||||
submessages = [start]
|
||||
range = start
|
||||
if not all:
|
||||
query = '(BODY[HEADER.FIELDS (DATE FROM TO SUBJECT)])'
|
||||
query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])'
|
||||
else:
|
||||
query = '(RFC822)'
|
||||
typ, data = self.m.fetch(range, query)
|
||||
imapid = int(start)
|
||||
if not folder:
|
||||
folder = "INBOX"
|
||||
tmpdict = {}
|
||||
for response_part in data:
|
||||
if isinstance(response_part, tuple):
|
||||
imapid = response_part[0].split()[0]
|
||||
flags = imaplib.ParseFlags(response_part[0])
|
||||
msg = email.message_from_string(response_part[1])
|
||||
msg["imapid"] = imapid
|
||||
result += [msg]
|
||||
imapid = imapid + 1
|
||||
msg["imapid"] = "%s/%s" % (folder, imapid)
|
||||
if not "\\Seen" in flags:
|
||||
msg["class"] = "unseen"
|
||||
if "\\Answered" in flags:
|
||||
msg["img_flags"] = "/static/pics/answered.png"
|
||||
tmpdict[imapid] = msg
|
||||
for id in submessages:
|
||||
result += [tmpdict[id]]
|
||||
return result
|
||||
|
||||
class ImapListing(EmailListing):
|
||||
tpl = "webmail/index.html"
|
||||
|
||||
def __init__(self, user, password, **kwargs):
|
||||
def __init__(self, user, password, baseurl=None, **kwargs):
|
||||
self.mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143)
|
||||
status, text = self.mbc.login(user, password)
|
||||
if not status:
|
||||
print "Login error: %s" % text
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
if baseurl:
|
||||
self.paginator.baseurl = baseurl
|
||||
|
||||
def getfolders(self):
|
||||
md_folders = [{"name" : "INBOX", "icon" : "overview.png"},
|
||||
|
||||
+43
-33
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
from django.http import HttpResponse
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import simplejson
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -13,6 +14,7 @@ from imap_listing import *
|
||||
|
||||
def init():
|
||||
events.register("UserMenuDisplay", menu)
|
||||
events.register("UserLogin", userlogin)
|
||||
parameters.register("webmail", "SERVER_ADDRESS", "string", "127.0.0.1",
|
||||
help=_("Mailboxes server address"))
|
||||
|
||||
@@ -25,59 +27,67 @@ def menu(**kwargs):
|
||||
return []
|
||||
return [
|
||||
{"name" : _("Webmail"),
|
||||
"url" : reverse(index),
|
||||
"url" : reverse(folder),
|
||||
"img" : "/static/pics/webmail.png"}
|
||||
]
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
start = time.time()
|
||||
lst = ImapListing(request.user.username, request.session["password"])
|
||||
try:
|
||||
pageid = request.GET["page"]
|
||||
except KeyError:
|
||||
pageid = 1
|
||||
return lst.render(request, pageid=int(pageid), start=start)
|
||||
def userlogin(**kwargs):
|
||||
kwargs["request"].session["password"] = kwargs["password"]
|
||||
|
||||
@login_required
|
||||
def folder(request, folder):
|
||||
def folder(request, name=None):
|
||||
if not name:
|
||||
name = "INBOX"
|
||||
lst = ImapListing(request.user.username, request.session["password"],
|
||||
folder=folder)
|
||||
try:
|
||||
pageid = request.GET["page"]
|
||||
except KeyError:
|
||||
pageid = 1
|
||||
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}
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
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, mail_id, all=False):
|
||||
def fetchmail(request, folder, mail_id, all=False):
|
||||
mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143)
|
||||
mbc.login(request.user.username, request.session["password"])
|
||||
res = mbc.fetch(start=mail_id, all=all)
|
||||
res = mbc.fetch(start=mail_id, folder=folder, all=all)
|
||||
if len(res):
|
||||
return res[0]
|
||||
return None
|
||||
|
||||
@login_required
|
||||
def viewmail(request, mail_id):
|
||||
header = fetchmail(request, mail_id)
|
||||
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:
|
||||
return _render(request, "webmail/viewmail.html", {
|
||||
"header" : header, "pageid" : pageid
|
||||
from templatetags.webextras import viewm_menu
|
||||
menu = viewm_menu("", folder, mail_id, pageid,
|
||||
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}
|
||||
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, mail_id):
|
||||
def getmailcontent(request, folder, mail_id):
|
||||
from mailng.lib.email_listing import Email
|
||||
|
||||
msg = fetchmail(request, mail_id, True)
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
email = Email(msg)
|
||||
return email.render(request)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<table id="headers">
|
||||
{% for hdr in headers %}
|
||||
<tr><th>{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@@ -46,27 +46,64 @@
|
||||
}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
$$("a[name=loadfolder]").addEvent("click", function(event) {
|
||||
{% 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;
|
||||
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);
|
||||
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) {
|
||||
event.stop();
|
||||
new Request.JSON({url: event.target.getParent().get("href"), onSuccess: function(resp) {
|
||||
updatelisting(resp);
|
||||
}}).get({"mode" : "ajax"});
|
||||
}
|
||||
select = function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.setStyle("background", "white");
|
||||
item.getParent().setStyle("background", "white");
|
||||
});
|
||||
event.target.setStyle("background", "#FFFFCC");
|
||||
obj.getParent().setStyle("background", "#FFFFCC");
|
||||
}
|
||||
loadfolder = function(event) {
|
||||
event.stop();
|
||||
new Request.JSON({url: event.target.get("href"), onSuccess: function(resp) {
|
||||
$("listing").set("html", resp.listing);
|
||||
$("navbar").set("html", resp.navbar);
|
||||
}}).get();
|
||||
});
|
||||
{% url extensions.webmail.main.viewmail as view_url %}
|
||||
bindRows("{{ view_url }}", "{{ current_page }}");
|
||||
updatelisting(resp);
|
||||
}}).get({"mode" : "ajax"});
|
||||
}
|
||||
foldercallback = function(event) {
|
||||
select(event.target);
|
||||
loadfolder(event);
|
||||
}
|
||||
|
||||
$$("a[name=loadfolder]").addEvent("click", foldercallback);
|
||||
$("navbar").getChildren("a").addEvent("click", navbarcallback);
|
||||
$$("tr").addEvent("click", loadMail);
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
|
||||
{% block menu %}
|
||||
<ul>
|
||||
<a href="">
|
||||
<li><img src="/static/pics/edit.png" />{% trans "New message" %}</li>
|
||||
</a>
|
||||
</ul>
|
||||
{% load webextras %}
|
||||
{% listing_menu "" perms %}
|
||||
{% endblock %}
|
||||
{% block leftcol %}
|
||||
<div id="leftcol" class="box">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
{% load webextras %}
|
||||
<style type="text/css">
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
td {
|
||||
font-size: 1em;
|
||||
@@ -16,32 +16,10 @@ th {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
window.addEvent("domready", function() {
|
||||
var spage = $("page").getSize();
|
||||
var stable = $("table-container").getSize();
|
||||
var slogo = $("header").getSize();
|
||||
var iheight = spage.y - stable.y - slogo.y;
|
||||
|
||||
$("mailcontent").setStyle("height", iheight + "px");
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Email content" %}{% endblock %}
|
||||
{% block menu %}
|
||||
{% load webextras %}
|
||||
{% viewm_menu "" header.imapid pageid perms %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="table-container">
|
||||
<table id="headers">
|
||||
<tr><th>{% trans "Subject" %}</th><td>{{ header.Subject }}</td></tr>
|
||||
<tr><th>{% trans "From" %}</th><td>{{ header.From }}</td></tr>
|
||||
<tr><th>{% trans "To" %}</th><td>{{ header.To }}</td></tr>
|
||||
<tr><th>{% trans "Date" %}</th><td>{{ header.Date }}</td></tr>
|
||||
</table>
|
||||
{% render_headers header %}
|
||||
</div>
|
||||
|
||||
{% url extensions.webmail.main.getmailcontent header.imapid as geturl %}
|
||||
{% url extensions.webmail.main.getmailcontent folder imapid as geturl %}
|
||||
<iframe id="mailcontent" src="{{ geturl }}"></iframe>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,14 +3,16 @@ from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from mailng.extensions import webmail
|
||||
from mailng.extensions.webmail.imap_listing import IMAPheader
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def viewm_menu(selection, mail_id, page_id, perms):
|
||||
def viewm_menu(selection, folder, mail_id, page_id, perms):
|
||||
entries = [
|
||||
{"name" : "back",
|
||||
"url" : reverse(webmail.main.index) + "?page=%s" % page_id,
|
||||
"url" : reverse(webmail.main.folder, args=[folder]) \
|
||||
+ "?page=%s" % page_id + "&menu=1",
|
||||
"img" : "/static/pics/back.png",
|
||||
"label" : _("Back to list")},
|
||||
]
|
||||
@@ -18,3 +20,32 @@ def viewm_menu(selection, mail_id, page_id, perms):
|
||||
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" : "",
|
||||
"img" : "/static/pics/edit.png",
|
||||
"label" : _("New message")}
|
||||
]
|
||||
|
||||
return render_to_string("main/menu.html",
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def render_headers(headers):
|
||||
fields = ["Subject", "From", "To", "Cc", "Date"]
|
||||
result = []
|
||||
for f in fields:
|
||||
if not f in headers.keys():
|
||||
continue
|
||||
try:
|
||||
result += [{"name" : f, "value" : getattr(IMAPheader, "parse_%s" % f.lower())(headers[f])}]
|
||||
except AttributeError:
|
||||
result += [{"name" : f, "value" : headers[f]}]
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : result
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.webmail.main',
|
||||
(r'^$', "index"),
|
||||
url(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail',
|
||||
name="viewmail_std"),
|
||||
url(r'^viewmail/$', 'viewmail', name="viewmail_null"),
|
||||
(r'^getmailcontent/(?P<mail_id>[\w\-\+]+)/$', 'getmailcontent'),
|
||||
# (r'^(?P<folder>.+)/$', "folder"),
|
||||
url(r'^$', "folder", name="index"),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
url(r'^(?P<name>.+)/$', "folder", name="folder"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user