mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
@@ -1,5 +1,8 @@
|
||||
{% extends "common/generic_modal_form.html" %}
|
||||
{% load i18n form_tags sfilters_tags %}
|
||||
|
||||
{% block modal_css_classes %}modal-lg{% endblock %}
|
||||
|
||||
{% block formbody %}
|
||||
{% if oldname %}<input type="hidden" name="oldname" value="{{ oldname }}" />{% endif %}
|
||||
{% render_field form.name %}
|
||||
|
||||
@@ -6,7 +6,6 @@ from .imapemail import ImapEmail, ReplyModifier, ForwardModifier
|
||||
from .imaputils import (
|
||||
BodyStructure, IMAPconnector, get_imapconnector, separate_mailbox
|
||||
)
|
||||
from .listing import ImapListing
|
||||
from .signature import EmailSignature
|
||||
from .utils import decode_payload, WebmailNavigationParameters
|
||||
from .sendmail import send_mail
|
||||
@@ -20,7 +19,6 @@ __all__ = [
|
||||
'ForwardModifier',
|
||||
'IMAPconnector',
|
||||
'ImapEmail',
|
||||
'ImapListing',
|
||||
'ReplyModifier',
|
||||
'WebmailNavigationParameters',
|
||||
'clean_attachments',
|
||||
|
||||
@@ -141,6 +141,9 @@ class BodyStructure(object):
|
||||
|
||||
|
||||
class IMAPconnector(object):
|
||||
|
||||
"""The IMAPv4 connector."""
|
||||
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
list_base_pattern = r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" "?(?P<name>[^"]*)"?'
|
||||
@@ -300,6 +303,32 @@ class IMAPconnector(object):
|
||||
del self.m
|
||||
self.m = None
|
||||
|
||||
def parse_search_parameters(self, criterion, pattern):
|
||||
"""Parse search information and apply them."""
|
||||
|
||||
def or_criterion(old, c):
|
||||
if old == "":
|
||||
return c
|
||||
return "OR (%s) (%s)" % (old, c)
|
||||
|
||||
if criterion == u"both":
|
||||
criterion = u"from_addr, subject"
|
||||
criterions = ""
|
||||
if type(pattern) is unicode:
|
||||
pattern = pattern.encode("utf-8")
|
||||
if type(criterion) is unicode:
|
||||
criterion = criterion.encode("utf-8")
|
||||
for c in criterion.split(','):
|
||||
if c == "from_addr":
|
||||
key = "FROM"
|
||||
elif c == "subject":
|
||||
key = "SUBJECT"
|
||||
else:
|
||||
continue
|
||||
criterions = \
|
||||
or_criterion(criterions, '(%s "%s")' % (key, pattern))
|
||||
self.criterions = [criterions]
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
"""An enhanced version of messages_count
|
||||
|
||||
@@ -456,7 +485,9 @@ class IMAPconnector(object):
|
||||
from operator import itemgetter
|
||||
mailboxes += sorted(newmboxes, key=itemgetter("name"))
|
||||
|
||||
def getmboxes(self, user, topmailbox='', until_mailbox=None, unseen_messages=True):
|
||||
def getmboxes(
|
||||
self, user, topmailbox='', until_mailbox=None,
|
||||
unseen_messages=True):
|
||||
"""Returns a list of mailboxes for a particular user
|
||||
|
||||
By default, only the first level of mailboxes under
|
||||
@@ -588,23 +619,33 @@ class IMAPconnector(object):
|
||||
return True
|
||||
|
||||
def getquota(self, mailbox):
|
||||
"""Retrieve quota information from the server.
|
||||
|
||||
We also compute the current usage.
|
||||
"""
|
||||
if not "QUOTA" in self.capabilities:
|
||||
self.quota_limit = self.quota_actual = None
|
||||
self.quota_limit = self.quota_current = None
|
||||
return
|
||||
|
||||
data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
|
||||
responses=["QUOTAROOT", "QUOTA"])
|
||||
if data is None:
|
||||
self.quota_limit = self.quota_actual = None
|
||||
self.quota_limit = self.quota_current = None
|
||||
return
|
||||
|
||||
quotadef = data[1][0]
|
||||
m = re.search("\(STORAGE (\d+) (\d+)\)", quotadef)
|
||||
m = re.search(r"\(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))
|
||||
self.quota_current = int(m.group(1))
|
||||
try:
|
||||
self.quota_usage = (
|
||||
int(float(self.quota_current) / float(self.quota_limit) * 100)
|
||||
)
|
||||
except TypeError:
|
||||
self.quota_usage = -1
|
||||
|
||||
def fetchpart(self, uid, mbox, partnum):
|
||||
"""Retrieve a specific message part
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from modoboa.lib import parameters, tables
|
||||
from modoboa.lib.email_listing import EmailListing
|
||||
from . import IMAPconnector, imapheader
|
||||
|
||||
|
||||
class WMtable(tables.Table):
|
||||
|
||||
"""Webmail listing table."""
|
||||
|
||||
tableid = "emails"
|
||||
styles = "table-hover"
|
||||
idkey = "imapid"
|
||||
drag = tables.ImgColumn(
|
||||
"drag", cssclass="draggable left",
|
||||
defvalue="%spics/grippy.png" % settings.STATIC_URL,
|
||||
header="<input type='checkbox' name='toggleselect' id='toggleselect' />"
|
||||
)
|
||||
selection = tables.SelectionColumn(
|
||||
"selection", safe=True, header=None, sortable=False
|
||||
)
|
||||
flags = tables.ImgColumn("flags")
|
||||
withatts = tables.ImgColumn("withatts")
|
||||
subject = tables.Column(
|
||||
"subject", label=ugettext_lazy("Subject"), limit=60,
|
||||
cssclass="openable"
|
||||
)
|
||||
from_ = tables.Column(
|
||||
"from", label=ugettext_lazy("From"), limit=30,
|
||||
cssclass="openable"
|
||||
)
|
||||
date = tables.Column(
|
||||
"date", label=ugettext_lazy("Date"), cssclass="openable"
|
||||
)
|
||||
|
||||
cols_order = [
|
||||
"drag", "selection", "flags", "from_", "subject", "withatts", "date"
|
||||
]
|
||||
|
||||
def parse(self, header, value):
|
||||
if value is None:
|
||||
return ""
|
||||
try:
|
||||
value = getattr(imapheader, "parse_%s" % header)(value)
|
||||
except AttributeError:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
class ImapListing(EmailListing):
|
||||
tpl = "webmail/index.html"
|
||||
tbltype = WMtable
|
||||
deflocation = "INBOX/"
|
||||
defcallback = "wm_updatelisting"
|
||||
reset_wm_url = False
|
||||
|
||||
def __init__(self, user, password, **kwargs):
|
||||
self.user = user
|
||||
self.mbc = IMAPconnector(user=user.username, password=password)
|
||||
if "pattern" in kwargs and kwargs["pattern"]:
|
||||
self.parse_search_parameters(
|
||||
kwargs["criteria"], kwargs["pattern"]
|
||||
)
|
||||
else:
|
||||
self.mbc.criterions = []
|
||||
|
||||
super(ImapListing, self).__init__(**kwargs)
|
||||
self.extravars["refreshrate"] = \
|
||||
int(parameters.get_user(user, "REFRESH_INTERVAL")) * 1000
|
||||
|
||||
def parse_search_parameters(self, criterion, pattern):
|
||||
|
||||
def or_criterion(old, c):
|
||||
if old == "":
|
||||
return c
|
||||
return "OR (%s) (%s)" % (old, c)
|
||||
|
||||
if criterion == u"both":
|
||||
criterion = u"from_addr, subject"
|
||||
criterions = ""
|
||||
if type(pattern) is unicode:
|
||||
pattern = pattern.encode("utf-8")
|
||||
if type(criterion) is unicode:
|
||||
criterion = criterion.encode("utf-8")
|
||||
for c in criterion.split(','):
|
||||
if c == "from_addr":
|
||||
key = "FROM"
|
||||
elif c == "subject":
|
||||
key = "SUBJECT"
|
||||
else:
|
||||
continue
|
||||
criterions = \
|
||||
or_criterion(criterions, '(%s "%s")' % (key, pattern))
|
||||
self.mbc.criterions = [criterions]
|
||||
|
||||
@staticmethod
|
||||
def computequota(mbc):
|
||||
try:
|
||||
return int(float(mbc.quota_actual) \
|
||||
/ float(mbc.quota_limit) * 100)
|
||||
except (AttributeError, TypeError):
|
||||
return -1
|
||||
|
||||
def getquota(self):
|
||||
return ImapListing.computequota(self.mbc)
|
||||
@@ -46,9 +46,6 @@
|
||||
}
|
||||
|
||||
/* GENERAL */
|
||||
body, html {
|
||||
/*overflow: hidden;*/
|
||||
}
|
||||
|
||||
#rightcol {
|
||||
position: fixed;
|
||||
@@ -90,10 +87,6 @@ body, html {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#mboxes_container {
|
||||
/*overflow: auto;*/
|
||||
}
|
||||
|
||||
.nav-sidebar li > a {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
@@ -273,8 +266,9 @@ list {
|
||||
}
|
||||
|
||||
/*
|
||||
* Folder form
|
||||
* Folders form
|
||||
*/
|
||||
|
||||
.modal-body {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
@@ -284,6 +278,22 @@ list {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#folders2 div.clickbox {
|
||||
left: 0;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#folders2 li li {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#folders2 .active > a,
|
||||
#folders2 .active > a:hover,
|
||||
#folders2 .active > a:focus {
|
||||
color: #fff;
|
||||
background-color: #428bca;
|
||||
}
|
||||
|
||||
/*
|
||||
* Table
|
||||
*/
|
||||
|
||||
@@ -186,7 +186,7 @@ Webmail.prototype = {
|
||||
return $("#emails").height() - $element.height();
|
||||
},
|
||||
get_args: function() {
|
||||
var args = that.navparams;
|
||||
var args = $.extend({}, that.navparams);
|
||||
args.scroll = true;
|
||||
return args;
|
||||
},
|
||||
@@ -281,6 +281,8 @@ Webmail.prototype = {
|
||||
|
||||
/**
|
||||
* Simple helper to retrieve the currently selected mailbox.
|
||||
*
|
||||
* @this Webmail
|
||||
*/
|
||||
get_current_mailbox: function() {
|
||||
return this.navobject.getparam("mbox", "INBOX");
|
||||
@@ -289,9 +291,11 @@ Webmail.prototype = {
|
||||
/**
|
||||
* Keep track of interesting navigation parameters in order to
|
||||
* restore them later.
|
||||
*
|
||||
* @this Webmail
|
||||
*/
|
||||
store_nav_params: function() {
|
||||
var params = new Array("page", "order", "pattern", "criteria");
|
||||
var params = new Array("order", "pattern", "criteria");
|
||||
|
||||
this.navparams = {};
|
||||
for (var idx in params) {
|
||||
@@ -299,9 +303,11 @@ Webmail.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* Restore navigation parameters previously stored via
|
||||
* store_nav_params.
|
||||
*
|
||||
* @this Webmail
|
||||
*/
|
||||
restore_nav_params: function() {
|
||||
if (this.navparams === undefined) {
|
||||
@@ -360,12 +366,13 @@ Webmail.prototype = {
|
||||
}
|
||||
if (response.menu !== undefined) {
|
||||
$("#menubar").html(response.menu);
|
||||
$("#searchfield").searchbar({navobj: this.navobject});
|
||||
$("#searchfield").searchbar({
|
||||
navobj: this.navobject,
|
||||
pattern_changed: function(navobj) {
|
||||
navobj.setparam("reset_page", "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
/*if (response.navbar) {
|
||||
$("#pagination").html(response.navbar);
|
||||
$("#pagination-responsive").html(response.navbar);
|
||||
}*/
|
||||
|
||||
$("#listing").html(response.listing);
|
||||
|
||||
@@ -525,7 +532,12 @@ Webmail.prototype = {
|
||||
* @param {Object} parent - parent node where mailboxes will be appended
|
||||
* @param {boolean} async - if true, the ajax call will be asynchronous
|
||||
*/
|
||||
get_mailboxes: function(parent, async) {
|
||||
get_mailboxes: function(parent, async, with_unseen) {
|
||||
var args = "topmailbox=" + parent.attr("name");
|
||||
|
||||
if (with_unseen) {
|
||||
args += "&unseen=true";
|
||||
}
|
||||
if (async === undefined) {
|
||||
async = true;
|
||||
}
|
||||
@@ -533,7 +545,7 @@ Webmail.prototype = {
|
||||
url: this.options.submboxes_url,
|
||||
dataType: 'json',
|
||||
async: async,
|
||||
data: "topmailbox=" + parent.attr("name")
|
||||
data: args
|
||||
}).done($.proxy(function(data) {
|
||||
this.inject_mailboxes(parent, data);
|
||||
}, this));
|
||||
@@ -575,7 +587,9 @@ Webmail.prototype = {
|
||||
var $ul = $parent.find('ul[name="' + $parent.attr("name") + '"]');
|
||||
|
||||
if (!$ul.length) {
|
||||
this.get_mailboxes($parent);
|
||||
var $link = $div.next();
|
||||
var with_unseen = ($link.attr("name") == "loadfolder") ? true : false;
|
||||
this.get_mailboxes($parent, true, with_unseen);
|
||||
return;
|
||||
}
|
||||
this.toggle_mbox_state($div, $ul);
|
||||
@@ -642,6 +656,12 @@ Webmail.prototype = {
|
||||
$('li[name="' + mailbox + '"]').addClass("active");
|
||||
},
|
||||
|
||||
/**
|
||||
* Click handler : select the parent mailbox of the mailbox being
|
||||
* created/modified.
|
||||
*
|
||||
* @param {Object} e - event object
|
||||
*/
|
||||
select_parent_mailbox: function(e) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
@@ -808,6 +828,12 @@ Webmail.prototype = {
|
||||
}, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Click event: empty Trash folder.
|
||||
*
|
||||
* @this Webmail
|
||||
* @param {Object} e - event object
|
||||
*/
|
||||
empty: function(e) {
|
||||
e.preventDefault();
|
||||
var $link = $(e.target);
|
||||
@@ -894,7 +920,7 @@ Webmail.prototype = {
|
||||
}
|
||||
this.navobject.reset().setparams({
|
||||
action: "listmailbox",
|
||||
mbox: obj.attr("href"),
|
||||
mbox: obj.attr("href")
|
||||
});
|
||||
if (reset_page) {
|
||||
this.navobject.setparam("reset_page", reset_page);
|
||||
@@ -1061,16 +1087,19 @@ Webmail.prototype = {
|
||||
this.resize_compose_body();
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* Callback of the 'viewmail' action
|
||||
*
|
||||
* @this Webmail
|
||||
* @param {Object} resp - AJAX call response (JSON)
|
||||
*/
|
||||
viewmail_callback: function(resp) {
|
||||
this.page_update(resp);
|
||||
$("#listing").css("overflow", "hidden");
|
||||
// $("a[name=back]").click($.proxy(function(e) {
|
||||
// e.preventDefault();
|
||||
// this.go_back_to_listing();
|
||||
// }, this));
|
||||
$("a[name=back]").click($.proxy(function(e) {
|
||||
e.preventDefault();
|
||||
this.go_back_to_listing();
|
||||
}, this));
|
||||
},
|
||||
|
||||
mark_callback: function(data) {
|
||||
|
||||
@@ -19,6 +19,7 @@ from django.views.decorators.gzip import gzip_page
|
||||
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.exceptions import ModoboaException, BadRequest
|
||||
from modoboa.lib.paginator import Paginator
|
||||
from modoboa.lib.webutils import (
|
||||
_render_to_string, ajax_response, render_to_json_response
|
||||
)
|
||||
@@ -29,7 +30,7 @@ from .forms import (
|
||||
)
|
||||
from .lib import (
|
||||
decode_payload, AttachmentUploadHandler,
|
||||
save_attachment, ImapListing, EmailSignature,
|
||||
save_attachment, EmailSignature,
|
||||
clean_attachments, set_compose_session, send_mail,
|
||||
ImapEmail, WebmailNavigationParameters, ReplyModifier, ForwardModifier,
|
||||
get_imapconnector, IMAPconnector, separate_mailbox
|
||||
@@ -337,7 +338,8 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
|
||||
parameter. (user preferences)
|
||||
|
||||
:param request: a ``Request`` object
|
||||
:param defmailbox: the default mailbox (when not present inside request arguments)
|
||||
:param defmailbox: the default mailbox (when not present inside
|
||||
request arguments)
|
||||
:return: a dictionnary
|
||||
"""
|
||||
navparams = WebmailNavigationParameters(request, defmailbox)
|
||||
@@ -345,30 +347,34 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
|
||||
if update_session:
|
||||
navparams.store()
|
||||
mbox = navparams.get('mbox')
|
||||
lst = ImapListing(
|
||||
request.user, request.session["password"],
|
||||
baseurl="?action=listmailbox&mbox=%s&" % mbox,
|
||||
folder=mbox,
|
||||
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
|
||||
**request.session["webmail_navparams"]
|
||||
)
|
||||
page_id = int(navparams["page"])
|
||||
page = lst.paginator.getpage(page_id)
|
||||
mbc = get_imapconnector(request)
|
||||
mbc.parse_search_parameters(
|
||||
navparams.get("criteria"), navparams.get("pattern"))
|
||||
|
||||
paginator = Paginator(
|
||||
mbc.messages_count(folder=mbox, order=navparams.get("order")),
|
||||
int(parameters.get_user(request.user, "MESSAGES_PER_PAGE"))
|
||||
)
|
||||
page = paginator.getpage(page_id)
|
||||
|
||||
if page is not None:
|
||||
email_list = lst.mbc.fetch(
|
||||
page.id_start, page.id_stop, mbox,
|
||||
nbelems=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE"))
|
||||
email_list = mbc.fetch(
|
||||
page.id_start, page.id_stop, mbox, nbelems=page.items
|
||||
)
|
||||
content = _render_to_string(request, "webmail/email_list.html", {
|
||||
"email_list": email_list,
|
||||
"page": page_id,
|
||||
"with_top_div": request.GET.get("scroll", "false") == "false"
|
||||
})
|
||||
length = len(content)
|
||||
else:
|
||||
content = ""
|
||||
content = "<div class='alert alert-info'>{}</div>".format(
|
||||
_("Empty mailbox")
|
||||
)
|
||||
length = 0
|
||||
navparams["page"] = previous_page_id
|
||||
return {"listing": content, "length": len(content), "page": page_id}
|
||||
#return lst.render(request, navparams.get('page'))
|
||||
return {"listing": content, "length": length, "page": page_id}
|
||||
|
||||
|
||||
def render_compose(request, form, posturl, email=None, insert_signature=False):
|
||||
@@ -513,8 +519,11 @@ def viewmail(request):
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def submailboxes(request):
|
||||
"""Retrieve the sub mailboxes of a mailbox."""
|
||||
topmailbox = request.GET.get('topmailbox', '')
|
||||
mboxes = get_imapconnector(request).getmboxes(request.user, topmailbox)
|
||||
with_unseen = request.GET.get('unseen', None)
|
||||
mboxes = get_imapconnector(request).getmboxes(
|
||||
request.user, topmailbox, unseen_messages=with_unseen == 'true')
|
||||
return render_to_json_response(mboxes)
|
||||
|
||||
|
||||
@@ -575,7 +584,7 @@ def index(request):
|
||||
imapc.getquota(curmbox)
|
||||
response["refreshrate"] = \
|
||||
int(parameters.get_user(request.user, "REFRESH_INTERVAL"))
|
||||
response["quota"] = ImapListing.computequota(imapc)
|
||||
response["quota"] = imapc.quota_usage
|
||||
trash = parameters.get_user(request.user, "TRASH_FOLDER")
|
||||
response["trash"] = trash
|
||||
response["ro_mboxes"] = [
|
||||
|
||||
@@ -4,72 +4,7 @@ from django.template.loader import render_to_string
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
attached_map = {}
|
||||
|
||||
|
||||
class Page(object):
|
||||
def __init__(self, pageid, id_start, id_stop, items,
|
||||
items_per_page, has_previous, has_next,
|
||||
baseurl=None):
|
||||
self.number = pageid
|
||||
self.id_start = id_start
|
||||
self.id_stop = id_stop
|
||||
self.items = items
|
||||
self.items_per_page = items_per_page
|
||||
self.has_previous = has_previous
|
||||
self.has_next = has_next
|
||||
self.baseurl = baseurl
|
||||
|
||||
def previous_page_number(self):
|
||||
if not self.has_previous:
|
||||
return False
|
||||
return self.number - 1
|
||||
|
||||
def next_page_number(self):
|
||||
if not self.has_next:
|
||||
return False
|
||||
return self.number + 1
|
||||
|
||||
def last_page(self):
|
||||
lid = self.items / self.items_per_page
|
||||
if not lid:
|
||||
return 1
|
||||
if self.items % self.items_per_page:
|
||||
lid += 1
|
||||
return lid
|
||||
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, total, elems_per_page):
|
||||
self.total = total
|
||||
self.elems_per_page = elems_per_page
|
||||
self.num_pages = total / elems_per_page
|
||||
if total % elems_per_page:
|
||||
self.num_pages += 1
|
||||
|
||||
def _indexes(self, page):
|
||||
id_start = self.elems_per_page * page + 1
|
||||
id_stop = id_start + self.elems_per_page - 1
|
||||
return (id_start, id_stop)
|
||||
|
||||
def getpage(self, page):
|
||||
if page < 1:
|
||||
return None
|
||||
id_start, id_stop = self._indexes(page - 1)
|
||||
has_previous = has_next = False
|
||||
if id_start <= self.total:
|
||||
if page > 1:
|
||||
has_previous = True
|
||||
if id_stop < self.total:
|
||||
has_next = True
|
||||
else:
|
||||
id_stop = self.total
|
||||
else:
|
||||
return None
|
||||
p = Page(page, id_start, id_stop, self.total,
|
||||
self.elems_per_page, has_previous, has_next)
|
||||
p.paginator = self
|
||||
return p
|
||||
from modoboa.lib.paginator import Paginator
|
||||
|
||||
|
||||
class EmailListing(object):
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Custom pagination tools.
|
||||
"""
|
||||
|
||||
|
||||
class Page(object):
|
||||
|
||||
"""Class to represent a page."""
|
||||
|
||||
def __init__(self, paginator, pageid, id_start, id_stop, baseurl=None):
|
||||
"""Constructor."""
|
||||
self.paginator = paginator
|
||||
self.number = pageid
|
||||
self.id_start = id_start
|
||||
self.id_stop = id_stop
|
||||
self.baseurl = baseurl
|
||||
|
||||
self._has_previous = None
|
||||
self._has_next = None
|
||||
|
||||
@property
|
||||
def items_per_page(self):
|
||||
"""The number of items in a page. (shortcut)"""
|
||||
return self.paginator.elems_per_page
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
"""The total number of items. (shortcut)"""
|
||||
return self.paginator.total
|
||||
|
||||
@property
|
||||
def has_previous(self):
|
||||
"""Tell if a previous page is available or not."""
|
||||
if self._has_previous is None:
|
||||
self._has_previous = True if self.number > 1 else False
|
||||
return self._has_previous
|
||||
|
||||
@property
|
||||
def previous_page_number(self):
|
||||
"""Return the index of the previous page."""
|
||||
if not self.has_previous:
|
||||
return False
|
||||
return self.number - 1
|
||||
|
||||
@property
|
||||
def has_next(self):
|
||||
"""Tell if a next page is available or not."""
|
||||
if self._has_next is None:
|
||||
self._has_next = (
|
||||
True if self.id_stop < self.paginator.total else False
|
||||
)
|
||||
return self._has_next
|
||||
|
||||
@property
|
||||
def next_page_number(self):
|
||||
"""Return the index of the next page."""
|
||||
if not self.has_next:
|
||||
return False
|
||||
return self.number + 1
|
||||
|
||||
@property
|
||||
def last_page(self):
|
||||
"""Retrieve the id of the last page."""
|
||||
lid = self.items / self.items_per_page
|
||||
if not lid:
|
||||
return 1
|
||||
if self.items % self.items_per_page:
|
||||
lid += 1
|
||||
return lid
|
||||
|
||||
|
||||
class Paginator(object):
|
||||
|
||||
"""Pagination class."""
|
||||
|
||||
def __init__(self, total, elems_per_page):
|
||||
self.total = total
|
||||
self.elems_per_page = elems_per_page
|
||||
self.num_pages = total / elems_per_page
|
||||
if total % elems_per_page:
|
||||
self.num_pages += 1
|
||||
|
||||
def _indexes(self, page):
|
||||
"""Compute start and stop indexes."""
|
||||
id_start = self.elems_per_page * page + 1
|
||||
id_stop = id_start + self.elems_per_page - 1
|
||||
return (id_start, id_stop)
|
||||
|
||||
def getpage(self, page_id):
|
||||
"""Retrieve a specific page."""
|
||||
if page_id < 1:
|
||||
return None
|
||||
id_start, id_stop = self._indexes(page_id - 1)
|
||||
if id_start <= self.total:
|
||||
if id_stop >= self.total:
|
||||
id_stop = self.total
|
||||
else:
|
||||
return None
|
||||
return Page(self, page_id, id_start, id_stop)
|
||||
@@ -20,6 +20,11 @@ function get_static_url(value) {
|
||||
return static_url + value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a modal box and load its content using an AJAX request.
|
||||
*
|
||||
* @param {Object} e - event object
|
||||
*/
|
||||
function modalbox(e, css, defhref, defcb, defclosecb) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
@@ -39,7 +44,8 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
|
||||
url: href
|
||||
}).done(function(data) {
|
||||
var $div = $('<div />', {
|
||||
id: "modalbox", 'class': "modal fade", html: data
|
||||
id: "modalbox", 'class': "modal fade", role: "modal",
|
||||
html: data, 'aria-hidden': true
|
||||
});
|
||||
|
||||
$div.modal({show: false});
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Creates an instance of InfiniteScroll.
|
||||
*
|
||||
* @constructor
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
* @param {object} element
|
||||
* @param {dictionary} options - instance options
|
||||
*/
|
||||
@@ -33,7 +33,7 @@
|
||||
/**
|
||||
* Scroll event handler.
|
||||
*
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
*/
|
||||
on_scroll: function() {
|
||||
var bottom = this.$options.calculate_bottom(this.$element);
|
||||
@@ -49,7 +49,7 @@
|
||||
* Load the previous page from the server.
|
||||
*
|
||||
* @private
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
*/
|
||||
load_previous_page: function() {
|
||||
var $this = this;
|
||||
@@ -74,7 +74,7 @@
|
||||
/**
|
||||
* Load the next page from the server.
|
||||
*
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
*/
|
||||
load_next_page: function() {
|
||||
var $this = this;
|
||||
@@ -95,7 +95,7 @@
|
||||
* Load a page's content.
|
||||
*
|
||||
* @private
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
* @param {string} direction - direction of the scroll (up or down)
|
||||
*/
|
||||
load_page: function(direction) {
|
||||
@@ -117,7 +117,6 @@
|
||||
}
|
||||
$this.executing = false;
|
||||
$this.loaded_pages.push($this.current_page);
|
||||
console.log("page", $this.current_page, "loaded successfuly");
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -125,18 +124,19 @@
|
||||
/**
|
||||
* Reset the loaded pages cache.
|
||||
*
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
* @param {Array} current_page - the new current_page
|
||||
*/
|
||||
reset_loaded_pages: function(current_page) {
|
||||
this.current_page = current_page;
|
||||
this.loaded_pages = [this.current_page];
|
||||
this.end_of_results = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Suspend the "infinite scroll" mode.
|
||||
*
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
*/
|
||||
pause: function() {
|
||||
this.$element.unbind("scroll");
|
||||
@@ -145,7 +145,7 @@
|
||||
/**
|
||||
* Resume the "infinite scroll" mode.
|
||||
*
|
||||
* @this {InfiniteScroll}
|
||||
* @this InfiniteScroll
|
||||
*/
|
||||
resume: function() {
|
||||
this.$element.scroll($.proxy(this.on_scroll, this));
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Return an instance of SearchBar.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} element -
|
||||
* @param {Object} options - instance options
|
||||
*/
|
||||
var SearchBar = function(element, options) {
|
||||
this.initialize(element, options);
|
||||
};
|
||||
@@ -16,24 +24,30 @@
|
||||
});
|
||||
this.$element.blur(function(e) {
|
||||
var $this = $(this);
|
||||
if ($this.val() == "") {
|
||||
if ($this.val() === "") {
|
||||
$this.val(gettext("Search..."));
|
||||
}
|
||||
});
|
||||
if (this.options.navobj.params.pattern != undefined) {
|
||||
if (this.options.navobj.params.pattern !== undefined) {
|
||||
this.$element.val(this.options.navobj.getparam("pattern"));
|
||||
}
|
||||
if (this.options.navobj.params.criteria != undefined) {
|
||||
if (this.options.navobj.params.criteria !== undefined) {
|
||||
$("#crit_" + this.options.navobj.params.criteria).attr("checked", "checked");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Change event: apply search criterion.
|
||||
*
|
||||
* @this SearchBar
|
||||
* @param {Object} e - event object
|
||||
*/
|
||||
pattern_change: function(e) {
|
||||
var $input = $(e.target);
|
||||
var criteria = $("input[name=scriteria]:checked").val();
|
||||
var pattern = $input.val();
|
||||
|
||||
if (pattern != "") {
|
||||
if (pattern !== "") {
|
||||
this.options.navobj.setparams({
|
||||
pattern: pattern,
|
||||
criteria: criteria
|
||||
@@ -43,6 +57,9 @@
|
||||
this.options.navobj.delparam("criteria");
|
||||
this.options.navobj.delparam("page");
|
||||
}
|
||||
if (this.options.pattern_changed) {
|
||||
this.options.pattern_changed(this.options.navobj);
|
||||
}
|
||||
this.options.navobj.update();
|
||||
}
|
||||
};
|
||||
@@ -63,6 +80,7 @@
|
||||
};
|
||||
|
||||
$.fn.searchbar.defaults = {
|
||||
navobj: null
|
||||
navobj: null,
|
||||
pattern_changed: null
|
||||
};
|
||||
})(jQuery);
|
||||
})(jQuery);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog {% block modal_css_classes %}{% endblock %}">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% trans "Close" %}</span></button>
|
||||
|
||||
Reference in New Issue
Block a user