diff --git a/auth/views.py b/auth/views.py
index 75e730a90..52d127346 100755
--- a/auth/views.py
+++ b/auth/views.py
@@ -4,6 +4,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
+from mailng.lib import events
import mailng.admin
from forms import LoginForm
@@ -17,7 +18,10 @@ def dologin(request):
if user.is_active:
login(request, user)
- request.session["password"] = request.POST["password"]
+ events.raiseEvent("UserLogin", request=request,
+ username=request.POST["username"],
+ password=request.POST["password"])
+
next = request.POST["next"]
if next == "None":
next = reverse(mailng.admin.views.domains)
diff --git a/extensions/webmail/imap_listing.py b/extensions/webmail/imap_listing.py
index c5977d064..2ee488a29 100644
--- a/extensions/webmail/imap_listing.py
+++ b/extensions/webmail/imap_listing.py
@@ -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"},
diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py
index cc2dc6381..444d05008 100644
--- a/extensions/webmail/main.py
+++ b/extensions/webmail/main.py
@@ -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)
diff --git a/extensions/webmail/templates/headers.html b/extensions/webmail/templates/headers.html
new file mode 100644
index 000000000..fa570da53
--- /dev/null
+++ b/extensions/webmail/templates/headers.html
@@ -0,0 +1,5 @@
+
diff --git a/extensions/webmail/templates/index.html b/extensions/webmail/templates/index.html
index eb15bc75d..68c7b95e7 100644
--- a/extensions/webmail/templates/index.html
+++ b/extensions/webmail/templates/index.html
@@ -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 %}
-
+ {% load webextras %}
+ {% listing_menu "" perms %}
{% endblock %}
{% block leftcol %}
diff --git a/extensions/webmail/templates/viewmail.html b/extensions/webmail/templates/viewmail.html
index b2cb5e310..97637a322 100644
--- a/extensions/webmail/templates/viewmail.html
+++ b/extensions/webmail/templates/viewmail.html
@@ -1,10 +1,10 @@
-{% extends "nlayout.html" %}
{% load i18n %}
-{% block html_head %}
+{% load webextras %}
-
-{% endblock %}
-{% block middletitle %}{% trans "Email content" %}{% endblock %}
-{% block menu %}
- {% load webextras %}
- {% viewm_menu "" header.imapid pageid perms %}
-{% endblock %}
-{% block content %}
-
+ {% render_headers header %}
-{% url extensions.webmail.main.getmailcontent header.imapid as geturl %}
+{% url extensions.webmail.main.getmailcontent folder imapid as geturl %}
-{% endblock %}
diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py
index 99bc4cc5c..d948db506 100644
--- a/extensions/webmail/templatetags/webextras.py
+++ b/extensions/webmail/templatetags/webextras.py
@@ -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
+ })
+
diff --git a/extensions/webmail/urls.py b/extensions/webmail/urls.py
index 0203315fd..6997d4b7c 100644
--- a/extensions/webmail/urls.py
+++ b/extensions/webmail/urls.py
@@ -1,10 +1,8 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.webmail.main',
- (r'^$', "index"),
- url(r'^viewmail/(?P
[\w\-\+]+)/$', 'viewmail',
- name="viewmail_std"),
- url(r'^viewmail/$', 'viewmail', name="viewmail_null"),
- (r'^getmailcontent/(?P[\w\-\+]+)/$', 'getmailcontent'),
-# (r'^(?P.+)/$', "folder"),
+ url(r'^$', "folder", name="index"),
+ (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 c5bdf5d6a..71ae04db3 100644
--- a/lib/email_listing.py
+++ b/lib/email_listing.py
@@ -14,7 +14,8 @@ class MBconnector(object):
class Page(object):
def __init__(self, pageid, id_start, id_stop, items,
- items_per_page, has_previous, has_next):
+ items_per_page, has_previous, has_next,
+ baseurl=None):
self.pageid = pageid
self.id_start = id_start
self.id_stop = id_stop
@@ -22,6 +23,7 @@ class Page(object):
self.items_per_page = items_per_page
self.has_previous = has_previous
self.has_next = has_next
+ self.baseurl = baseurl
def previous_page(self):
if not self.has_previous:
@@ -45,6 +47,7 @@ class Paginator(object):
def __init__(self, total, elems_per_page):
self.total = total
self.elems_per_page = elems_per_page
+ self.baseurl = None
def _indexes(self, page):
id_start = self.elems_per_page * page + 1
@@ -65,8 +68,11 @@ class Paginator(object):
id_stop = self.total
else:
return None
- return Page(page, id_start, id_stop, self.total,
- self.elems_per_page, has_previous, has_next)
+ p = Page(page, id_start, id_stop, self.total,
+ self.elems_per_page, has_previous, has_next)
+ if self.baseurl:
+ p.baseurl = self.baseurl
+ return p
class EmailListing:
def __init__(self, folder=None, elems_per_page=40, **kwargs):
diff --git a/lib/events.py b/lib/events.py
index 9e04a9900..4e9dcf493 100644
--- a/lib/events.py
+++ b/lib/events.py
@@ -13,7 +13,9 @@ events = ["CreateDomain",
"ModifyMailbox",
"UserMenuDisplay",
- "AdminMenuDisplay"]
+ "AdminMenuDisplay",
+
+ "UserLogin"]
callbacks = {}
diff --git a/lib/tables.py b/lib/tables.py
index a83b806a3..13a36eba1 100644
--- a/lib/tables.py
+++ b/lib/tables.py
@@ -24,6 +24,13 @@ class SelectionColumn(Column):
return "" \
% (self.name, value)
+class ImgColumn(Column):
+ def __str__(self):
+ return ""
+
+ def render(self, value):
+ return "
" % value
+
class Table(object):
def __init__(self, rows={}):
self.columns = []
@@ -41,16 +48,24 @@ class Table(object):
if isinstance(c, SelectionColumn):
nrow["cols"] += [{"value" : c.render(row[self.idkey]), "safe" : True}]
continue
+ if isinstance(c, ImgColumn):
+ key = "img_%s" % c.name
+ if key in row.keys():
+ nrow["cols"] += [{"value" : c.render(row[key]), "safe" : True}]
+ else:
+ nrow["cols"] += ["."]
+ continue
if row.has_key(c.name):
- try:
- nrow["cols"] += [{"value" : getattr(self, "parse_%s" % c.name)(row[c.name])}]
- except AttributeError:
- nrow["cols"] += [{"value" : row[c.name]}]
+ if "class" in row.keys():
+ cssclass = row["class"]
+ else:
+ cssclass = ""
+
+ nrow["cols"] += [{"value" : self.parse(c.name, row[c.name]),
+ "class" : cssclass}]
self.rows += [nrow]
def render(self, request):
return render_to_string("common/tables.html", {
"table" : self
})
-
-
diff --git a/static/js/common.js b/static/js/common.js
index 17bcf706a..081fe2a54 100644
--- a/static/js/common.js
+++ b/static/js/common.js
@@ -71,13 +71,17 @@ confirmation = function(question, action, callback) {
SqueezeBox.open("/mailng/main/confirm/?question=" + question);
}
-setContentSize = function(cellsize) {
- var contentsize = $(document.body).getSize().y - $("topmenubar").getSize().y
- - $("header").getSize().y;
+setDivHeight = function(id, extrah, modulo) {
+ var contentsize = $(document.body).getSize().y
+ - $("topmenubar").getSize().y
+ - $("header").getSize().y
+ - ($("navbar").getSize().y)
+ - extrah;
- contentsize -= $("navbar").getSize().y * 2;
- contentsize -= contentsize % cellsize;
- $("listing").setStyle("height", contentsize + "px");
+ if (modulo) {
+ contentsize -= contentsize % modulo;
+ }
+ $(id).setStyle("height", contentsize + "px");
}
bindRows = function(rooturl, page) {
diff --git a/static/pics/answered.png b/static/pics/answered.png
new file mode 100644
index 000000000..9d31e533f
Binary files /dev/null and b/static/pics/answered.png differ
diff --git a/static/stylesheets/default.css b/static/stylesheets/default.css
index 07b9c14e0..0e72a938d 100644
--- a/static/stylesheets/default.css
+++ b/static/stylesheets/default.css
@@ -47,7 +47,6 @@ html, body {
#page {
border: 2px solid #333;
background: white;
- min-height: 92%;
margin: 0 15px;
}
@@ -251,4 +250,8 @@ input:hover {
.hr td.even {
+}
+
+.unseen {
+ font-weight: bold;
}
\ No newline at end of file
diff --git a/templates/common/email_listing.html b/templates/common/email_listing.html
index 3c0459666..474df0eeb 100644
--- a/templates/common/email_listing.html
+++ b/templates/common/email_listing.html
@@ -40,8 +40,7 @@ window.addEvent("domready", function() {
});
}
{% block js %}{% endblock %}
- setContentSize(20);
- var scroller = new Scroller("listing", {area: 150, velocity: 1});
+ setDivHeight("listing", 0, 25);
});
{% endblock %}
diff --git a/templates/common/navbar.html b/templates/common/navbar.html
index 9d6ed8b4e..668152024 100644
--- a/templates/common/navbar.html
+++ b/templates/common/navbar.html
@@ -2,18 +2,18 @@
{% if page.previous_page %}
-
+
-
+
{% endif %}
{% if page.next_page %}
-
+
-
+
{% endif %}
diff --git a/templates/common/tables.html b/templates/common/tables.html
index 3906302ee..1225475e3 100644
--- a/templates/common/tables.html
+++ b/templates/common/tables.html
@@ -10,7 +10,7 @@
{% if cell.safe %}
{{ cell.value|safe }} |
{% else %}
- {{ cell.value }} |
+ {{ cell.value }} |
{% endif %}
{% endfor %}
diff --git a/templates/registration/login.html b/templates/registration/login.html
index 3d9cc6b7b..f5b23a6bc 100644
--- a/templates/registration/login.html
+++ b/templates/registration/login.html
@@ -21,7 +21,7 @@
{% if error %}
{% endif %}
- Antoine Nguyen - 2010 - Version 0.7-dev
+ 2010 - Version 0.7-dev
{% trans "Welcome" %}