wip: UI refactoring

* Possibility to dowload message attachemnts

see #204
This commit is contained in:
Antoine Nguyen
2012-04-22 10:22:02 +02:00
parent 4d47e2b126
commit 89d946c8cf
5 changed files with 67 additions and 57 deletions
+21 -3
View File
@@ -93,6 +93,7 @@ class BodyStructure(object):
self.inlines[params["cid"].strip("<>")] = params
return
params["Content-Type"] = ftype
if len(definition) > 7:
extensions = ["md5", "disposition", "language", "location"]
if mtype == "text":
@@ -124,6 +125,12 @@ class BodyStructure(object):
def has_attachments(self):
return len(self.attachments)
def find_attachment(self, pnum):
for att in self.attachments:
if pnum == att["pnum"]:
return att
return None
class IMAPconnector(object):
__metaclass__ = ConnectionsManager
@@ -551,9 +558,21 @@ class IMAPconnector(object):
self.quota_actual = int(m.group(1))
def fetchpart(self, uid, mbox, partnum):
"""Retrieve a specific message part
Useful to fetch attachments from the server. Part headers and
the payload are returned separatly.
:param uid: a message UID
:param mbox: the mailbox containing the message
:param partnum: the part number
:return: a 2uple (dict, string)
"""
self.select_mailbox(mbox, False)
data = self._cmd("FETCH", uid, "(BODY[%s])" % partnum)
return data[int(uid)]["BODY[%s]" % partnum]
data = self._cmd("FETCH", uid, "(BODYSTRUCTURE BODY[%s])" % partnum)
bs = BodyStructure(data[int(uid)]["BODYSTRUCTURE"])
attdef = bs.find_attachment(partnum)
return attdef, data[int(uid)]["BODY[%s]" % partnum]
def fetch(self, start, stop=None, mbox=None, **kwargs):
"""Retrieve information about messages from the server
@@ -604,7 +623,6 @@ class IMAPconnector(object):
:param readonly:
:param extraheaders:
"""
print mbox
self.select_mailbox(mbox, readonly)
if headers is None:
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
+23 -14
View File
@@ -182,8 +182,8 @@ class ImapEmail(Email):
pnum = self.bs.contents[mformat]['pnum']
data = self.imapc._cmd("FETCH", mailid, "(BODY.PEEK[%s])" % pnum)
content = self._decode_content(self.bs.contents[mformat]['encoding'],
data[int(mailid)]['BODY[%s]' % pnum])
content = decode_payload(self.bs.contents[mformat]['encoding'],
data[int(mailid)]['BODY[%s]' % pnum])
charset = self._find_content_charset(mformat)
if charset is not None:
content = content.decode(charset)
@@ -229,16 +229,6 @@ class ImapEmail(Email):
return self.bs.contents[subtype]["params"][pos + 1]
return None
def _decode_content(self, encoding, content):
encoding = encoding.lower()
if encoding == "base64":
import base64
return base64.b64decode(content)
elif encoding == "quoted-printable":
import quopri
return quopri.decodestring(content)
return content
def _find_attachments(self):
for att in self.bs.attachments:
attname = "part_%s" % att["pnum"]
@@ -270,9 +260,9 @@ class ImapEmail(Email):
if os.path.exists(path):
continue
content = self.imapc.fetchpart(self.mailid, self.mbox, params["pnum"])
pdef, content = self.imapc.fetchpart(self.mailid, self.mbox, params["pnum"])
fp = open(path, "wb")
fp.write(self._decode_content(params["encoding"], content))
fp.write(decode_payload(params["encoding"], content))
fp.close()
@@ -428,6 +418,25 @@ class EmailSignature(object):
def __repr__(self):
return self._sig
def decode_payload(encoding, payload):
"""Decode the payload according to the given encoding
Supported encodings: base64, quoted-printable.
:param encoding: the encoding's name
:param payload: the value to decode
:return: a string
"""
print payload
encoding = encoding.lower()
if encoding == "base64":
import base64
return base64.b64decode(payload)
elif encoding == "quoted-printable":
import quopri
return quopri.decodestring(payload)
return payload
def find_images_in_body(body):
"""Looks for images inside a HTML body
@@ -1,13 +1,11 @@
{% extends "common/mailheaders.html" %}
{% load i18n %}
{% extends "common/mailheaders.html" %}{% load i18n %}
{% block extraheaders %}
{% if attachments %}
<tr>
<th>{% trans "Attachments" %}</th>
<td>
{% url modoboa.extensions.webmail.views.getattachment folder mail_id as test %}
<td>{% url modoboa.extensions.webmail.views.getattachment as url %}
{% for key, fname in attachments.items %}
<a href="{{ test }}?fname={{ fname }}&partnumber={{ key }}">{{ fname }}</a>
<a href="{{ url }}?mbox={{ folder }}&mailid={{ mail_id }}&fname={{ fname }}&partnumber={{ key }}">{{ fname }}</a>
{% endfor %}
</td>
</tr>
+1 -2
View File
@@ -18,6 +18,5 @@ urlpatterns = patterns(
(r'^attachments/$', 'attachments'),
(r'^delattachment/$', 'delattachment'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/part/$', 'getattachment'),
(r'^getattachment/$', 'getattachment'),
)
+19 -33
View File
@@ -16,49 +16,35 @@ from templatetags import webextras
@login_required
@needs_mailbox()
def getattachment(request, folder, mail_id):
def getattachment(request):
"""Fetch a message attachment
Problème lié aux optimisations apportées : la méthode fetchpart du
client IMAP ne renvoie que le payload. Pour obtenir les headers,
deux solutions :
FIXME: par manque de caching, le bodystructure du message est
redemandé pour accéder aux headers de cette pièce jointe.
* Conserver le bodystructure parsé précédemment (nécessite du caching)
* Reparser le bodystructure :p
:param request: a ``Request`` object
:param folder
"""
mbox = request.GET.get("mbox", None)
mailid = request.GET.get("mailid", None)
pnum = request.GET.get("partnumber", None)
if not pnum:
raise Http404
if not mbox or not mailid or not pnum:
raise WebmailError(_("Invalid request"))
headers = {"Content-Type" : "text/plain",
"Content-Transfer-Encoding" : None}
imapc = get_imapconnector(request)
part = imapc.fetchpart(mail_id, folder, pnum)
if part is None:
raise Http404
if part.get_content_maintype() == "message":
payload = part.get_payload(0)
partdef, payload = imapc.fetchpart(mailid, mbox, pnum)
resp = HttpResponse(decode_payload(partdef["encoding"], payload))
resp["Content-Type"] = partdef["Content-Type"]
resp["Content-Transfer-Encoding"] = partdef["encoding"]
if partdef["disposition"] != 'NIL':
disp = partdef["disposition"]
cd = "%s; %s=%s" % (disp[0], disp[1][0], disp[1][1])
else:
payload = part.get_payload(decode=True)
resp = HttpResponse(payload)
for hdr, default in headers.iteritems():
if not part.has_key(hdr):
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 I 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" % request.GET["fname"]
resp["Content-Length"] = len(payload)
cd = "attachment; filename=%s" % request.GET["fname"]
resp["Content-Disposition"] = cd
resp["Content-Length"] = partdef["size"]
return resp
@login_required