mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
wip: webmail big refactoring and performance improvment.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
import rrdtool
|
||||
#import rrdtool
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.lib import parameters
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
:mod:`exceptions` --- Webmail custom exceptions
|
||||
-----------------------------------------------
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
class WebmailError(Exception):
|
||||
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
|
||||
|
||||
def __init__(self, reason, ajax=False):
|
||||
m = WebmailError.errorexpr.match(reason)
|
||||
if m is None:
|
||||
self.reason = reason
|
||||
else:
|
||||
self.reason = "%s: %s" % (_("Server response"), m.group(2))
|
||||
self.ajax = ajax
|
||||
|
||||
def __str__(self):
|
||||
return self.reason
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
:mod:`imaputils` --- Extra IMAPv4 utilities
|
||||
-------------------------------------------
|
||||
"""
|
||||
import imaplib, ssl, email
|
||||
import re
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.connections import *
|
||||
from modoboa.lib.webutils import static_url
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
def __init__(self, user=None, password=None):
|
||||
self.criterions = []
|
||||
self.address = parameters.get_admin("IMAP_SERVER")
|
||||
self.port = int(parameters.get_admin("IMAP_PORT"))
|
||||
status, msg = self.login(user, password)
|
||||
if not status:
|
||||
raise Exception(msg)
|
||||
|
||||
def refresh(self, user, password):
|
||||
"""Check if current connection needs a refresh
|
||||
|
||||
Is it really secure?
|
||||
"""
|
||||
if self.m is not None:
|
||||
try:
|
||||
self.m.select()
|
||||
return
|
||||
except imaplib.IMAP4.error, error:
|
||||
print error
|
||||
print self.login(user, password)
|
||||
|
||||
|
||||
def login(self, user, passwd):
|
||||
try:
|
||||
secured = parameters.get_admin("IMAP_SECURED")
|
||||
if secured == "yes":
|
||||
self.m = imaplib.IMAP4_SSL(self.address, self.port)
|
||||
else:
|
||||
self.m = imaplib.IMAP4(self.address, self.port)
|
||||
except (socket.error, imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Connection to IMAP server failed, check your configuration")
|
||||
try:
|
||||
self.m.login(user, passwd)
|
||||
except (imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Authentication failed, check your configuration")
|
||||
return True, None
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.m.select()
|
||||
except imaplib.IMAP4.error:
|
||||
pass
|
||||
self.m.logout()
|
||||
del self.m
|
||||
self.m = None
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
"""An enhanced version of messages_count
|
||||
|
||||
With IMAP, to know how many messages a mailbox contains, we
|
||||
have to make a request to the server. To avoid requests
|
||||
multiplications, we sort messages in the same time. This will
|
||||
be usefull for other methods.
|
||||
|
||||
:param order: sorting order
|
||||
:param folder: mailbox to scan
|
||||
"""
|
||||
if "order" in kwargs.keys() and kwargs["order"]:
|
||||
sign = kwargs["order"][:1]
|
||||
criterion = kwargs["order"][1:].upper()
|
||||
if sign == '-':
|
||||
criterion = "REVERSE %s" % criterion
|
||||
else:
|
||||
criterion = "REVERSE DATE"
|
||||
folder = kwargs.has_key("folder") and kwargs["folder"] or None
|
||||
(status, data) = self.m.select(self._encodefolder(folder))
|
||||
(status, data) = self.m.sort("(%s)" % criterion, "UTF-8", "(NOT DELETED)",
|
||||
*self.criterions)
|
||||
self.messages = data[0].split()
|
||||
self.getquota(folder)
|
||||
return len(self.messages)
|
||||
|
||||
def unseen_messages(self, folder):
|
||||
"""Return the number of unseen messages for folder"""
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
status, data = self.m.search("UTF-8", "(NOT DELETED UNSEEN)")
|
||||
if status != "OK":
|
||||
return
|
||||
return len(data[0].split())
|
||||
|
||||
def _encodefolder(self, folder):
|
||||
if not folder:
|
||||
return "INBOX"
|
||||
return folder.encode("imap4-utf-7")
|
||||
|
||||
def _parse_folder_name(self, descr, prefix, delimiter, parts):
|
||||
if not len(parts):
|
||||
return False
|
||||
path = "%s%s%s" % (prefix, delimiter, parts[0])
|
||||
sdescr = None
|
||||
for d in descr:
|
||||
if d["path"] == path:
|
||||
sdescr = d
|
||||
break
|
||||
if sdescr is None:
|
||||
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
|
||||
descr += [sdescr]
|
||||
if self._parse_folder_name(sdescr["sub"], path, delimiter, parts[1:]):
|
||||
sdescr["class"] = "subfolders"
|
||||
return True
|
||||
|
||||
def _listfolders(self, topfolder='INBOX', md_folders=[]):
|
||||
list_response_pattern = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
|
||||
(status, data) = self.m.list()
|
||||
result = []
|
||||
for mb in data:
|
||||
flags, delimiter, name = list_response_pattern.match(mb).groups()
|
||||
name = name.strip('"').decode("imap4-utf-7")
|
||||
if re.search("\%s" % delimiter, name):
|
||||
parts = name.split(".")
|
||||
if not descr.has_key("path"):
|
||||
descr["path"] = parts[0]
|
||||
descr["sub"] = []
|
||||
if self._parse_folder_name(descr["sub"], parts[0], delimiter,
|
||||
parts[1:]):
|
||||
descr["class"] = "subfolders"
|
||||
continue
|
||||
present = False
|
||||
descr = {"name" : name}
|
||||
for mdf in md_folders:
|
||||
if mdf["name"] == name:
|
||||
present = True
|
||||
break
|
||||
if not present:
|
||||
result += [descr]
|
||||
from operator import itemgetter
|
||||
return sorted(result, key=itemgetter("name"))
|
||||
|
||||
def getfolders(self, user, unseen_messages=True):
|
||||
md_folders = [{"name" : "INBOX", "class" : "inbox"},
|
||||
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class" : "drafts"},
|
||||
{"name" : 'Junk'},
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER")},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "trash"}]
|
||||
md_folders += self._listfolders(md_folders=md_folders)
|
||||
if unseen_messages:
|
||||
for fd in md_folders:
|
||||
key = fd.has_key("path") and "path" or "name"
|
||||
count = self.unseen_messages(fd[key])
|
||||
if count == 0:
|
||||
continue
|
||||
fd["unseen"] = count
|
||||
return md_folders
|
||||
|
||||
def _add_flag(self, folder, mail_id, flag):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.store(mail_id, "+FLAGS", flag)
|
||||
|
||||
def msg_unread(self, folder, msgset):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.store(msgset, "-FLAGS", r'(\Seen)')
|
||||
|
||||
def msg_read(self, folder, msgset):
|
||||
self._add_flag(folder, msgset, r'(\Seen)')
|
||||
|
||||
def msgforwarded(self, folder, imapid):
|
||||
self._add_flag(folder, imapid, '($Forwarded)')
|
||||
|
||||
def msg_answered(self, folder, imapid):
|
||||
self._add_flag(folder, imapid, r'(\Answered)')
|
||||
|
||||
def move(self, msgset, oldfolder, newfolder):
|
||||
self.m.select(self._encodefolder(oldfolder))
|
||||
status, data = self.m.copy(msgset, self._encodefolder(newfolder))
|
||||
if status == 'OK':
|
||||
self.m.store(msgset, "+FLAGS", r'(\Deleted)')
|
||||
|
||||
def push_mail(self, folder, msg):
|
||||
now = imaplib.Time2Internaldate(time.time())
|
||||
self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
|
||||
|
||||
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", r'(\Deleted)')
|
||||
self.m.expunge()
|
||||
|
||||
def compact(self, folder):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.expunge()
|
||||
|
||||
def create_folder(self, name, parent=None):
|
||||
if parent is not None:
|
||||
name = "%s.%s" % (parent, name)
|
||||
typ, data = self.m.create(self._encodefolder(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
|
||||
def rename_folder(self, oldname, newname):
|
||||
typ, data = self.m.rename(self._encodefolder(oldname),
|
||||
self._encodefolder(newname))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0], ajax=True)
|
||||
return True
|
||||
|
||||
def delete_folder(self, name):
|
||||
typ, data = self.m.delete(self._encodefolder(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
|
||||
def getquota(self, folder):
|
||||
if not "QUOTA" in self.m.capabilities:
|
||||
self.quota_limit = self.quota_actual = None
|
||||
return
|
||||
|
||||
status, data = self.m.getquotaroot(self._encodefolder(folder))
|
||||
if status == "OK":
|
||||
quotadef = data[1][0]
|
||||
m = re.match("[^\s]+ \(STORAGE (\d+) (\d+)\)", quotadef)
|
||||
if not m:
|
||||
print "Problem while parsing quota def"
|
||||
return
|
||||
self.quota_limit = int(m.group(2))
|
||||
self.quota_actual = int(m.group(1))
|
||||
|
||||
def fetchpart(self, uid, folder, part):
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
typ, data = self.m.fetch(uid, "(BODY[%(p)s.MIME] BODY[%(p)s])" \
|
||||
% {"p" : part})
|
||||
if typ != "OK":
|
||||
return None
|
||||
msg = email.message_from_string(data[0][1] + data[1][1])
|
||||
return msg
|
||||
|
||||
def fetch(self, start=None, stop=None, folder=None, all=False, **kwargs):
|
||||
if not start and not stop:
|
||||
return []
|
||||
result = []
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
if start and stop:
|
||||
submessages = self.messages[start - 1:stop]
|
||||
range = ",".join(submessages)
|
||||
else:
|
||||
submessages = [start]
|
||||
range = start
|
||||
if not all:
|
||||
query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])'
|
||||
else:
|
||||
query = '(RFC822)'
|
||||
typ, data = self.m.fetch(range, query)
|
||||
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
|
||||
if not "\\Seen" in flags:
|
||||
msg["class"] = "unseen"
|
||||
if "\\Answered" in flags:
|
||||
msg["img_flags"] = static_url("pics/answered.png")
|
||||
tmpdict[imapid] = msg
|
||||
for id in submessages:
|
||||
result += [tmpdict[id]]
|
||||
return result
|
||||
|
||||
def get_imapconnector(request):
|
||||
imapc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
return imapc
|
||||
+2
-281
@@ -22,19 +22,8 @@ from modoboa.lib.connections import ConnectionsManager
|
||||
from modoboa.lib.email_listing import MBconnector, EmailListing
|
||||
from modoboa.lib.emailutils import *
|
||||
|
||||
class WebmailError(Exception):
|
||||
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
|
||||
|
||||
def __init__(self, reason, ajax=False):
|
||||
m = WebmailError.errorexpr.match(reason)
|
||||
if m is None:
|
||||
self.reason = reason
|
||||
else:
|
||||
self.reason = "%s: %s" % (_("Server response"), m.group(2))
|
||||
self.ajax = ajax
|
||||
|
||||
def __str__(self):
|
||||
return self.reason
|
||||
from imaputils import *
|
||||
from exceptions import *
|
||||
|
||||
class WMtable(tables.Table):
|
||||
tableid = "emails"
|
||||
@@ -108,274 +97,6 @@ class IMAPheader(object):
|
||||
def parse_subject(value, **kwargs):
|
||||
return decode(u2u_decode.u2u_decode(value))
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
def __init__(self, user=None, password=None):
|
||||
self.criterions = []
|
||||
self.address = parameters.get_admin("IMAP_SERVER")
|
||||
self.port = int(parameters.get_admin("IMAP_PORT"))
|
||||
status, msg = self.login(user, password)
|
||||
if not status:
|
||||
raise Exception(msg)
|
||||
|
||||
def refresh(self, user, password):
|
||||
"""Check if current connection needs a refresh
|
||||
|
||||
Is it really secure?
|
||||
"""
|
||||
if self.m is not None:
|
||||
try:
|
||||
self.m.select()
|
||||
return
|
||||
except imaplib.IMAP4.error, error:
|
||||
print error
|
||||
print self.login(user, password)
|
||||
|
||||
|
||||
def login(self, user, passwd):
|
||||
import imaplib
|
||||
try:
|
||||
secured = parameters.get_admin("IMAP_SECURED")
|
||||
if secured == "yes":
|
||||
self.m = imaplib.IMAP4_SSL(self.address, self.port)
|
||||
else:
|
||||
self.m = imaplib.IMAP4(self.address, self.port)
|
||||
except (socket.error, imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Connection to IMAP server failed, check your configuration")
|
||||
try:
|
||||
self.m.login(user, passwd)
|
||||
except (imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Authentication failed, check your configuration")
|
||||
return True, None
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.m.select()
|
||||
except imaplib.IMAP4.error:
|
||||
pass
|
||||
self.m.logout()
|
||||
del self.m
|
||||
self.m = None
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
"""An enhanced version of messages_count
|
||||
|
||||
With IMAP, to know how many messages a mailbox contains, we
|
||||
have to make a request to the server. To avoid requests
|
||||
multiplications, we sort messages in the same time. This will
|
||||
be usefull for other methods.
|
||||
|
||||
:param order: sorting order
|
||||
:param folder: mailbox to scan
|
||||
"""
|
||||
if "order" in kwargs.keys() and kwargs["order"]:
|
||||
sign = kwargs["order"][:1]
|
||||
criterion = kwargs["order"][1:].upper()
|
||||
if sign == '-':
|
||||
criterion = "REVERSE %s" % criterion
|
||||
else:
|
||||
criterion = "REVERSE DATE"
|
||||
folder = kwargs.has_key("folder") and kwargs["folder"] or None
|
||||
(status, data) = self.m.select(self._encodefolder(folder))
|
||||
(status, data) = self.m.sort("(%s)" % criterion, "UTF-8", "(NOT DELETED)",
|
||||
*self.criterions)
|
||||
self.messages = data[0].split()
|
||||
self.getquota(folder)
|
||||
return len(self.messages)
|
||||
|
||||
def unseen_messages(self, folder):
|
||||
"""Return the number of unseen messages for folder"""
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
status, data = self.m.search("UTF-8", "(NOT DELETED UNSEEN)")
|
||||
if status != "OK":
|
||||
return
|
||||
return len(data[0].split())
|
||||
|
||||
def _encodefolder(self, folder):
|
||||
if not folder:
|
||||
return "INBOX"
|
||||
return folder.encode("imap4-utf-7")
|
||||
|
||||
def _parse_folder_name(self, descr, prefix, delimiter, parts):
|
||||
if not len(parts):
|
||||
return False
|
||||
path = "%s%s%s" % (prefix, delimiter, parts[0])
|
||||
sdescr = None
|
||||
for d in descr:
|
||||
if d["path"] == path:
|
||||
sdescr = d
|
||||
break
|
||||
if sdescr is None:
|
||||
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
|
||||
descr += [sdescr]
|
||||
if self._parse_folder_name(sdescr["sub"], path, delimiter, parts[1:]):
|
||||
sdescr["class"] = "subfolders"
|
||||
return True
|
||||
|
||||
def _listfolders(self, topfolder='INBOX', md_folders=[]):
|
||||
list_response_pattern = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
|
||||
(status, data) = self.m.list()
|
||||
result = []
|
||||
for mb in data:
|
||||
flags, delimiter, name = list_response_pattern.match(mb).groups()
|
||||
name = name.strip('"').decode("imap4-utf-7")
|
||||
if re.search("\%s" % delimiter, name):
|
||||
parts = name.split(".")
|
||||
if not descr.has_key("path"):
|
||||
descr["path"] = parts[0]
|
||||
descr["sub"] = []
|
||||
if self._parse_folder_name(descr["sub"], parts[0], delimiter,
|
||||
parts[1:]):
|
||||
descr["class"] = "subfolders"
|
||||
continue
|
||||
present = False
|
||||
descr = {"name" : name}
|
||||
for mdf in md_folders:
|
||||
if mdf["name"] == name:
|
||||
present = True
|
||||
break
|
||||
if not present:
|
||||
result += [descr]
|
||||
from operator import itemgetter
|
||||
return sorted(result, key=itemgetter("name"))
|
||||
|
||||
def getfolders(self, user, unseen_messages=True):
|
||||
md_folders = [{"name" : "INBOX", "class" : "inbox"},
|
||||
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class" : "drafts"},
|
||||
{"name" : 'Junk'},
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER")},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "trash"}]
|
||||
md_folders += self._listfolders(md_folders=md_folders)
|
||||
if unseen_messages:
|
||||
for fd in md_folders:
|
||||
key = fd.has_key("path") and "path" or "name"
|
||||
count = self.unseen_messages(fd[key])
|
||||
if count == 0:
|
||||
continue
|
||||
fd["unseen"] = count
|
||||
return md_folders
|
||||
|
||||
def _add_flag(self, folder, mail_id, flag):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.store(mail_id, "+FLAGS", flag)
|
||||
|
||||
def msg_unread(self, folder, msgset):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.store(msgset, "-FLAGS", r'(\Seen)')
|
||||
|
||||
def msg_read(self, folder, msgset):
|
||||
self._add_flag(folder, msgset, r'(\Seen)')
|
||||
|
||||
def msgforwarded(self, folder, imapid):
|
||||
self._add_flag(folder, imapid, '($Forwarded)')
|
||||
|
||||
def msg_answered(self, folder, imapid):
|
||||
self._add_flag(folder, imapid, r'(\Answered)')
|
||||
|
||||
def move(self, msgset, oldfolder, newfolder):
|
||||
self.m.select(self._encodefolder(oldfolder))
|
||||
status, data = self.m.copy(msgset, self._encodefolder(newfolder))
|
||||
if status == 'OK':
|
||||
self.m.store(msgset, "+FLAGS", r'(\Deleted)')
|
||||
|
||||
def push_mail(self, folder, msg):
|
||||
now = imaplib.Time2Internaldate(time.time())
|
||||
self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
|
||||
|
||||
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", r'(\Deleted)')
|
||||
self.m.expunge()
|
||||
|
||||
def compact(self, folder):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
self.m.expunge()
|
||||
|
||||
def create_folder(self, name, parent=None):
|
||||
if parent is not None:
|
||||
name = "%s.%s" % (parent, name)
|
||||
typ, data = self.m.create(self._encodefolder(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
|
||||
def rename_folder(self, oldname, newname):
|
||||
typ, data = self.m.rename(self._encodefolder(oldname),
|
||||
self._encodefolder(newname))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0], ajax=True)
|
||||
return True
|
||||
|
||||
def delete_folder(self, name):
|
||||
typ, data = self.m.delete(self._encodefolder(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
|
||||
def getquota(self, folder):
|
||||
if not "QUOTA" in self.m.capabilities:
|
||||
self.quota_limit = self.quota_actual = None
|
||||
return
|
||||
|
||||
status, data = self.m.getquotaroot(self._encodefolder(folder))
|
||||
if status == "OK":
|
||||
quotadef = data[1][0]
|
||||
m = re.match("[^\s]+ \(STORAGE (\d+) (\d+)\)", quotadef)
|
||||
if not m:
|
||||
print "Problem while parsing quota def"
|
||||
return
|
||||
self.quota_limit = int(m.group(2))
|
||||
self.quota_actual = int(m.group(1))
|
||||
|
||||
def fetchpart(self, uid, folder, part):
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
typ, data = self.m.fetch(uid, "(BODY[%(p)s.MIME] BODY[%(p)s])" \
|
||||
% {"p" : part})
|
||||
if typ != "OK":
|
||||
return None
|
||||
msg = email.message_from_string(data[0][1] + data[1][1])
|
||||
return msg
|
||||
|
||||
def fetch(self, start=None, stop=None, folder=None, all=False, **kwargs):
|
||||
if not start and not stop:
|
||||
return []
|
||||
result = []
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
if start and stop:
|
||||
submessages = self.messages[start - 1:stop]
|
||||
range = ",".join(submessages)
|
||||
else:
|
||||
submessages = [start]
|
||||
range = start
|
||||
if not all:
|
||||
query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])'
|
||||
else:
|
||||
query = '(RFC822)'
|
||||
typ, data = self.m.fetch(range, query)
|
||||
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
|
||||
if not "\\Seen" in flags:
|
||||
msg["class"] = "unseen"
|
||||
if "\\Answered" in flags:
|
||||
msg["img_flags"] = static_url("pics/answered.png")
|
||||
tmpdict[imapid] = msg
|
||||
for id in submessages:
|
||||
result += [tmpdict[id]]
|
||||
return result
|
||||
|
||||
class ImapListing(EmailListing):
|
||||
tpl = "webmail/index.html"
|
||||
tbltype = WMtable
|
||||
|
||||
@@ -89,7 +89,7 @@ def menu(**kwargs):
|
||||
return [
|
||||
{"name" : "webmail",
|
||||
"label" : _("Webmail"),
|
||||
"url" : reverse(views.index),
|
||||
"url" : reverse(views.newindex),
|
||||
"img" : static_url("pics/webmail.png")}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,3 +1,531 @@
|
||||
var Webmail = new Class({
|
||||
Implements: [Options],
|
||||
|
||||
/*
|
||||
* Default values for options
|
||||
*/
|
||||
defaults: {
|
||||
refreshRate: 0,
|
||||
modify_url: "",
|
||||
delete_url: "",
|
||||
move_url: ""
|
||||
},
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
*/
|
||||
initialize: function(options) {
|
||||
this.setOptions(options);
|
||||
|
||||
this.rtimer = null;
|
||||
this.editorid = "id_body";
|
||||
|
||||
this.init_menubar();
|
||||
this.init_folders_buttons();
|
||||
this.init_folders_browsing();
|
||||
this.init_emails_table();
|
||||
|
||||
current_anchor.register_callback("viewmail", this.viewmail_callback.bind(this));
|
||||
current_anchor.register_callback("compose", this.compose_callback.bind(this));
|
||||
current_anchor.register_callback("reply", this.compose_callback.bind(this));
|
||||
current_anchor.register_callback("replyall", this.compose_callback.bind(this));
|
||||
current_anchor.register_callback("forward", this.compose_callback.bind(this));
|
||||
},
|
||||
|
||||
/*
|
||||
* Menubar initialization
|
||||
*/
|
||||
init_menubar: function() {
|
||||
searchbox_init();
|
||||
$$("a[name=mark-read]").addEvent("click", this.send_mark_request.bind(this));
|
||||
$$("a[name=mark-unread]").addEvent("click", this.send_mark_request.bind(this));
|
||||
$$("a[name=fdaction]").addEvent("click", function(evt) {
|
||||
this.simple_request(evt.target.get("href"), {});
|
||||
evt.stop();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
send_mark_request: function(evt) {
|
||||
evt.stop();
|
||||
if (!this.emails_table._selectedRows.length) {
|
||||
return;
|
||||
}
|
||||
var ids = new Array();
|
||||
|
||||
this.emails_table._selectedRows.each(function(item, index) {
|
||||
ids.include(item.get("id"));
|
||||
});
|
||||
this.simple_request(evt.target.get("href"), {
|
||||
ids : ids.join()
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes the misc. buttons available to manipulate folders.
|
||||
*/
|
||||
init_folders_buttons: function() {
|
||||
$$("li[class*=clickable]").removeEvents();
|
||||
|
||||
$$("a[name=loadfolder]").addEvent("click", this.folder_loader.bind(this));
|
||||
|
||||
SqueezeBox.assign($("folders").getElements('a[class=boxed]'), {
|
||||
parse: 'rel'
|
||||
});
|
||||
|
||||
var fdbuttons = $$("img[class*=footer]");
|
||||
var fdmenu = new FdMenu({
|
||||
modify_url: this.options.modify_url,
|
||||
delete_url: this.options.delete_url
|
||||
});
|
||||
|
||||
fdbuttons.addEvents({
|
||||
click: function(evt) {
|
||||
var parent = this.getParent();
|
||||
var fdname = parent.getFirst("a[name=loadfolder]").get("href");
|
||||
|
||||
fdmenu.toggle(this, fdname);
|
||||
evt.stop();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Enables folders opening/closing
|
||||
*
|
||||
* Available for folders with subfolders.
|
||||
*/
|
||||
init_folders_browsing: function() {
|
||||
$$("div[class*=clickbox]").addEvent("click", function(evt) {
|
||||
var parent = this.getParent();
|
||||
var ul = parent.getFirst("ul[name=" + parent.get("name") + "]");
|
||||
|
||||
if (!$defined(ul)) {
|
||||
return;
|
||||
}
|
||||
if (ul.hasClass("hidden")) {
|
||||
this.removeClass("collapsed").addClass("expanded");
|
||||
ul.removeClass("hidden").addClass("visible");
|
||||
} else {
|
||||
this.removeClass("expanded").addClass("collapsed");
|
||||
ul.removeClass("visible").addClass("hidden");
|
||||
}
|
||||
evt.stop();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Select a particular folder
|
||||
*/
|
||||
select_folder: function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.getParent().removeClass("selected");
|
||||
item.getParent().addClass("droppable");
|
||||
});
|
||||
obj.getParent().removeClass("droppable");
|
||||
obj.getParent().addClass("selected");
|
||||
|
||||
$$(obj.getParents("ul[class*=hidden]")).each(function(item) {
|
||||
item.removeClass("hidden");
|
||||
item.addClass("visible");
|
||||
$$("li[name=" + item.get("name") + "]").each(function(item) {
|
||||
item.setStyle(
|
||||
"background",
|
||||
"url('" + static_url("pics/folder-open.png") + "') no-repeat"
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Automatic folder listing refresh
|
||||
*
|
||||
* Called at a regular frequency to make an asynchronous request that
|
||||
* retrieves a folder's content (ie. mails)
|
||||
*/
|
||||
refresh_folder: function() {
|
||||
var query = current_anchor.serialized.substring(1);
|
||||
|
||||
clearInterval(this.rtimer);
|
||||
infobox.show(gettext("Reloading..."), {
|
||||
profile : "gray",
|
||||
spinner : true
|
||||
});
|
||||
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
|
||||
var callback = ($defined(resp.callback)) ? resp.callback : "default";
|
||||
|
||||
current_anchor.get_callback(callback)(resp);
|
||||
infobox.info(gettext("Done"));
|
||||
infobox.hide(1);
|
||||
}}).get();
|
||||
},
|
||||
|
||||
init_automatic_refresh: function() {
|
||||
this.rtimer = this.refresh_folder
|
||||
.periodical(this.options.refreshRate);
|
||||
},
|
||||
|
||||
/*
|
||||
* Emails table initialization.
|
||||
*/
|
||||
init_emails_table: function() {
|
||||
this.emails_table = new HtmlTable($("emails"), {
|
||||
selectable: true,
|
||||
shiftForMultiSelect: true
|
||||
}).addEvents({
|
||||
rowFocus: function(tr) {
|
||||
clearInterval(this.rtimer);
|
||||
}.bind(this),
|
||||
rowUnfocus: function(tr) {
|
||||
if (this.emails_table._selectedRows.length == 0) {
|
||||
this.init_automatic_refresh();
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
/* The IE way for disabling default selection */
|
||||
/* Actually it doesn't work :p */
|
||||
$$("tbody>tr").set("unselectable", "on");
|
||||
|
||||
if ($("toggleselect")) {
|
||||
$("toggleselect").addEvent("click", function(evt) {
|
||||
if (evt.target.checked) {
|
||||
this.emails_table.selectAll();
|
||||
} else {
|
||||
this.emails_table.selectNone();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
init_sortable_columns();
|
||||
update_sortable_column();
|
||||
|
||||
$$("td[class*=draggable]").each(function(item) {
|
||||
new Drag.Move(item, {
|
||||
droppables: ".droppable"
|
||||
}).addEvents({
|
||||
beforeStart: this.bstart_dragging,
|
||||
start: this.start_dragging,
|
||||
enter: this.enter_droppable,
|
||||
leave: this.leave_droppable,
|
||||
drop: this.drop_element,
|
||||
cancel: this.cancel_dragging
|
||||
});
|
||||
});
|
||||
|
||||
$$("tbody>tr").addEvent("dblclick", viewmail);
|
||||
},
|
||||
|
||||
/*
|
||||
* Drag & Drop feature
|
||||
*/
|
||||
|
||||
/*
|
||||
* 'before start' event callback
|
||||
*/
|
||||
bstart_dragging: function(item) {
|
||||
var block = new Element("div", {id: "draggeditems"})
|
||||
.setStyles({"position" : "absolute",
|
||||
"opacity" : 0.8,
|
||||
"width" : "200px",
|
||||
"padding" : "5px",
|
||||
"text-align" : "center",
|
||||
"height" : "25px",
|
||||
"border" : "1px solid #333",
|
||||
"background" : "white",
|
||||
display: "none",
|
||||
"z-index" : 3,
|
||||
top : this.element.getCoordinates()['top'],
|
||||
left : this.element.getCoordinates()['left']})
|
||||
.inject(document.body);
|
||||
block.store("parent", this.element);
|
||||
this.element = block;
|
||||
clearInterval(rtimer);
|
||||
},
|
||||
|
||||
start_dragging: function(item) {
|
||||
var tr = this.element.retrieve("parent").getParent();
|
||||
|
||||
if (!emails_table.isSelected(tr)) {
|
||||
emails_table.selectNone();
|
||||
emails_table.selectRow(tr);
|
||||
}
|
||||
item.set("html", "Moving " + emails_table._selectedRows.length + " messages");
|
||||
this.element.setStyle("display", "block");
|
||||
},
|
||||
|
||||
enter_droppable: function(item, droppable) {
|
||||
droppable.addClass("dragging");
|
||||
},
|
||||
|
||||
leave_droppable: function(item, droppable) {
|
||||
droppable.removeClass("dragging");
|
||||
},
|
||||
|
||||
restore_parent: function(drag, item) {
|
||||
var parent = item.retrieve("parent");
|
||||
|
||||
drag.element = parent;
|
||||
this.init_automatic_refresh();
|
||||
},
|
||||
|
||||
drop_element: function(item, droppable, event) {
|
||||
this.element.dispose();
|
||||
delete(this.element);
|
||||
if (!droppable) {
|
||||
restore_parent(this, item);
|
||||
return;
|
||||
}
|
||||
droppable.removeClass("dragging");
|
||||
dest = gethref(droppable.getFirst("a"));
|
||||
msgset = new Array();
|
||||
this.emails_table._selectedRows.each(function(item, index) {
|
||||
msgset.include(item.get("id"));
|
||||
});
|
||||
this.simple_request(this.options.move_url, {
|
||||
msgset : msgset.join(),
|
||||
to : dest
|
||||
});
|
||||
},
|
||||
|
||||
cancel_dragging: function(item) {
|
||||
this.element.dispose();
|
||||
delete(this.element);
|
||||
restore_parent(this, item);
|
||||
},
|
||||
|
||||
/*
|
||||
* Bottom left quota bar initialisation.
|
||||
*/
|
||||
init_quota_bar: function() {
|
||||
this.quota_pb = new ProgressBar({
|
||||
container: $("quotabox"),
|
||||
startPercentage: 0,
|
||||
boxID: "box",
|
||||
percentageID: "perc",
|
||||
displayText: true,
|
||||
displayID: "quotatext"
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
page_update: function(response) {
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
* Webmail actions start here
|
||||
*/
|
||||
|
||||
/*
|
||||
* Onclick callback used to load the content of a particular
|
||||
* folder. (activated when clicking on a folder's name)
|
||||
*/
|
||||
_folder_loader: function(event, obj) {
|
||||
event.stop();
|
||||
if (!$defined(obj)) {
|
||||
obj = $(event.target);
|
||||
}
|
||||
current_anchor.parse_string(gethref(obj), true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
},
|
||||
|
||||
folder_loader: function(event) {
|
||||
this.select_folder(event.target);
|
||||
this._folder_loader(event, event.target);
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback for 'folder' action
|
||||
*/
|
||||
folder_callback: function(resp) {
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback of the 'viewmail' action
|
||||
*/
|
||||
viewmail_callback: function(resp) {
|
||||
wm_updatelisting(resp, "hidden");
|
||||
$$("a[name=back]").addEvent("click", this._folder_loader);
|
||||
$$("a[name=reply]").addEvent("click", this.reply_loader);
|
||||
$$("a[name=replyall]").addEvent("click", this.replyall_loader);
|
||||
$$("a[name=forward]").addEvent("click", this.forward_loader);
|
||||
$$("a[name=delete]").addEvent("click", function(event) {
|
||||
var lnk = event.target;
|
||||
event.stop();
|
||||
this.simple_request(lnk.get("href"));
|
||||
}.bind(this));
|
||||
$$("a[name=activate_links]").addEvent("click", function(evt) {
|
||||
evt.stop();
|
||||
current_anchor.setparam("links", "1").update();
|
||||
});
|
||||
$$("a[name=disable_links]").addEvent("click", function(evt) {
|
||||
evt.stop();
|
||||
current_anchor.setparam("links", "0").update();
|
||||
});
|
||||
setDivHeight("mailcontent", 5, 0);
|
||||
},
|
||||
|
||||
/*
|
||||
* Loader of the 'compose' action (called when the associated button
|
||||
* is clicked)
|
||||
*/
|
||||
compose_loader: function(event) {
|
||||
event.stop();
|
||||
current_anchor.baseurl("compose").update();
|
||||
},
|
||||
|
||||
/*
|
||||
* Loader of the 'reply' action (called when the associated button
|
||||
* is clicked).
|
||||
*/
|
||||
reply_loader: function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href");
|
||||
},
|
||||
|
||||
/*
|
||||
* Loader of the 'replyall' action (called when the associated button
|
||||
* is clicked).
|
||||
*/
|
||||
replyall_loader: function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href") + "?all=1";
|
||||
},
|
||||
|
||||
/*
|
||||
* Loader of the 'forward' action (called when the associated button
|
||||
* is clicked).
|
||||
*/
|
||||
forward_loader: function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href");
|
||||
},
|
||||
|
||||
/*
|
||||
* Resize the current editor on page resizing.
|
||||
*/
|
||||
resize_window_callback: function(event) {
|
||||
CKEDITOR.instances[this.editorid]
|
||||
.resize("100%", $("body_container").getSize().y);
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback of the 'compose' action.
|
||||
*
|
||||
* It is also shared with other similar actions : reply, forward.
|
||||
*/
|
||||
compose_callback: function(resp) {
|
||||
wm_updatelisting(resp);
|
||||
|
||||
window.addEvent("resize", this.resize_window_callback.bind(this));
|
||||
|
||||
var editormode = resp.editor;
|
||||
|
||||
if (editormode == "html") {
|
||||
var instance = CKEDITOR.instances[editorid];
|
||||
if (instance) {
|
||||
CKEDITOR.remove(instance);
|
||||
}
|
||||
CKEDITOR.replace(editorid, {
|
||||
customConfig: static_url("js/editor_config.js")
|
||||
});
|
||||
CKEDITOR.on("instanceReady", function(evt) {
|
||||
this.resize_window_callback();
|
||||
}.bind(this));
|
||||
}
|
||||
if (resp.id) {
|
||||
current_anchor.setparam("id", resp.id).update(0, 1);
|
||||
}
|
||||
$("attachments").addEvent("click", function(evt) {
|
||||
SqueezeBox.open(this.get("name"), {
|
||||
handler: "iframe",
|
||||
size: {x: 350, y: 400},
|
||||
closeBtn: false
|
||||
});
|
||||
});
|
||||
|
||||
$$("a[name=back]")
|
||||
.removeEvents("click")
|
||||
.addEvent("click", this._folder_loader);
|
||||
$$("a[name=sendmail]")
|
||||
.store("editormode", editormode)
|
||||
.removeEvents("click")
|
||||
.addEvent("click", this.sendmail_callback.bind(this));
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback of the 'sendmail' action.
|
||||
*/
|
||||
sendmail_callback: function(evt) {
|
||||
evt.stop();
|
||||
infobox.show(gettext("Sending..."), {
|
||||
profile : "gray",
|
||||
spinner : true
|
||||
});
|
||||
disable_link(this);
|
||||
$("composemail").set("send", {
|
||||
onSuccess: function(resp) {
|
||||
resp = JSON.decode(resp);
|
||||
if (resp.status == "ko") {
|
||||
current_anchor.get_callback("compose")(resp);
|
||||
if ($defined(resp.respmsg)) {
|
||||
infobox.error(resp.respmsg);
|
||||
}
|
||||
enable_link(this, sendmail_callback);
|
||||
return;
|
||||
}
|
||||
current_anchor.parse_string(resp.url, true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
this.eliminate("editormode");
|
||||
}.bind(this)
|
||||
});
|
||||
if (this.retrieve("editormode") == "html") {
|
||||
CKEDITOR.instances[editorid].updateElement();
|
||||
}
|
||||
$("composemail").send();
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* Simple AJAX request
|
||||
*
|
||||
* Initialize the infobox with a loading message and wait for a JSON
|
||||
* encoded answer. The infobox is cleared by the request callback
|
||||
* function.
|
||||
*/
|
||||
simple_request: function(url, params) {
|
||||
infobox.show(gettext("Waiting..."), {
|
||||
profile: "gray",
|
||||
spinner: true
|
||||
});
|
||||
new Request.JSON({
|
||||
url : url,
|
||||
onSuccess : function(response) {
|
||||
if (response.status == "ok") {
|
||||
if (!$defined(response.next)) {
|
||||
current_anchor.get_callback("folder")(response);
|
||||
}
|
||||
infobox.info(gettext("Done"));
|
||||
if ($defined(response.next)) {
|
||||
(function(response) {
|
||||
current_anchor.parse_string(response.next).update();
|
||||
select_folder($$("a[href=" + current_anchor.getbaseurl() + "]"));
|
||||
}).delay(500, this, response);
|
||||
} else {
|
||||
infobox.hide(1);
|
||||
}
|
||||
} else {
|
||||
infobox.error(response.respmsg);
|
||||
}
|
||||
}
|
||||
}).get(params);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
|
||||
@@ -49,7 +49,7 @@ function wm_updatelisting(resp) {
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
quota_pb = new ProgressBar({
|
||||
/*quota_pb = new ProgressBar({
|
||||
container: $("quotabox"),
|
||||
startPercentage: 0,
|
||||
boxID: "box",
|
||||
@@ -69,6 +69,13 @@ function wm_updatelisting(resp) {
|
||||
clearInterval(rtimer);
|
||||
rtimer = null;
|
||||
}
|
||||
});*/
|
||||
|
||||
new Webmail({
|
||||
refreshRate: {{ refreshrate }},
|
||||
modify_url: "{% url extensions.webmail.views.editfolder %}",
|
||||
delete_url: "{% url extensions.webmail.views.delfolder %}",
|
||||
move_url: "{% url extensions.webmail.views.move as moveurl %}"
|
||||
});
|
||||
|
||||
current_anchor.register_callback("folder", function(resp) {
|
||||
@@ -211,9 +218,9 @@ function wm_updatelisting(resp) {
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
|
||||
{% block leftcol %}
|
||||
<div id="folders" class="box">
|
||||
</div>
|
||||
<div id="folders" class="box">{{ mboxes }}</div>
|
||||
{% endblock %}
|
||||
{% block menu %}{{ menu }}{% endblock %}
|
||||
{% block footer %}
|
||||
<div id="quotalabel">{% trans "Mailbox quota" %}</div>
|
||||
<div id="quotabox"></div>
|
||||
|
||||
@@ -69,7 +69,7 @@ def compose_menu(selection, backurl, user):
|
||||
"user" : user})
|
||||
|
||||
@register.simple_tag
|
||||
def listing_menu(selection, folder, user):
|
||||
def listmailbox_menu(selection, folder, user):
|
||||
entries = [
|
||||
{"name" : "compose",
|
||||
"url" : reverse(webmail.views.compose),
|
||||
|
||||
@@ -6,7 +6,9 @@ from django.conf.urls.defaults import *
|
||||
# "attachments"), the associated folder will not be accessible.
|
||||
#
|
||||
urlpatterns = patterns('modoboa.extensions.webmail.views',
|
||||
(r'^$', "index"),
|
||||
#(r'^$', "index"),
|
||||
(r'^$', "newindex"),
|
||||
|
||||
(r'^compose/$', "compose"),
|
||||
(r'^move/$', "move"),
|
||||
(r'^mark/(?P<name>.+)/$', "mark"),
|
||||
|
||||
@@ -497,3 +497,67 @@ def delattachment(request):
|
||||
if error is None:
|
||||
error = _("Unknown attachment")
|
||||
return ajax_response(request, "ko", respmsg=error)
|
||||
|
||||
#
|
||||
# NEW CODE STARTS HERE
|
||||
#
|
||||
def render_mboxes_list(request, imapc):
|
||||
return _render_to_string(request, "webmail/folders.html", {
|
||||
"titlebar" : True,
|
||||
"selected" : request.session["mbox"],
|
||||
"folders" : imapc.getfolders(request.user),
|
||||
"withmenu" : True,
|
||||
"withunseen" : True
|
||||
})
|
||||
|
||||
def listmailbox(request):
|
||||
mbox = request.GET.get("mbox", "INBOX")
|
||||
request.session["mbox"] = mbox
|
||||
lst = ImapListing(request.user, request.session["password"],
|
||||
baseurl=mbox, folder=mbox, order="-date")
|
||||
|
||||
return dict(listing=lst.render(request))
|
||||
|
||||
@login_required
|
||||
@is_not_localadmin()
|
||||
def newindex(request):
|
||||
"""Webmail actions handler
|
||||
|
||||
Problèmes liés à la navigation 'anchor based'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Lors d'un rafraichissemt complet, une première requête est envoyée
|
||||
vers /webmail/. On ne connait pas encore l'action qui va être
|
||||
demandée mais on peut déjà envoyer des informations indépendantes
|
||||
(comme les dossiers, le quota).
|
||||
|
||||
Si on se contente de cela, l'affichage donnera un aspect décomposé
|
||||
qui n'est pas très séduisant (à cause de la latence notamment). Il
|
||||
faudrait pouvoir envoyer le menu par la même occasion, le souci
|
||||
étant de savoir lequel...
|
||||
|
||||
"""
|
||||
action = request.GET.get("action", None)
|
||||
json = request.GET.get("json", False)
|
||||
|
||||
if action is not None:
|
||||
try:
|
||||
response = globals()[action](request)
|
||||
except KeyError:
|
||||
raise WebmailError(_("Undefined action"))
|
||||
else:
|
||||
if json:
|
||||
raise WebmailError(_("Bad request"))
|
||||
response = dict(deflocation="?action=listmailbox",
|
||||
defcallback="wm_updatelisting")
|
||||
|
||||
if not json:
|
||||
imapc = get_imapconnector(request)
|
||||
response["refreshrate"] = parameters.get_user(request.user, "REFRESH_INTERVAL")
|
||||
response["mboxes"] = render_mboxes_list(request, imapc)
|
||||
try:
|
||||
menufunc = globals()["%s_menu" % action]
|
||||
except KeyError:
|
||||
menufunc = globals()["listmailbox_menu"]
|
||||
response["menu"] = menufunc("", request.session["mbox"], request.user)
|
||||
return _render(request, "webmail/index.html", response)
|
||||
|
||||
@@ -56,7 +56,7 @@ function update_sortable_column() {
|
||||
|
||||
window.addEvent("domready", function() {
|
||||
navparams = new Hash({
|
||||
{% join navparams %}
|
||||
{% if navparams %}{% join navparams %}{% endif %}
|
||||
});
|
||||
{% block js %}{% endblock %}
|
||||
current_anchor.set_default_callback({{ defcallback }});
|
||||
|
||||
Reference in New Issue
Block a user