mediagoblin/mediagoblin/tools/pagination.py
Christopher Allan Webber 1fef79f4f8 Fix pagination for certain request.GET data
This didn't work at all nicely with MultiDict objects in various
circumstances and could possibly break pagination.  This fix handles
that!

This commit sponsored by Alessandro Francolini.  Thank you!
2013-09-13 10:16:07 -05:00

118 lines
3.8 KiB
Python

# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib
import copy
from math import ceil, floor
from itertools import izip, count
from werkzeug.datastructures import MultiDict
PAGINATION_DEFAULT_PER_PAGE = 30
class Pagination(object):
"""
Pagination class for database queries.
Initialization through __init__(self, cursor, page=1, per_page=2),
get actual data slice through __call__().
"""
def __init__(self, page, cursor, per_page=PAGINATION_DEFAULT_PER_PAGE,
jump_to_id=False):
"""
Initializes Pagination
Args:
- page: requested page
- per_page: number of objects per page
- cursor: db cursor
- jump_to_id: object id, sets the page to the page containing the
object with id == jump_to_id.
"""
self.page = page
self.per_page = per_page
self.cursor = cursor
self.total_count = self.cursor.count()
self.active_id = None
if jump_to_id:
cursor = copy.copy(self.cursor)
for (doc, increment) in izip(cursor, count(0)):
if doc.id == jump_to_id:
self.page = 1 + int(floor(increment / self.per_page))
self.active_id = jump_to_id
break
def __call__(self):
"""
Returns slice of objects for the requested page
"""
# TODO, return None for out of index so templates can
# distinguish between empty galleries and out-of-bound pages???
return self.cursor.slice(
(self.page - 1) * self.per_page,
self.page * self.per_page)
@property
def pages(self):
return int(ceil(self.total_count / float(self.per_page)))
@property
def has_prev(self):
return self.page > 1
@property
def has_next(self):
return self.page < self.pages
def iter_pages(self, left_edge=2, left_current=2,
right_current=5, right_edge=2):
last = 0
for num in xrange(1, self.pages + 1):
if num <= left_edge or \
(num > self.page - left_current - 1 and \
num < self.page + right_current) or \
num > self.pages - right_edge:
if last + 1 != num:
yield None
yield num
last = num
def get_page_url_explicit(self, base_url, get_params, page_no):
"""
Get a page url by adding a page= parameter to the base url
"""
if isinstance(get_params, MultiDict):
new_get_params = get_params.to_dict()
else:
new_get_params = dict(get_params) or {}
new_get_params['page'] = page_no
return "%s?%s" % (
base_url, urllib.urlencode(new_get_params))
def get_page_url(self, request, page_no):
"""
Get a new page url based of the request, and the new page number.
This is a nice wrapper around get_page_url_explicit()
"""
return self.get_page_url_explicit(
request.full_path, request.GET, page_no)