diff --git a/kallithea/controllers/admin/admin.py b/kallithea/controllers/admin/admin.py --- a/kallithea/controllers/admin/admin.py +++ b/kallithea/controllers/admin/admin.py @@ -41,7 +41,7 @@ from kallithea.lib.auth import LoginRequ from kallithea.lib.base import BaseController, render from kallithea.lib.utils2 import safe_int, remove_prefix, remove_suffix from kallithea.lib.indexers import JOURNAL_SCHEMA -from kallithea.lib.helpers import Page +from kallithea.lib.page import Page log = logging.getLogger(__name__) diff --git a/kallithea/controllers/admin/gists.py b/kallithea/controllers/admin/gists.py --- a/kallithea/controllers/admin/gists.py +++ b/kallithea/controllers/admin/gists.py @@ -44,7 +44,7 @@ from kallithea.lib.base import BaseContr from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.utils import jsonify from kallithea.lib.utils2 import safe_int, safe_unicode, time_to_datetime -from kallithea.lib.helpers import Page +from kallithea.lib.page import Page from sqlalchemy.sql.expression import or_ from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError diff --git a/kallithea/controllers/admin/notifications.py b/kallithea/controllers/admin/notifications.py --- a/kallithea/controllers/admin/notifications.py +++ b/kallithea/controllers/admin/notifications.py @@ -38,7 +38,7 @@ from kallithea.model.meta import Session from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.base import BaseController, render from kallithea.lib import helpers as h -from kallithea.lib.helpers import Page +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_int diff --git a/kallithea/controllers/changelog.py b/kallithea/controllers/changelog.py --- a/kallithea/controllers/changelog.py +++ b/kallithea/controllers/changelog.py @@ -36,9 +36,9 @@ import kallithea.lib.helpers as h from kallithea.config.routing import url from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.helpers import RepoPage from kallithea.lib.compat import json from kallithea.lib.graphmod import graph_data +from kallithea.lib.page import RepoPage from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \ ChangesetError, NodeDoesNotExistError, EmptyRepositoryError from kallithea.lib.utils2 import safe_int, safe_str diff --git a/kallithea/controllers/followers.py b/kallithea/controllers/followers.py --- a/kallithea/controllers/followers.py +++ b/kallithea/controllers/followers.py @@ -29,11 +29,11 @@ import logging from pylons import tmpl_context as c, request -from kallithea.lib.helpers import Page from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.page import Page +from kallithea.lib.utils2 import safe_int from kallithea.model.db import UserFollowing -from kallithea.lib.utils2 import safe_int log = logging.getLogger(__name__) diff --git a/kallithea/controllers/forks.py b/kallithea/controllers/forks.py --- a/kallithea/controllers/forks.py +++ b/kallithea/controllers/forks.py @@ -37,15 +37,15 @@ from webob.exc import HTTPFound import kallithea.lib.helpers as h from kallithea.config.routing import url -from kallithea.lib.helpers import Page from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator, HasPermissionAny from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.page import Page +from kallithea.lib.utils2 import safe_int from kallithea.model.db import Repository, UserFollowing, User, Ui from kallithea.model.repo import RepoModel from kallithea.model.forms import RepoForkForm from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices -from kallithea.lib.utils2 import safe_int log = logging.getLogger(__name__) diff --git a/kallithea/controllers/journal.py b/kallithea/controllers/journal.py --- a/kallithea/controllers/journal.py +++ b/kallithea/controllers/journal.py @@ -46,11 +46,11 @@ from kallithea.model.db import UserLog, from kallithea.model.meta import Session from kallithea.model.repo import RepoModel import kallithea.lib.helpers as h -from kallithea.lib.helpers import Page from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.base import BaseController, render +from kallithea.lib.compat import json +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_int, AttributeDict -from kallithea.lib.compat import json log = logging.getLogger(__name__) diff --git a/kallithea/controllers/pullrequests.py b/kallithea/controllers/pullrequests.py --- a/kallithea/controllers/pullrequests.py +++ b/kallithea/controllers/pullrequests.py @@ -35,19 +35,19 @@ from pylons.i18n.translation import _ from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest from kallithea.config.routing import url -from kallithea.lib.vcs.utils.hgcompat import unionrepo -from kallithea.lib.compat import json, OrderedDict -from kallithea.lib.base import BaseRepoController, render +from kallithea.lib import helpers as h +from kallithea.lib import diffs from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ NotAnonymous -from kallithea.lib.helpers import Page -from kallithea.lib import helpers as h -from kallithea.lib import diffs +from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.compat import json, OrderedDict +from kallithea.lib.diffs import LimitedDiffContainer from kallithea.lib.exceptions import UserInvalidException +from kallithea.lib.page import Page from kallithea.lib.utils import action_logger, jsonify +from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError from kallithea.lib.vcs.utils import safe_str -from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError -from kallithea.lib.diffs import LimitedDiffContainer +from kallithea.lib.vcs.utils.hgcompat import unionrepo from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment, \ PullRequestReviewers, User from kallithea.model.pull_request import PullRequestModel diff --git a/kallithea/controllers/search.py b/kallithea/controllers/search.py --- a/kallithea/controllers/search.py +++ b/kallithea/controllers/search.py @@ -40,9 +40,9 @@ from kallithea.lib.auth import LoginRequ from kallithea.lib.base import BaseRepoController, render from kallithea.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \ IDX_NAME, WhooshResultWrapper -from kallithea.model.repo import RepoModel +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_str, safe_int -from kallithea.lib.helpers import Page +from kallithea.model.repo import RepoModel log = logging.getLogger(__name__) diff --git a/kallithea/lib/helpers.py b/kallithea/lib/helpers.py --- a/kallithea/lib/helpers.py +++ b/kallithea/lib/helpers.py @@ -19,7 +19,6 @@ available to Controllers. This module is """ import hashlib import StringIO -import math import logging import re import urlparse @@ -47,7 +46,6 @@ from webhelpers.text import chop_at, col convert_misc_entities, lchop, plural, rchop, remove_formatting, \ replace_whitespace, urlify, truncate, wrap_paragraphs from webhelpers.date import time_ago_in_words -from webhelpers.paginate import Page as _Page from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ convert_boolean_attrs, NotGiven, _make_safe_id_component @@ -885,234 +883,6 @@ def gravatar_url(email_address, size=30, .replace('{size}', safe_str(size)) return url -class Page(_Page): - """ - Custom pager to match rendering style with YUI paginator - """ - - def __init__(self, *args, **kwargs): - kwargs.setdefault('url', url.current) - _Page.__init__(self, *args, **kwargs) - - def _get_pos(self, cur_page, max_page, items): - edge = (items / 2) + 1 - if (cur_page <= edge): - radius = max(items / 2, items - cur_page) - elif (max_page - cur_page) < edge: - radius = (items - 1) - (max_page - cur_page) - else: - radius = items / 2 - - left = max(1, (cur_page - (radius))) - right = min(max_page, cur_page + (radius)) - return left, cur_page, right - - def _range(self, regexp_match): - """ - Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). - - Arguments: - - regexp_match - A "re" (regular expressions) match object containing the - radius of linked pages around the current page in - regexp_match.group(1) as a string - - This function is supposed to be called as a callable in - re.sub. - - """ - radius = int(regexp_match.group(1)) - - # Compute the first and last page number within the radius - # e.g. '1 .. 5 6 [7] 8 9 .. 12' - # -> leftmost_page = 5 - # -> rightmost_page = 9 - leftmost_page, _cur, rightmost_page = self._get_pos(self.page, - self.last_page, - (radius * 2) + 1) - nav_items = [] - - # Create a link to the first page (unless we are on the first page - # or there would be no need to insert '..' spacers) - if self.page != self.first_page and self.first_page < leftmost_page: - nav_items.append(self._pagerlink(self.first_page, self.first_page)) - - # Insert dots if there are pages between the first page - # and the currently displayed page range - if leftmost_page - self.first_page > 1: - # Wrap in a SPAN tag if nolink_attr is set - text_ = '..' - if self.dotdot_attr: - text_ = HTML.span(c=text_, **self.dotdot_attr) - nav_items.append(text_) - - for thispage in xrange(leftmost_page, rightmost_page + 1): - # Highlight the current page number and do not use a link - text_ = str(thispage) - if thispage == self.page: - # Wrap in a SPAN tag if nolink_attr is set - if self.curpage_attr: - text_ = HTML.span(c=text_, **self.curpage_attr) - nav_items.append(text_) - # Otherwise create just a link to that page - else: - nav_items.append(self._pagerlink(thispage, text_)) - - # Insert dots if there are pages between the displayed - # page numbers and the end of the page range - if self.last_page - rightmost_page > 1: - text_ = '..' - # Wrap in a SPAN tag if nolink_attr is set - if self.dotdot_attr: - text_ = HTML.span(c=text_, **self.dotdot_attr) - nav_items.append(text_) - - # Create a link to the very last page (unless we are on the last - # page or there would be no need to insert '..' spacers) - if self.page != self.last_page and rightmost_page < self.last_page: - nav_items.append(self._pagerlink(self.last_page, self.last_page)) - - #_page_link = url.current() - #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) - #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) - return self.separator.join(nav_items) - - def pager(self, format='~2~', page_param='page', partial_param='partial', - show_if_single_page=False, separator=' ', onclick=None, - symbol_first='<<', symbol_last='>>', - symbol_previous='<', symbol_next='>', - link_attr=None, - curpage_attr=None, - dotdot_attr=None, **kwargs): - self.curpage_attr = curpage_attr or {'class': 'pager_curpage'} - self.separator = separator - self.pager_kwargs = kwargs - self.page_param = page_param - self.partial_param = partial_param - self.onclick = onclick - self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'} - self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'} - - # Don't show navigator if there is no more than one page - if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): - return '' - - from string import Template - # Replace ~...~ in token format by range of pages - result = re.sub(r'~(\d+)~', self._range, format) - - # Interpolate '%' variables - result = Template(result).safe_substitute({ - 'first_page': self.first_page, - 'last_page': self.last_page, - 'page': self.page, - 'page_count': self.page_count, - 'items_per_page': self.items_per_page, - 'first_item': self.first_item, - 'last_item': self.last_item, - 'item_count': self.item_count, - 'link_first': self.page > self.first_page and \ - self._pagerlink(self.first_page, symbol_first) or '', - 'link_last': self.page < self.last_page and \ - self._pagerlink(self.last_page, symbol_last) or '', - 'link_previous': self.previous_page and \ - self._pagerlink(self.previous_page, symbol_previous) \ - or HTML.span(symbol_previous, class_="yui-pg-previous"), - 'link_next': self.next_page and \ - self._pagerlink(self.next_page, symbol_next) \ - or HTML.span(symbol_next, class_="yui-pg-next") - }) - - return literal(result) - - -#============================================================================== -# REPO PAGER, PAGER FOR REPOSITORY -#============================================================================== -class RepoPage(Page): - - def __init__(self, collection, page=1, items_per_page=20, - item_count=None, **kwargs): - - """Create a "RepoPage" instance. special pager for paging - repository - """ - # TODO: call baseclass __init__ - self._url_generator = kwargs.pop('url', url.current) - - # Safe the kwargs class-wide so they can be used in the pager() method - self.kwargs = kwargs - - # Save a reference to the collection - self.original_collection = collection - - self.collection = collection - - # The self.page is the number of the current page. - # The first page has the number 1! - try: - self.page = int(page) # make it int() if we get it as a string - except (ValueError, TypeError): - self.page = 1 - - self.items_per_page = items_per_page - - # Unless the user tells us how many items the collections has - # we calculate that ourselves. - if item_count is not None: - self.item_count = item_count - else: - self.item_count = len(self.collection) - - # Compute the number of the first and last available page - if self.item_count > 0: - self.first_page = 1 - self.page_count = int(math.ceil(float(self.item_count) / - self.items_per_page)) - self.last_page = self.first_page + self.page_count - 1 - - # Make sure that the requested page number is the range of - # valid pages - if self.page > self.last_page: - self.page = self.last_page - elif self.page < self.first_page: - self.page = self.first_page - - # Note: the number of items on this page can be less than - # items_per_page if the last page is not full - self.first_item = max(0, (self.item_count) - (self.page * - items_per_page)) - self.last_item = ((self.item_count - 1) - items_per_page * - (self.page - 1)) - - self.items = list(self.collection[self.first_item:self.last_item + 1]) - - # Links to previous and next page - if self.page > self.first_page: - self.previous_page = self.page - 1 - else: - self.previous_page = None - - if self.page < self.last_page: - self.next_page = self.page + 1 - else: - self.next_page = None - - # No items available - else: - self.first_page = None - self.page_count = 0 - self.last_page = None - self.first_item = None - self.last_item = None - self.previous_page = None - self.next_page = None - self.items = [] - - # This is a subclass of the 'list' type. Initialise the list now. - list.__init__(self, reversed(self.items)) - def changed_tooltip(nodes): """ diff --git a/kallithea/lib/page.py b/kallithea/lib/page.py new file mode 100644 --- /dev/null +++ b/kallithea/lib/page.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Custom paging classes +""" + +import math +import re +from kallithea.config.routing import url +from webhelpers.html import literal, HTML +from webhelpers.paginate import Page as _Page + +class Page(_Page): + """ + Custom pager to match rendering style with YUI paginator + """ + + def __init__(self, *args, **kwargs): + kwargs.setdefault('url', url.current) + _Page.__init__(self, *args, **kwargs) + + def _get_pos(self, cur_page, max_page, items): + edge = (items / 2) + 1 + if (cur_page <= edge): + radius = max(items / 2, items - cur_page) + elif (max_page - cur_page) < edge: + radius = (items - 1) - (max_page - cur_page) + else: + radius = items / 2 + + left = max(1, (cur_page - (radius))) + right = min(max_page, cur_page + (radius)) + return left, cur_page, right + + def _range(self, regexp_match): + """ + Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). + + Arguments: + + regexp_match + A "re" (regular expressions) match object containing the + radius of linked pages around the current page in + regexp_match.group(1) as a string + + This function is supposed to be called as a callable in + re.sub. + + """ + radius = int(regexp_match.group(1)) + + # Compute the first and last page number within the radius + # e.g. '1 .. 5 6 [7] 8 9 .. 12' + # -> leftmost_page = 5 + # -> rightmost_page = 9 + leftmost_page, _cur, rightmost_page = self._get_pos(self.page, + self.last_page, + (radius * 2) + 1) + nav_items = [] + + # Create a link to the first page (unless we are on the first page + # or there would be no need to insert '..' spacers) + if self.page != self.first_page and self.first_page < leftmost_page: + nav_items.append(self._pagerlink(self.first_page, self.first_page)) + + # Insert dots if there are pages between the first page + # and the currently displayed page range + if leftmost_page - self.first_page > 1: + # Wrap in a SPAN tag if nolink_attr is set + text_ = '..' + if self.dotdot_attr: + text_ = HTML.span(c=text_, **self.dotdot_attr) + nav_items.append(text_) + + for thispage in xrange(leftmost_page, rightmost_page + 1): + # Highlight the current page number and do not use a link + text_ = str(thispage) + if thispage == self.page: + # Wrap in a SPAN tag if nolink_attr is set + if self.curpage_attr: + text_ = HTML.span(c=text_, **self.curpage_attr) + nav_items.append(text_) + # Otherwise create just a link to that page + else: + nav_items.append(self._pagerlink(thispage, text_)) + + # Insert dots if there are pages between the displayed + # page numbers and the end of the page range + if self.last_page - rightmost_page > 1: + text_ = '..' + # Wrap in a SPAN tag if nolink_attr is set + if self.dotdot_attr: + text_ = HTML.span(c=text_, **self.dotdot_attr) + nav_items.append(text_) + + # Create a link to the very last page (unless we are on the last + # page or there would be no need to insert '..' spacers) + if self.page != self.last_page and rightmost_page < self.last_page: + nav_items.append(self._pagerlink(self.last_page, self.last_page)) + + #_page_link = url.current() + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) + return self.separator.join(nav_items) + + def pager(self, format='~2~', page_param='page', partial_param='partial', + show_if_single_page=False, separator=' ', onclick=None, + symbol_first='<<', symbol_last='>>', + symbol_previous='<', symbol_next='>', + link_attr=None, + curpage_attr=None, + dotdot_attr=None, **kwargs): + self.curpage_attr = curpage_attr or {'class': 'pager_curpage'} + self.separator = separator + self.pager_kwargs = kwargs + self.page_param = page_param + self.partial_param = partial_param + self.onclick = onclick + self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'} + self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'} + + # Don't show navigator if there is no more than one page + if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): + return '' + + from string import Template + # Replace ~...~ in token format by range of pages + result = re.sub(r'~(\d+)~', self._range, format) + + # Interpolate '%' variables + result = Template(result).safe_substitute({ + 'first_page': self.first_page, + 'last_page': self.last_page, + 'page': self.page, + 'page_count': self.page_count, + 'items_per_page': self.items_per_page, + 'first_item': self.first_item, + 'last_item': self.last_item, + 'item_count': self.item_count, + 'link_first': self.page > self.first_page and \ + self._pagerlink(self.first_page, symbol_first) or '', + 'link_last': self.page < self.last_page and \ + self._pagerlink(self.last_page, symbol_last) or '', + 'link_previous': self.previous_page and \ + self._pagerlink(self.previous_page, symbol_previous) \ + or HTML.span(symbol_previous, class_="yui-pg-previous"), + 'link_next': self.next_page and \ + self._pagerlink(self.next_page, symbol_next) \ + or HTML.span(symbol_next, class_="yui-pg-next") + }) + + return literal(result) + + +class RepoPage(Page): + + def __init__(self, collection, page=1, items_per_page=20, + item_count=None, **kwargs): + + """Create a "RepoPage" instance. special pager for paging + repository + """ + # TODO: call baseclass __init__ + self._url_generator = kwargs.pop('url', url.current) + + # Safe the kwargs class-wide so they can be used in the pager() method + self.kwargs = kwargs + + # Save a reference to the collection + self.original_collection = collection + + self.collection = collection + + # The self.page is the number of the current page. + # The first page has the number 1! + try: + self.page = int(page) # make it int() if we get it as a string + except (ValueError, TypeError): + self.page = 1 + + self.items_per_page = items_per_page + + # Unless the user tells us how many items the collections has + # we calculate that ourselves. + if item_count is not None: + self.item_count = item_count + else: + self.item_count = len(self.collection) + + # Compute the number of the first and last available page + if self.item_count > 0: + self.first_page = 1 + self.page_count = int(math.ceil(float(self.item_count) / + self.items_per_page)) + self.last_page = self.first_page + self.page_count - 1 + + # Make sure that the requested page number is the range of + # valid pages + if self.page > self.last_page: + self.page = self.last_page + elif self.page < self.first_page: + self.page = self.first_page + + # Note: the number of items on this page can be less than + # items_per_page if the last page is not full + self.first_item = max(0, (self.item_count) - (self.page * + items_per_page)) + self.last_item = ((self.item_count - 1) - items_per_page * + (self.page - 1)) + + self.items = list(self.collection[self.first_item:self.last_item + 1]) + + # Links to previous and next page + if self.page > self.first_page: + self.previous_page = self.page - 1 + else: + self.previous_page = None + + if self.page < self.last_page: + self.next_page = self.page + 1 + else: + self.next_page = None + + # No items available + else: + self.first_page = None + self.page_count = 0 + self.last_page = None + self.first_item = None + self.last_item = None + self.previous_page = None + self.next_page = None + self.items = [] + + # This is a subclass of the 'list' type. Initialise the list now. + list.__init__(self, reversed(self.items))