Webmail improvements. (drap&drop of messages for moving and deleting, empty trash, etc.)

This commit is contained in:
tonio
2010-01-27 14:51:38 +01:00
parent 5d9099d33e
commit db9fbef0ba
8 changed files with 166 additions and 49 deletions
+31 -9
View File
@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
from email.header import decode_header
import email.utils
from django.utils.translation import ugettext as _
from mailng.lib import decode, tables, imap_utf7
from mailng.lib import decode, tables, imap_utf7, Singleton
from mailng.lib.email_listing import MBconnector, EmailListing
from mailng.lib import tables, imap_utf7, parameters
@@ -17,7 +17,8 @@ class WMtable(tables.Table):
idkey = "imapid"
selection = tables.SelectionColumn("selection", width="20px", first=True)
_1_flags = tables.ImgColumn("flags", width="40px")
_2_subject = tables.Column("subject", width="50%", label=_("Subject"))
_2_subject = tables.Column("subject", width="50%", label=_("Subject"),
cssclass="draggable")
_3_from_ = tables.Column("from", width="20%", label=_("From"))
_4_date = tables.Column("date", width="20%", label=_("Date"))
@@ -83,11 +84,23 @@ class IMAPheader(object):
res += " "
res += part[0].strip(" ")
return decode(res)
class IMAPconnector(MBconnector):
class IMAPconnector(object):
__metaclass__ = Singleton
def __init__(self, request=None, user=None, password=None):
self.address = parameters.get("webmail", "IMAP_SERVER")
if request:
status, msg = self.login(request.user.username,
request.session["password"])
else:
status, msg = self.login(user, password)
if not status:
# Gestion d'erreur
pass
def login(self, user, passwd):
import imaplib
try:
self.m = imaplib.IMAP4_SSL(self.address)
self.m.login(user, passwd)
@@ -125,6 +138,18 @@ class IMAPconnector(MBconnector):
folder, id = imapid.split("/")
self.m.store(id, "+FLAGS", "\\Seen")
def move(self, msgset, oldfolder, newfolder):
self.m.select(self._encodefolder(oldfolder), True)
self.m.copy(msgset, newfolder)
self.m.store(msgset, "+FLAGS", "\\Deleted")
def empty(self, folder):
self.m.select(self._encodefolder(folder))
typ, data = self.m.search(None, 'ALL')
for num in data[0].split():
self.m.store(num, "+FLAGS", "\\Deleted")
self.m.expunge()
def fetch(self, start=None, stop=None, folder=None, all=False):
if not start and not stop:
return []
@@ -164,10 +189,7 @@ class ImapListing(EmailListing):
tbltype = WMtable
def __init__(self, user, password, baseurl=None, **kwargs):
self.mbc = IMAPconnector(parameters.get("webmail", "IMAP_SERVER"), 143)
status, text = self.mbc.login(user, password)
if not status:
print "Login error: %s" % text
self.mbc = IMAPconnector(user=user, password=password)
EmailListing.__init__(self, **kwargs)
if baseurl:
self.paginator.baseurl = baseurl
+20 -11
View File
@@ -15,8 +15,6 @@ from imap_listing import *
from forms import *
from templatetags.webextras import *
mbc = None
def init():
events.register("UserMenuDisplay", menu)
events.register("UserLogin", userlogin)
@@ -66,7 +64,7 @@ def folder(request, name, updatenav=True):
content = "Empty folder"
navbar = ""
ctx = getctx("ok", listing=content, navbar=navbar,
menu=listing_menu("", request.user.get_all_permissions()))
menu=listing_menu("", name, request.user.get_all_permissions()))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@@ -76,11 +74,7 @@ def index(request):
return lst.render(request, empty=True)
def fetchmail(request, folder, mail_id, all=False):
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)
res = IMAPconnector(request).fetch(start=mail_id, folder=folder, all=all)
if len(res):
return res[0]
return None
@@ -106,9 +100,7 @@ def getmailcontent(request, folder, mail_id):
msg = fetchmail(request, folder, mail_id, True)
if "class" in msg.keys() and msg["class"] == "unseen":
global mbc
mbc.msgseen(msg["imapid"])
IMAPconnector(request).msgseen(msg["imapid"])
email = Email(msg, mode="html", links="1")
return email.render(request)
@@ -139,3 +131,20 @@ def compose(request):
})
ctx = getctx("ok", menu=menu, listing=content)
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def move(request):
for arg in ["msgset", "to"]:
if not request.GET.has_key(arg):
return
folder, id = request.GET["msgset"].split("/")
mbc = IMAPconnector(request)
mbc.move(id, folder, request.GET["to"])
return HttpResponse(simplejson.dumps({"status" : "ok"}),
mimetype="application/json")
@login_required
def empty(request, name):
mbc = IMAPconnector(request)
mbc.empty(name)
return folder(request, name, False)
+89 -25
View File
@@ -45,8 +45,20 @@
border: 0;
}
tr.table-tr-selected {
background: red;
td {
position: relative;
}
.draggable {
cursor: move;
}
.dragging {
background: #6AB0C5;
}
tr {
cursor: pointer;
}
{% endblock %}
{% block js %}
@@ -64,6 +76,22 @@ tr.table-tr-selected {
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}
$$("a[name=compose]").addEvent("click", loadCompose);
$$("a[name=empty]").addEvent("click", empty);
}
empty = function(event) {
event.stop();
new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) {
updatelisting(resp);
}}).get();
}
select = function(obj) {
$$("a[name=loadfolder]").each(function(item) {
item.getParent().removeClass("selected");
item.getParent().addClass("droppable");
});
obj.getParent().removeClass("droppable");
obj.getParent().addClass("selected");
}
{% url extensions.webmail.main.folder as view_url %}
@@ -110,20 +138,62 @@ tr.table-tr-selected {
}
register_callback("folder", function(resp) {
updatelisting(resp, "auto");
var tbl = new HtmlTable($("emails"), {
selectable: true
});
var drag_group = new Drag.Group({
});
tbl.addEvent("rowFocus", function(obj) {
var clone = obj.clone()
.setStyles(obj.getCoordinates())
.setStyles({"opacity": 0.7, "position": "absolute"})
.inject(document.body);
drag_group.add(clone, {
/*var drag_group = new Drag.Group({
filter: function(item) { return item.hasClass("active"); },
drag: {
onComplete: function(item) {
item.dispose();
delete(item);
},
onEnter: function(item, droppable) {
alert(droppable);
}
}
});*/
$$("td[class*=draggable]").each(function(item) {
item.makeDraggable({
droppables: ".droppable",
}).addEvents({
"enter" : function(item, droppable) {
droppable.addClass("dragging");
},
"leave" : function(item, droppable) {
droppable.removeClass("dragging");
},
"drop" : function(item, droppable, event) {
item.dispose();
delete(item);
if (!droppable) {
this.element = this.oldelement;
return;
}
droppable.removeClass("dragging");
this.oldelement.getParent().dispose();
to = droppable.getFirst("a").get("href");
{% url extensions.webmail.main.move as moveurl %}
new Request.JSON({url: "{{ moveurl }}", onSuccess: function(resp) {
}}).get({msgset : this.oldelement.getParent().get("id"),
to : to});
},
"beforeStart": function(item) {
var clone = item.clone()
.setStyles({"position" : "absolute",
"opacity" : 0.6,
"width" : "300px",
"border" : "1px solid #333",
"background" : "white"})
.setStyles(item.getCoordinates())
.inject(document.body);
this.oldelement = this.element;
this.element = clone;
},
"cancel": function(item) {
if ($defined(this.oldelement)) {
item.dispose();
delete(item);
this.element = this.oldelement;
}
}
});
});
@@ -134,13 +204,6 @@ tr.table-tr-selected {
loadFolder(event, event.target.getParent());
}
select = function(obj) {
$$("a[name=loadfolder]").each(function(item) {
item.getParent().setStyle("background", "white");
});
obj.getParent().setStyle("background", "#FFFFCC");
}
foldercallback = function(event) {
select(event.target);
loadFolder(event, event.target);
@@ -154,7 +217,7 @@ tr.table-tr-selected {
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
{% block menu %}
{% load webextras %}
{% listing_menu "" perms %}
{% listing_menu "" "INBOX" perms %}
{% endblock %}
{% block leftcol %}
<div id="leftcol" class="box">
@@ -162,13 +225,14 @@ tr.table-tr-selected {
<ul>
{% for fd in folders %}
{% url extensions.webmail.main.folder fd.name as fdurl %}
<a href="{{ fd.name }}" name="loadfolder">
<li {% ifequal selection fd.name %}class="selected"{% endifequal %}>
<li class="droppable">
<a href="{{ fd.name }}" name="loadfolder">
{% if fd.icon %}<img src="/static/pics/{{ fd.icon }}" />
{% else %}<img src="/static/pics/folder.png" />
{% endif %}
{{ fd.name }}</li>
</a>
{{ fd.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
+8 -1
View File
@@ -37,13 +37,20 @@ def compose_menu(selection, folder, page_id, perms):
"perms" : perms})
@register.simple_tag
def listing_menu(selection, perms):
def listing_menu(selection, folder, perms):
entries = [
{"name" : "compose",
"url" : reverse(webmail.main.compose),
"img" : "/static/pics/edit.png",
"label" : _("New message")}
]
if folder in ["Trash"]:
entries += [
{"name" : "empty",
"url" : reverse(webmail.main.empty, args=[folder]),
"img" : "/static/pics/clear.png",
"label" : _("Empty trash")}
]
return render_to_string("main/menu.html",
{"selection" : selection, "entries" : entries,
+2
View File
@@ -3,6 +3,8 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.webmail.main',
(r'^$', "index"),
(r'^compose/$', "compose"),
(r'^move/$', "move"),
(r'^empty/(?P<name>.+)/', "empty"),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
(r'^(?P<name>.+)/$', "folder"),
+11
View File
@@ -78,3 +78,14 @@ def getoption(name, default=None):
except AttributeError:
res = default
return res
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.instance
+5 -3
View File
@@ -58,10 +58,12 @@ class Table(object):
nrow["cols"] += ["."]
continue
if row.has_key(c.name):
if "class" in row.keys():
cssclass = row["class"]
else:
try:
cssclass = getattr(c, "cssclass")
except AttributeError:
cssclass = ""
if "class" in row.keys():
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
nrow["cols"] += [{"value" : self.parse(c.name, row[c.name]),
"class" : cssclass}]
Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B