diff --git a/extensions/amavis_quarantine/__init__.py b/extensions/amavis_quarantine/__init__.py
index d19402022..a93a74156 100644
--- a/extensions/amavis_quarantine/__init__.py
+++ b/extensions/amavis_quarantine/__init__.py
@@ -103,11 +103,9 @@ def extra_static_content(user):
return []
tpl = Template("""""")
url = reverse("modoboa.extensions.amavis_quarantine.views.nbrequests")
diff --git a/extensions/amavis_quarantine/views.py b/extensions/amavis_quarantine/views.py
index b2c23e500..b7c664679 100644
--- a/extensions/amavis_quarantine/views.py
+++ b/extensions/amavis_quarantine/views.py
@@ -303,4 +303,4 @@ def process(request):
@user_passes_test(lambda u: u.group != 'SimpleUsers')
def nbrequests(request):
nbrequests = get_nb_requests(request.user)
- return ajax_simple_response(dict(requests=nbrequests))
+ return ajax_simple_response(dict(status="ok", requests=nbrequests))
diff --git a/extensions/sievefilters/forms.py b/extensions/sievefilters/forms.py
index 549c9934d..f9e529142 100644
--- a/extensions/sievefilters/forms.py
+++ b/extensions/sievefilters/forms.py
@@ -150,23 +150,26 @@ class FilterForm(forms.Form):
def _build_fileinto_field(self, request, value):
self._build_action_field(request, "fileinto", value)
- def __build_folders_list(self, folders):
+ def __build_folders_list(self, folders, user, imapc, parentmb=None):
ret = []
for fd in folders:
value = fd["path"] if fd.has_key("path") else fd["name"]
- ret += [(value, fd["name"])]
- if fd.has_key("sub") and len(fd["sub"]):
- ret += self.__build_folders_list(fd["sub"])
+ if parentmb:
+ ret += [(value, fd["name"].replace("%s." % parentmb, ""))]
+ else:
+ ret += [(value, fd["name"])]
+ if fd.has_key("sub"):
+ submboxes = imapc.getmboxes(user, value, unseen_messages=False)
+ ret += self.__build_folders_list(submboxes, user, imapc, value)
return ret
def userfolders(self, request):
- from modoboa.extensions.webmail.lib import IMAPconnector
+ from modoboa.extensions.webmail.imaputils import get_imapconnector
- mbc = IMAPconnector(user=request.user.username,
- password=request.session["password"])
- ret = mbc.getfolders(request.user, unseen_messages=False)
+ mbc = get_imapconnector(request)
+ ret = mbc.getmboxes(request.user, unseen_messages=False)
- folders = self.__build_folders_list(ret)
+ folders = self.__build_folders_list(ret, request.user, mbc)
return folders
def tofilter(self):
diff --git a/extensions/sievefilters/static/js/dynamic_fields.js b/extensions/sievefilters/static/js/dynamic_fields.js
deleted file mode 100644
index 0dcafb5f4..000000000
--- a/extensions/sievefilters/static/js/dynamic_fields.js
+++ /dev/null
@@ -1,208 +0,0 @@
-
-var DynamicCondition = new Class({
-
- Extends: DynamicTextInput,
-
- options: {
- defaultcond: "Subject",
- templates: []
- },
-
- loadoperators: function(container, tpl) {
- container.empty();
- for (var ocpt = 0; ocpt < tpl["operators"].length; ocpt++) {
- var opt = new Element("option", {
- value: tpl["operators"][ocpt][0],
- html: tpl["operators"][ocpt][1]
- });
- opt.inject(container);
- }
- },
-
- onTargetChange: function(event) {
- var parts = event.target.get("name").split("_");
- var container = $("id_cond_operator_" + parts[2]);
-
- for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
- var tpl = this.options.templates[cpt];
- if (tpl["name"] != event.target.get("value")) {
- continue;
- }
- this.loadoperators(container, tpl);
- break;
- }
- },
-
- createfield: function() {
- var div = new Element("div", {"class": "item"});
- var condtarget = new Element("select", {
- id: "id_cond_target_" + this.nextid,
- name: "cond_target_" + this.nextid
- });
- var condoperator = new Element("select", {
- id: "id_cond_operator_" + this.nextid,
- name: "cond_operator_" + this.nextid
- });
- var condvalue = new Element("input", {
- id: "id_cond_value_" + this.nextid,
- name: "cond_value_" + this.nextid,
- type: "text"
- });
-
- for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
- var tpl = this.options.templates[cpt];
- var opt = new Element("option", {
- value: tpl["name"],
- html: tpl["label"]
- });
- opt.inject(condtarget);
-
- if (tpl["name"] != this.options.defaultcond) {
- continue;
- }
- this.loadoperators(condoperator, tpl);
- }
- condtarget.inject(div);
- condtarget.addEvent("change", this.onTargetChange.bind(this));
- condoperator.inject(div);
- condvalue.inject(div);
- return [div, condvalue];
- },
-
- removeelement: function(event) {
- var id = (event.target.get("id").split("_"))[1];
-
- event.stop();
- $("id_cond_target_" + id).destroy();
- $("id_cond_operator_" + id).destroy();
- $("id_cond_value_" + id).destroy();
- event.target.destroy();
- this.fields_cnt--;
- }
-});
-
-var DynamicAction = new Class({
- Extends: DynamicTextInput,
-
- options: {
- defaultaction: "fileinto",
- templates: [],
- ufolders_url: null
- },
-
- initialize: function(targets, options) {
- this.parent(targets, options);
- $("id_action_arg_0_0").getElements("option").each(function(item) {
- var cpt = 1;
- var start = 0;
- var value = item.get("value");
-
- while (true) {
- var id = value.indexOf(".", start);
- if (id == -1) {
- break;
- }
- item.setStyle("margin-left", (cpt * 20) + "px");
- cpt++;
- start = id + 1;
- }
- });
- this.fdoptions = $("id_action_arg_0_0").getElements("option");
- },
-
- loadargs: function(id, tpl) {
- var res = new Elements();
-
- for (var acpt = 0; acpt < tpl["args"].length; acpt++) {
- var argname = "action_arg_" + id + "_" + acpt;
- var child = null;
-
- if (tpl["args"][acpt]["type"] == "list") {
- child = new Element("select", {id: "id_" + argname, name: argname});
- if ($defined(tpl["args"][acpt]["vloader"])) {
- this[tpl["args"][acpt]["vloader"]](child);
- }
- } else if (tpl["args"][acpt]["type"] == "string") {
- child = new Element("input", {type: "text",
- id: "id_" + argname, name: argname});
- }
- res.push(child);
- }
- return res;
- },
-
- deletePrevArgs: function(id) {
- for (var cpt = 0; true; cpt++) {
- var arg = $("id_action_arg_" + id + "_" + cpt);
- if (!$defined(arg)) {
- break;
- }
- arg.destroy();
- }
- },
-
- onNameChange: function(event) {
- var parts = event.target.get("name").split("_");
- var container = event.target.getParent();
-
- event.stop();
- this.deletePrevArgs(parts[2]);
- for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
- var tpl = this.options.templates[cpt];
- if (tpl["name"] != event.target.get("value")) {
- continue;
- }
- var args = this.loadargs(parts[2], tpl);
- args.inject(container);
- }
- },
-
- createfield: function() {
- var div = new Element("div", {"class": "item"});
- var actionname = new Element("select", {
- id: "id_action_name_" + this.nextid,
- name: "action_name_" + this.nextid
- });
- var args = null;
-
- for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
- var tpl = this.options.templates[cpt];
- var opt = new Element("option", {
- value: tpl["name"],
- html: tpl["label"]
- });
- opt.inject(actionname);
-
- if (tpl["name"] != this.options.defaultaction) {
- continue;
- }
- args = this.loadargs(this.nextid, tpl);
- }
- actionname.inject(div);
- actionname.addEvent("change", this.onNameChange.bind(this));
- args.inject(div);
- return [div, actionname];
- },
-
- removeelement: function(event) {
- var id = (event.target.get("id").split("_"))[1];
-
- event.stop();
- $("id_action_name_" + id).destroy();
- for (var cpt = 0; true; cpt++) {
- var arg = $("id_action_arg_" + id + "_" + cpt);
- if (!$defined(arg)) {
- break;
- }
- arg.destroy();
- }
- event.target.destroy();
- this.fields_cnt--;
- },
-
- userfolders: function(container) {
- this.fdoptions.each(function(item) {
- item.clone().inject(container);
- });
- }
-});
\ No newline at end of file
diff --git a/extensions/sievefilters/static/js/jquery.dynfields.js b/extensions/sievefilters/static/js/jquery.dynfields.js
index eb3c61264..7980d9c6b 100644
--- a/extensions/sievefilters/static/js/jquery.dynfields.js
+++ b/extensions/sievefilters/static/js/jquery.dynfields.js
@@ -54,7 +54,7 @@
return $('', {
href: '#',
id: 'condition_' + this.nextid + '_rmbtn',
- html: ''
+ html: ''
}).click($.proxy(this.removefields, this));
},
@@ -250,7 +250,7 @@
return $('', {
href: '#',
id: 'action_' + this.nextid + '_rmbtn',
- html: ''
+ html: ''
}).click($.proxy(this.removefields, this));
},
@@ -322,7 +322,6 @@
$.fn.dynaction.defaults = {
defaultaction: "fileinto",
- templates: [],
- ufolders_url: null
+ templates: []
};
})(jQuery);
\ No newline at end of file
diff --git a/extensions/sievefilters/templates/sievefilters/index.html b/extensions/sievefilters/templates/sievefilters/index.html
index 03dcc4eb5..783b74be8 100644
--- a/extensions/sievefilters/templates/sievefilters/index.html
+++ b/extensions/sievefilters/templates/sievefilters/index.html
@@ -22,8 +22,7 @@ function filterform_cb() {
templates: cond_templates
}).data('dyncondition');
var $da = $("#action_0").dynaction({
- templates: action_templates,
- ufolders_url: "{% url modoboa.extensions.sievefilters.views.get_user_folders %}"
+ templates: action_templates
}).data('dynaction');
$("input[name=match_type]").click(function(e) {
diff --git a/extensions/sievefilters/templatetags/sfilters_tags.py b/extensions/sievefilters/templatetags/sfilters_tags.py
index b717b9a31..dbed8e525 100644
--- a/extensions/sievefilters/templatetags/sfilters_tags.py
+++ b/extensions/sievefilters/templatetags/sfilters_tags.py
@@ -53,7 +53,7 @@ def fset_menu(mode, setname):
"url" : reverse("modoboa.extensions.sievefilters.views.activate_filters_set",
args=[setname])},
{"name" : "removefs",
- "img" : "icon-remove",
+ "img" : "icon-trash",
"label" : _("Remove filters set"),
"url" : reverse("modoboa.extensions.sievefilters.views.remove_filters_set",
args=[setname])},
@@ -81,7 +81,7 @@ def filter_actions(setname, f, position, islast):
{"name" : "removefilter",
"url" : reverse("modoboa.extensions.sievefilters.views.removefilter",
args=[setname, f["name"]]),
- "img" : "icon-remove",
+ "img" : "icon-trash",
"title" : _("Remove this filter")}
]
if position != 1:
diff --git a/extensions/sievefilters/urls.py b/extensions/sievefilters/urls.py
index d728be9c0..406c84fd5 100644
--- a/extensions/sievefilters/urls.py
+++ b/extensions/sievefilters/urls.py
@@ -8,7 +8,6 @@ urlpatterns = patterns('modoboa.extensions.sievefilters.views',
(r'^activatefs/(?P.+)/$', 'activate_filters_set'),
(r'^downloadfs/(?P.+)/$', 'download_filters_set'),
(r'^templates/(?P\w+)/$', 'get_templates'),
- (r'^userfolders/$', 'get_user_folders'),
(r'^(?P.+)/newfilter/$', 'newfilter'),
(r'^(?P.+)/editfilter/(?P.+)/$', 'editfilter'),
(r'^(?P.+)/removefilter/(?P.+)/$', 'removefilter'),
diff --git a/extensions/sievefilters/views.py b/extensions/sievefilters/views.py
index 259bc9015..4fe598e44 100644
--- a/extensions/sievefilters/views.py
+++ b/extensions/sievefilters/views.py
@@ -48,16 +48,6 @@ def get_templates(request, ftype):
return ajax_simple_response(FilterForm([], [], request).cond_templates)
return ajax_simple_response(FilterForm([], [], request).action_templates)
-@login_required
-@needs_mailbox()
-def get_user_folders(request):
- from modoboa.extensions.webmail.lib import IMAPconnector
-
- mbc = IMAPconnector(user=request.user.username,
- password=request.session["password"])
- ret = mbc.getfolders(request.user, unseen_messages=False)
- return ajax_simple_response(ret)
-
@login_required
@needs_mailbox()
def getfs(request, name):
diff --git a/extensions/webmail/imaputils.py b/extensions/webmail/imaputils.py
index 26978081b..232dd6d63 100644
--- a/extensions/webmail/imaputils.py
+++ b/extensions/webmail/imaputils.py
@@ -149,7 +149,7 @@ class IMAPconnector(object):
:param name: the command's name
:return: the command's result
"""
- if name in ['FETCH', 'SORT', 'STORE', 'COPY']:
+ if name in ['FETCH', 'SORT', 'STORE', 'COPY', 'SEARCH']:
try:
typ, data = self.m.uid(name, *args)
except imaplib.IMAP4.error, e:
@@ -211,12 +211,15 @@ class IMAPconnector(object):
"""
if self.m is not None:
try:
- self.m.select()
+ self._cmd("CHECK")
+ except ImapError, e:
+ if hasattr(self, "current_mailbox"):
+ del self.current_mailbox
+ else:
return
- except imaplib.IMAP4.error, error:
- print error
- print self.login(user, password)
-
+ status, msg = self.login(user, password)
+ if not status:
+ raise ImapError(msg)
def login(self, user, passwd):
import socket
@@ -263,14 +266,18 @@ class IMAPconnector(object):
else:
criterion = "REVERSE DATE"
folder = kwargs.has_key("folder") and kwargs["folder"] or None
- self.select_mailbox(folder, False)
+
+ # FIXME: pourquoi suis je obligé de faire un SELECT ici? un
+ # EXAMINE plante mais je pense que c'est du à une mauvaise
+ # lecture des réponses de ma part...
+ self.select_mailbox(folder, readonly=False)
data = self._cmd("SORT", "(%s)" % criterion, "UTF-8", "(NOT DELETED)",
*self.criterions)
self.messages = data[0].split()
self.getquota(folder)
return len(self.messages)
- def select_mailbox(self, name, readonly=True):
+ def select_mailbox(self, name, readonly=True, force=False):
"""Issue a SELECT/EXAMINE command to the server
The given name is first 'imap-utf7' encoded.
@@ -278,11 +285,16 @@ class IMAPconnector(object):
:param name: mailbox's name
:param readonly:
"""
+ if hasattr(self, "current_mailbox"):
+ if self.current_mailbox == name and not force:
+ return
+ self.current_mailbox = name
name = name.encode("imap4-utf-7")
if readonly:
self._cmd("EXAMINE", name)
else:
self._cmd("SELECT", name)
+ self.m.state = "SELECTED"
def unseen_messages(self, mailbox):
"""Return the number of unseen messages
@@ -290,21 +302,18 @@ class IMAPconnector(object):
:param mailbox: the mailbox's name
:return: an integer
"""
- # self.select_mailbox(mailbox)
- # data = self._cmd("SEARCH", "UTF-8", "(NOT DELETED UNSEEN)")
- # return len(data[0].split())
data = self._cmd("STATUS", mailbox.encode("imap4-utf-7"), "(UNSEEN)")
m = self.unseen_pattern.match(data[0])
if m is None:
return 0
return int(m.group(1))
- def _encodefolder(self, folder):
+ def _encode_mbox_name(self, folder):
if not folder:
return "INBOX"
return folder.encode("imap4-utf-7")
- def _parse_folder_name(self, descr, prefix, delimiter, parts):
+ def _parse_mailbox_name(self, descr, prefix, delimiter, parts):
if not len(parts):
return False
path = "%s%s%s" % (prefix, delimiter, parts[0])
@@ -316,11 +325,11 @@ class IMAPconnector(object):
if sdescr is None:
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
descr += [sdescr]
- if self._parse_folder_name(sdescr["sub"], path, delimiter, parts[1:]):
+ if self._parse_mailbox_name(sdescr["sub"], path, delimiter, parts[1:]):
sdescr["class"] = "subfolders"
return True
- def _listfolders_simple(self, topmailbox='INBOX', md_mailboxes=[]):
+ def _listmboxes_simple(self, topmailbox='INBOX', md_mailboxes=[]):
(status, data) = self.m.list()
result = []
for mb in data:
@@ -331,8 +340,8 @@ class IMAPconnector(object):
if not descr.has_key("path"):
descr["path"] = parts[0]
descr["sub"] = []
- if self._parse_folder_name(descr["sub"], parts[0], delimiter,
- parts[1:]):
+ if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
+ parts[1:]):
descr["class"] = "subfolders"
continue
present = False
@@ -347,31 +356,35 @@ class IMAPconnector(object):
return sorted(result, key=itemgetter("name"))
@capability('LIST-EXTENDED', '_listfolders_simple')
- def _listfolders(self, topmailbox='', md_mailboxes=[]):
+ def _listmboxes(self, topmailbox='', mailboxes=[]):
pattern = ("%s.%%" % topmailbox.encode("imap4-utf-7")) if len(topmailbox) else "%"
resp = self._cmd("LIST", "", pattern, "RETURN", "(CHILDREN)")
- result = []
+ newmboxes = []
for mb in resp:
flags, delimiter, name, childinfo = \
self.listextended_response_pattern.match(mb).groups()
flags = flags.split(' ')
name = name.decode("imap4-utf-7")
- present = False
- for mdm in md_mailboxes:
+ pos = -1
+ for idx, mdm in enumerate(mailboxes):
if mdm["name"] == name:
- present = True
+ pos = idx
break
- if present:
- continue
- descr = dict(name=name)
+ if pos == -1:
+ descr = dict(name=name)
+ newmboxes += [descr]
+ else:
+ descr = mailboxes[idx]
+
+ if r'\Marked' in flags or not r'\UnMarked' in flags:
+ descr["send_status"] = True
if r'\HasChildren' in flags:
descr["path"] = name
descr["sub"] = []
- result += [descr]
from operator import itemgetter
- return sorted(result, key=itemgetter("name"))
+ mailboxes += sorted(newmboxes, key=itemgetter("name"))
- def getfolders(self, user, topmailbox='', unseen_messages=True):
+ def getmboxes(self, user, topmailbox='', unseen_messages=True):
if len(topmailbox):
md_mailboxes = []
else:
@@ -382,14 +395,18 @@ class IMAPconnector(object):
{"name" : parameters.get_user(user, "SENT_FOLDER")},
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
"class" : "icon-trash"}]
- md_mailboxes += self._listfolders(topmailbox, md_mailboxes)
+ self._listmboxes(topmailbox, md_mailboxes)
+
if unseen_messages:
- for fd in md_mailboxes:
- key = fd.has_key("path") and "path" or "name"
- count = self.unseen_messages(fd[key])
+ for mb in md_mailboxes:
+ if not mb.has_key("send_status"):
+ continue
+ del mb["send_status"]
+ key = mb.has_key("path") and "path" or "name"
+ count = self.unseen_messages(mb[key])
if count == 0:
continue
- fd["unseen"] = count
+ mb["unseen"] = count
return md_mailboxes
def _add_flag(self, mbox, msgset, flag):
@@ -432,20 +449,18 @@ class IMAPconnector(object):
"""
self.select_mailbox(oldmailbox, False)
self._cmd("COPY", msgset, newmailbox.encode("imap4-utf-7"))
- self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted)')
+ self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted \Seen)')
def push_mail(self, folder, msg):
now = imaplib.Time2Internaldate(time.time())
- self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
+ self.m.append(self._encode_mbox_name(folder), r'(\Seen)', now, str(msg))
def empty(self, mbox):
self.select_mailbox(mbox, False)
resp = self._cmd("SEARCH", "ALL")
- print resp
-
- # for num in data[0].split():
- # self.m.store(num, "+FLAGS", r'(\Deleted)')
- # self.m.expunge()
+ seq = ",".join(resp[0].split())
+ self._cmd("STORE", seq, "+FLAGS", r'(\Deleted)')
+ self._cmd("EXPUNGE")
def compact(self, mbox):
"""Compact a specific mailbox
@@ -460,20 +475,20 @@ class IMAPconnector(object):
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))
+ typ, data = self.m.create(self._encode_mbox_name(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))
+ typ, data = self.m.rename(self._encode_mbox_name(oldname),
+ self._encode_mbox_name(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))
+ typ, data = self.m.delete(self._encode_mbox_name(name))
if typ == "NO":
raise WebmailError(data[0])
return True
@@ -483,7 +498,7 @@ class IMAPconnector(object):
self.quota_limit = self.quota_actual = None
return
- data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox),
+ data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
responses=["QUOTAROOT", "QUOTA"])
if data is None:
self.quota_limit = self.quota_actual = None
@@ -526,7 +541,7 @@ class IMAPconnector(object):
msg = email.message_from_string(data[int(uid)]['BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)]'])
msg['imapid'] = uid
if not r'\Seen' in data[int(uid)]['FLAGS']:
- msg['class'] = 'unseen'
+ msg['style'] = 'unseen'
if r'\Answered' in data[int(uid)]['FLAGS']:
msg['img_flags'] = static_url('pics/answered.png')
if r'$Forwarded' in data[int(uid)]['FLAGS']:
@@ -551,6 +566,7 @@ class IMAPconnector(object):
:param readonly:
:param extraheaders:
"""
+ print mbox
self.select_mailbox(mbox, readonly)
if headers is None:
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
@@ -562,6 +578,10 @@ class IMAPconnector(object):
return data[int(mailid)]
def get_imapconnector(request):
+ """Simple shortcut to create a connector
+
+ :param request: a ``Request`` object
+ """
imapc = IMAPconnector(user=request.user.username,
password=request.session["password"])
return imapc
diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py
index 105dd1d46..3d4b92302 100644
--- a/extensions/webmail/templatetags/webextras.py
+++ b/extensions/webmail/templatetags/webextras.py
@@ -143,16 +143,18 @@ def print_folders(folders, selected=None, withunseen=False, selectonly=False):
result += "" % div_state
cssclass = "block"
+ extra_attrs = ""
if withunseen and fd.has_key("unseen"):
label += " (%d)" % fd["unseen"]
cssclass += " unseen"
+ extra_attrs = ' data-toggle="%d"' % fd["unseen"]
iclass = fd["class"] if fd.has_key("class") else "icon-folder-close"
- result += """
+ result += """
%s
""" % (fd.has_key("path") and fd["path"] or fd["name"], cssclass,
- 'selectfolder' if selectonly else 'loadfolder', iclass, label)
+ 'selectfolder' if selectonly else 'loadfolder', extra_attrs, iclass, label)
if fd.has_key("sub") and len(fd["sub"]):
result += "" % (fd["path"], ul_state) \
+ print_folders(fd["sub"], selected, withunseen, selectonly) + "
\n"
diff --git a/extensions/webmail/urls.py b/extensions/webmail/urls.py
index 64a27c006..9e31054d7 100644
--- a/extensions/webmail/urls.py
+++ b/extensions/webmail/urls.py
@@ -11,6 +11,7 @@ urlpatterns = patterns(
(r'^$', "newindex"),
(r'^submailboxes', "submailboxes"),
(r'^getmailcontent', 'getmailcontent'),
+ (r'^unseenmsgs', 'check_unseen_messages'),
# (r'^compose/$', "compose"),
(r'^delete/$', 'delete'),
diff --git a/extensions/webmail/views.py b/extensions/webmail/views.py
index db5b98354..f7f93146c 100644
--- a/extensions/webmail/views.py
+++ b/extensions/webmail/views.py
@@ -63,7 +63,7 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
"folders" : _render_to_string(request, "webmail/folders.html", {
"titlebar" : True,
"selected" : request.session["folder"],
- "folders" : mbc.getfolders(request.user),
+ "folders" : mbc.getmboxes(request.user),
"withunseen" : True
}),
"menu" : menu,
@@ -125,38 +125,50 @@ def index(request):
@login_required
@needs_mailbox()
def getattachment(request, folder, mail_id):
- if request.GET.has_key("partnumber"):
- headers = {"Content-Type" : "text/plain",
- "Content-Transfer-Encoding" : None}
- icon = IMAPconnector(user=request.user.username,
- password=request.session["password"])
- part = icon.fetchpart(mail_id, folder, request.GET["partnumber"])
- if part is not None:
- if part.get_content_maintype() == "message":
- payload = part.get_payload(0)
- 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)
- return resp
- raise Http404
+ """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 :
+ * Conserver le bodystructure parsé précédemment (nécessite du caching)
+
+ * Reparser le bodystructure :p
+ """
+ pnum = request.GET.get("partnumber", None)
+ if not pnum:
+ raise Http404
+
+ 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)
+ 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)
+ return resp
+
@login_required
@needs_mailbox()
def move(request):
@@ -204,7 +216,10 @@ def mark(request, name):
def empty(request, name):
if name == parameters.get_user(request.user, "TRASH_FOLDER"):
get_imapconnector(request).empty(name)
- return ajax_simple_response(dict(status="ok"))
+ content = "%s
" % _("Empty mailbox")
+ return ajax_simple_response(dict(
+ status="ok", listing=content
+ ))
@login_required
@needs_mailbox()
@@ -239,7 +254,7 @@ def newfolder(request, tplname="webmail/folder2.html"):
"withunseen" : False,
"selectonly" : True}
- ctx["folders"] = mbc.getfolders(request.user)
+ ctx["folders"] = mbc.getmboxes(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
@@ -267,7 +282,7 @@ def editfolder(request, tplname="webmail/folder2.html"):
"action_classes" : "submit",
"withunseen" : False,
"selectonly" : True}
- ctx["folders"] = mbc.getfolders(request.user)
+ ctx["folders"] = mbc.getmboxes(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
@@ -388,7 +403,7 @@ def render_mboxes_list(request, imapc):
curmbox = request.session.get("mbox", "INBOX")
return _render_to_string(request, "webmail/folders.html", {
"selected" : curmbox,
- "folders" : imapc.getfolders(request.user),
+ "folders" : imapc.getmboxes(request.user),
"withunseen" : True
})
@@ -544,9 +559,22 @@ def viewmail(request):
@needs_mailbox()
def submailboxes(request):
topmailbox = request.GET.get('topmailbox', '')
- mboxes = get_imapconnector(request).getfolders(request.user, topmailbox)
+ mboxes = get_imapconnector(request).getmboxes(request.user, topmailbox)
return ajax_simple_response(dict(status="ok", mboxes=mboxes))
+@login_required
+@needs_mailbox()
+def check_unseen_messages(request):
+ mboxes = request.GET.get("mboxes", None)
+ if not mboxes:
+ raise WebmailError(_("Invalid request"))
+ mboxes = mboxes.split(",")
+ counters = dict()
+ imapc = get_imapconnector(request)
+ for mb in mboxes:
+ counters[mb] = imapc.unseen_messages(mb)
+ return ajax_simple_response(dict(status="ok", counters=counters))
+
@login_required
@needs_mailbox()
def newindex(request):
@@ -587,10 +615,10 @@ def newindex(request):
if not request.is_ajax():
request.session["lastaction"] = None
imapc = get_imapconnector(request)
+ response["mboxes"] = render_mboxes_list(request, imapc)
imapc.getquota(curmbox)
response["refreshrate"] = \
- int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
- response["mboxes"] = render_mboxes_list(request, imapc)
+ int(parameters.get_user(request.user, "REFRESH_INTERVAL"))
response["quota"] = ImapListing.computequota(imapc)
if response["quota"] < 50:
response["quotalevel"] = "success"
diff --git a/lib/email_listing.py b/lib/email_listing.py
index 0d505bb89..bceb357b5 100644
--- a/lib/email_listing.py
+++ b/lib/email_listing.py
@@ -155,7 +155,7 @@ class EmailListing(object):
def render(self, request, pageid=1, **kwargs):
page = self.paginator.getpage(pageid)
if not page:
- listing = _("This folder contains no message")
+ listing = "%s
" % _("Empty mailbox")
else:
listing = self.fetch(request, page.id_start, page.id_stop)
return dict(listing=listing, navbar=self.render_navbar(page, self.baseurl))
diff --git a/lib/tables.py b/lib/tables.py
index c7425df9d..81ccbfd06 100644
--- a/lib/tables.py
+++ b/lib/tables.py
@@ -120,8 +120,10 @@ class Table(object):
trcpt = 0
for row in rows:
nrow = {"id" : row[self.idkey], "cols" : [], "trcpt" : trcpt}
- if row.has_key("options"):
- nrow["options"] = row["options"]
+ for name in ["style", "options"]:
+ if row.has_key(name):
+ nrow[name] = row[name]
+
for c in self.columns:
newcol = {"name" : c.name}
for key in ["width", "align", "cssclass"]:
diff --git a/static/js/poller.js b/static/js/poller.js
new file mode 100644
index 000000000..b715ca333
--- /dev/null
+++ b/static/js/poller.js
@@ -0,0 +1,62 @@
+/*
+ * Simple poller plugin.
+ *
+ * Sends a request at a given frequency.
+ */
+var Poller = function(url, options) {
+ this.initialize(url, options);
+};
+
+Poller.prototype = {
+ constructor: Poller,
+
+ defaults: {
+ interval: 2000,
+ success_cb: null,
+ error_cb: null
+ },
+
+ initialize: function(url, options) {
+ this.url = url;
+ this.options = $.extend({}, this.defaults, options);
+ this.running_request = false;
+ this.reset();
+ },
+
+ reset: function() {
+ setTimeout($.proxy(this.send_request, this), this.options.interval);
+ },
+
+ send_request: function() {
+ var poller = this;
+ var args = "";
+
+ if (this.options.args != undefined) {
+ args = "?";
+ if (typeof this.options.args === "function") {
+ args += this.options.args();
+ } else {
+ args += this.options.args;
+ }
+ }
+ this.running_request = true;
+ $.ajax({
+ url: this.url + args,
+ dataType: 'json',
+ success: function(data) {
+ poller.running_request = false;
+ if (data.status == "ok") {
+ if (poller.options.success_cb) {
+ poller.options.success_cb(data);
+ }
+ poller.reset();
+ } else {
+ $("body").notify("error", data.respmsg);
+ if (poller.options.error_cb) {
+ poller.options.error_cb(data);
+ }
+ }
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/templates/nlayout.html b/templates/nlayout.html
index 402792db1..be4aecb3e 100644
--- a/templates/nlayout.html
+++ b/templates/nlayout.html
@@ -16,6 +16,7 @@
+
{% url django.views.i18n.javascript_catalog as catalog_url %}