# HG changeset patch # User Thomas De Schampheleire # Date 2016-10-08 22:59:50 # Node ID e33b17db388d4eb163153f85a841912e98ea8065 # Parent d3930bd0c14a81743c73c78e11ae703a3772512f helpers: move Page/RepoPage to a separate file page.py The Page and RepoPage classes are not used from templates and thus do not belong in helpers. They could have been put in utils.py, but because they are completely standalone we can easily split them to a separate file. 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))