diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -1829,7 +1829,6 @@ self: super: { self."venusian" self."weberror" self."webhelpers2" - self."webhelpers" self."webob" self."whoosh" self."wsgiref" @@ -2212,20 +2211,6 @@ self: super: { license = [ pkgs.lib.licenses.mit ]; }; }; - "webhelpers" = super.buildPythonPackage { - name = "webhelpers-1.3"; - doCheck = false; - propagatedBuildInputs = [ - self."markupsafe" - ]; - src = fetchurl { - url = "https://files.pythonhosted.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz"; - sha256 = "10x5i82qdkrvyw18gsybwggfhfpl869siaab89vnndi9x62g51pa"; - }; - meta = { - license = [ pkgs.lib.licenses.bsdOriginal ]; - }; - }; "webhelpers2" = super.buildPythonPackage { name = "webhelpers2-2.0"; doCheck = false; diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -71,7 +71,6 @@ urlobject==2.4.3 venusian==1.2.0 weberror==0.10.3 webhelpers2==2.0 -webhelpers==1.3 webob==1.8.5 whoosh==2.7.4 wsgiref==0.1.2 diff --git a/rhodecode/apps/admin/views/audit_logs.py b/rhodecode/apps/admin/views/audit_logs.py --- a/rhodecode/apps/admin/views/audit_logs.py +++ b/rhodecode/apps/admin/views/audit_logs.py @@ -28,7 +28,7 @@ from rhodecode.model.db import joinedloa from rhodecode.lib.user_log_filter import user_log_filter from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.utils2 import safe_int -from rhodecode.lib.helpers import Page +from rhodecode.lib.helpers import SqlPage log = logging.getLogger(__name__) @@ -62,13 +62,16 @@ class AdminAuditLogsView(BaseAppView): p = safe_int(self.request.GET.get('page', 1), 1) - def url_generator(**kw): + def url_generator(page_num): + query_params = { + 'page': page_num + } if c.search_term: - kw['filter'] = c.search_term - return self.request.current_route_path(_query=kw) + query_params['filter'] = c.search_term + return self.request.current_route_path(_query=query_params) - c.audit_logs = Page(users_log, page=p, items_per_page=10, - url=url_generator) + c.audit_logs = SqlPage(users_log, page=p, items_per_page=10, + url_maker=url_generator) return self._get_template_context(c) @LoginRequired() diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py --- a/rhodecode/apps/admin/views/users.py +++ b/rhodecode/apps/admin/views/users.py @@ -44,6 +44,7 @@ from rhodecode.lib.ext_json import json from rhodecode.lib.auth import ( LoginRequired, HasPermissionAllDecorator, CSRFRequired) from rhodecode.lib import helpers as h +from rhodecode.lib.helpers import SqlPage from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict from rhodecode.model.auth_token import AuthTokenModel from rhodecode.model.forms import ( @@ -1228,13 +1229,16 @@ class UsersView(UserAppView): filter_term = self.request.GET.get('filter') user_log = UserModel().get_user_log(c.user, filter_term) - def url_generator(**kw): + def url_generator(page_num): + query_params = { + 'page': page_num + } if filter_term: - kw['filter'] = filter_term - return self.request.current_route_path(_query=kw) + query_params['filter'] = filter_term + return self.request.current_route_path(_query=query_params) - c.audit_logs = h.Page( - user_log, page=p, items_per_page=10, url=url_generator) + c.audit_logs = SqlPage( + user_log, page=p, items_per_page=10, url_maker=url_generator) c.filter_term = filter_term return self._get_template_context(c) diff --git a/rhodecode/apps/journal/views.py b/rhodecode/apps/journal/views.py --- a/rhodecode/apps/journal/views.py +++ b/rhodecode/apps/journal/views.py @@ -34,7 +34,7 @@ from rhodecode.model.db import ( or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys) from rhodecode.model.meta import Session import rhodecode.lib.helpers as h -from rhodecode.lib.helpers import Page +from rhodecode.lib.helpers import SqlPage from rhodecode.lib.user_log_filter import user_log_filter from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe @@ -232,15 +232,15 @@ class JournalView(BaseAppView): journal = self._get_journal_data(following, c.search_term) - def url_generator(**kw): + def url_generator(page_num): query_params = { + 'page': page_num, 'filter': c.search_term } - query_params.update(kw) return self.request.current_route_path(_query=query_params) - c.journal_pager = Page( - journal, page=p, items_per_page=20, url=url_generator) + c.journal_pager = SqlPage( + journal, page=p, items_per_page=20, url_maker=url_generator) c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) c.journal_data = render( @@ -333,13 +333,14 @@ class JournalView(BaseAppView): journal = self._get_journal_data(c.following, c.search_term) - def url_generator(**kw): - query_params = {} - query_params.update(kw) + def url_generator(page_num): + query_params = { + 'page': page_num + } return self.request.current_route_path(_query=query_params) - c.journal_pager = Page( - journal, page=p, items_per_page=20, url=url_generator) + c.journal_pager = SqlPage( + journal, page=p, items_per_page=20, url_maker=url_generator) c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) c.journal_data = render( diff --git a/rhodecode/apps/my_account/views/my_account_notifications.py b/rhodecode/apps/my_account/views/my_account_notifications.py --- a/rhodecode/apps/my_account/views/my_account_notifications.py +++ b/rhodecode/apps/my_account/views/my_account_notifications.py @@ -28,7 +28,7 @@ from rhodecode.apps._base import BaseApp from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired from rhodecode.lib import helpers as h -from rhodecode.lib.helpers import Page +from rhodecode.lib.helpers import SqlPage from rhodecode.lib.utils2 import safe_int from rhodecode.model.db import Notification from rhodecode.model.notification import NotificationModel @@ -74,13 +74,16 @@ class MyAccountNotificationsView(BaseApp p = safe_int(self.request.GET.get('page', 1), 1) - def url_generator(**kw): + def url_generator(page_num): + query_params = { + 'page': page_num + } _query = self.request.GET.mixed() - _query.update(kw) - return self.request.current_route_path(_query=_query) + query_params.update(_query) + return self.request.current_route_path(_query=query_params) - c.notifications = Page(notifications, page=p, items_per_page=10, - url=url_generator) + c.notifications = SqlPage(notifications, page=p, items_per_page=10, + url_maker=url_generator) c.unread_type = 'unread' c.all_type = 'all' diff --git a/rhodecode/apps/repository/views/repo_audit_logs.py b/rhodecode/apps/repository/views/repo_audit_logs.py --- a/rhodecode/apps/repository/views/repo_audit_logs.py +++ b/rhodecode/apps/repository/views/repo_audit_logs.py @@ -22,6 +22,7 @@ import logging from pyramid.view import view_config from rhodecode.apps._base import RepoAppView +from rhodecode.lib.helpers import SqlPage from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.utils2 import safe_int @@ -33,8 +34,6 @@ log = logging.getLogger(__name__) class AuditLogsView(RepoAppView): def load_default_context(self): c = self._get_local_tmpl_context() - - return c @LoginRequired() @@ -54,12 +53,15 @@ class AuditLogsView(RepoAppView): filter_term = self.request.GET.get('filter') user_log = RepoModel().get_repo_log(c.db_repo, filter_term) - def url_generator(**kw): + def url_generator(page_num): + query_params = { + 'page': page_num + } if filter_term: - kw['filter'] = filter_term - return self.request.current_route_path(_query=kw) + query_params['filter'] = filter_term + return self.request.current_route_path(_query=query_params) - c.audit_logs = h.Page( - user_log, page=p, items_per_page=10, url=url_generator) + c.audit_logs = SqlPage( + user_log, page=p, items_per_page=10, url_maker=url_generator) c.filter_term = filter_term return self._get_template_context(c) diff --git a/rhodecode/apps/repository/views/repo_changelog.py b/rhodecode/apps/repository/views/repo_changelog.py --- a/rhodecode/apps/repository/views/repo_changelog.py +++ b/rhodecode/apps/repository/views/repo_changelog.py @@ -121,9 +121,16 @@ class RepoChangelogView(RepoAppView): self, c, collection, page, chunk_size, branch_name=None, dynamic=False, f_path=None, commit_id=None): - def url_generator(**kw): - query_params = {} - query_params.update(kw) + def url_generator(page_num): + query_params = { + 'page': page_num + } + + if branch_name: + query_params.update({ + 'branch': branch_name + }) + if f_path: # changelog for file return h.route_path( @@ -139,8 +146,7 @@ class RepoChangelogView(RepoAppView): c.total_cs = len(collection) c.showing_commits = min(chunk_size, c.total_cs) c.pagination = RepoPage(collection, page=page, item_count=c.total_cs, - items_per_page=chunk_size, branch=branch_name, - url=url_generator) + items_per_page=chunk_size, url_maker=url_generator) c.next_page = c.pagination.next_page c.prev_page = c.pagination.previous_page diff --git a/rhodecode/apps/repository/views/repo_summary.py b/rhodecode/apps/repository/views/repo_summary.py --- a/rhodecode/apps/repository/views/repo_summary.py +++ b/rhodecode/apps/repository/views/repo_summary.py @@ -56,11 +56,11 @@ class RepoSummaryView(RepoAppView): p = safe_int(self.request.GET.get('page'), 1) size = safe_int(self.request.GET.get('size'), 10) - def url_generator(**kw): + def url_generator(page_num): query_params = { + 'page': page_num, 'size': size } - query_params.update(kw) return h.route_path( 'repo_summary_commits', repo_name=c.rhodecode_db_repo.repo_name, _query=query_params) @@ -73,7 +73,7 @@ class RepoSummaryView(RepoAppView): collection = self.rhodecode_vcs_repo c.repo_commits = h.RepoPage( - collection, page=p, items_per_page=size, url=url_generator) + collection, page=p, items_per_page=size, url_maker=url_generator) page_ids = [x.raw_id for x in c.repo_commits] c.comments = self.db_repo.get_comments(page_ids) c.statuses = self.db_repo.statuses(page_ids) diff --git a/rhodecode/apps/search/views.py b/rhodecode/apps/search/views.py --- a/rhodecode/apps/search/views.py +++ b/rhodecode/apps/search/views.py @@ -59,11 +59,19 @@ def perform_search(request, tmpl_context except validation_schema.Invalid as e: errors = e.children - def url_generator(**kw): + def url_generator(page_num): q = urllib.quote(safe_str(search_query)) - return update_params( - "?q=%s&type=%s&max_lines=%s&sort=%s" % ( - q, safe_str(search_type), search_max_lines, search_sort), **kw) + + query_params = { + 'page': page_num, + 'q': q, + 'type': safe_str(search_type), + 'max_lines': search_max_lines, + 'sort': search_sort + } + + return '?' + urllib.urlencode(query_params) + c = tmpl_context search_query = search_params.get('search_query') @@ -82,7 +90,7 @@ def perform_search(request, tmpl_context formatted_results = Page( search_result['results'], page=requested_page, item_count=search_result['count'], - items_per_page=page_limit, url=url_generator) + items_per_page=page_limit, url_maker=url_generator) finally: searcher.cleanup() @@ -100,7 +108,6 @@ def perform_search(request, tmpl_context c.perm_user = c.auth_user c.repo_name = repo_name c.repo_group_name = repo_group_name - c.url_generator = url_generator c.errors = errors c.formatted_results = formatted_results c.runtime = execution_time diff --git a/rhodecode/integrations/views.py b/rhodecode/integrations/views.py --- a/rhodecode/integrations/views.py +++ b/rhodecode/integrations/views.py @@ -27,11 +27,11 @@ from pyramid.httpexceptions import HTTPF from rhodecode.integrations import integration_type_registry from rhodecode.apps._base import BaseAppView from rhodecode.apps._base.navigation import navigation_list -from rhodecode.lib.paginate import PageURL from rhodecode.lib.auth import ( LoginRequired, CSRFRequired, HasPermissionAnyDecorator, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator) from rhodecode.lib.utils2 import safe_int +from rhodecode.lib.helpers import Page from rhodecode.lib import helpers as h from rhodecode.model.db import Repository, RepoGroup, Session, Integration from rhodecode.model.scm import ScmModel @@ -219,11 +219,16 @@ class IntegrationSettingsViewBase(BaseAp key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir == 'desc')) - page_url = PageURL(self.request.path, self.request.GET) + def url_generator(page_num): + query_params = { + 'page': page_num + } + return self.request.current_route_path(_query=query_params) + page = safe_int(self.request.GET.get('page', 1), 1) - integrations = h.Page( - integrations, page=page, items_per_page=10, url=page_url) + integrations = Page( + integrations, page=page, items_per_page=10, url_maker=url_generator) c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc' diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -75,7 +75,7 @@ from webhelpers2.html.tags import ( from webhelpers2.number import format_byte_size from rhodecode.lib.action_parser import action_parser -from rhodecode.lib.paginate import Page +from rhodecode.lib.pagination import Page, RepoPage, SqlPage from rhodecode.lib.ext_json import json from rhodecode.lib.utils import repo_name_slug, get_custom_lexer from rhodecode.lib.utils2 import ( @@ -1309,94 +1309,6 @@ def gravatar_url(email_address, size=30, return initials_gravatar(email_address, '', '', size=size) - - -#============================================================================== -# REPO PAGER, PAGER FOR REPOSITORY -#============================================================================== -class RepoPage(Page): - - def __init__(self, collection, page=1, items_per_page=20, - item_count=None, url=None, **kwargs): - - """Create a "RepoPage" instance. special pager for paging - repository - """ - self._url_generator = url - - # 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 breadcrumb_repo_link(repo): """ Makes a breadcrumbs path link to repo diff --git a/rhodecode/lib/paginate.py b/rhodecode/lib/pagination.py rename from rhodecode/lib/paginate.py rename to rhodecode/lib/pagination.py --- a/rhodecode/lib/paginate.py +++ b/rhodecode/lib/pagination.py @@ -1,5 +1,882 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2007-2012 Christoph Haas +# NOTE: MIT license based code, backported and edited by RhodeCode GmbH + +""" +paginate: helps split up large collections into individual pages +================================================================ + +What is pagination? +--------------------- + +This module helps split large lists of items into pages. The user is shown one page at a time and +can navigate to other pages. Imagine you are offering a company phonebook and let the user search +the entries. The entire search result may contains 23 entries but you want to display no more than +10 entries at once. The first page contains entries 1-10, the second 11-20 and the third 21-23. +Each "Page" instance represents the items of one of these three pages. + +See the documentation of the "Page" class for more information. + +How do I use it? +------------------ + +A page of items is represented by the *Page* object. A *Page* gets initialized with these arguments: + +- The collection of items to pick a range from. Usually just a list. +- The page number you want to display. Default is 1: the first page. + +Now we can make up a collection and create a Page instance of it:: + + # Create a sample collection of 1000 items + >> my_collection = range(1000) + + # Create a Page object for the 3rd page (20 items per page is the default) + >> my_page = Page(my_collection, page=3) + + # The page object can be printed as a string to get its details + >> str(my_page) + Page: + Collection type: + Current page: 3 + First item: 41 + Last item: 60 + First page: 1 + Last page: 50 + Previous page: 2 + Next page: 4 + Items per page: 20 + Number of items: 1000 + Number of pages: 50 + + # Print a list of items on the current page + >> my_page.items + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59] + + # The *Page* object can be used as an iterator: + >> for my_item in my_page: print(my_item) + 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 + + # The .pager() method returns an HTML fragment with links to surrounding pages. + >> my_page.pager(url="http://example.org/foo/page=$page") + + 1 + 2 + 3 + 4 + 5 + .. + 50' + + # Without the HTML it would just look like: + # 1 2 [3] 4 5 .. 50 + + # The pager can be customized: + >> my_page.pager('$link_previous ~3~ $link_next (Page $page of $page_count)', + url="http://example.org/foo/page=$page") + + < + 1 + 2 + 3 + 4 + 5 + 6 + .. + 50 + > + (Page 3 of 50) + + # Without the HTML it would just look like: + # 1 2 [3] 4 5 6 .. 50 > (Page 3 of 50) + + # The url argument to the pager method can be omitted when an url_maker is + # given during instantiation: + >> my_page = Page(my_collection, page=3, + url_maker=lambda p: "http://example.org/%s" % p) + >> page.pager() + +There are some interesting parameters that customize the Page's behavior. See the documentation on +``Page`` and ``Page.pager()``. + + +Notes +------- + +Page numbers and item numbers start at 1. This concept has been used because users expect that the +first page has number 1 and the first item on a page also has number 1. So if you want to use the +page's items by their index number please note that you have to subtract 1. +""" + +import re +import sys +from string import Template +from webhelpers2.html import literal + +# are we running at least python 3.x ? +PY3 = sys.version_info[0] >= 3 + +if PY3: + unicode = str + + +def make_html_tag(tag, text=None, **params): + """Create an HTML tag string. + + tag + The HTML tag to use (e.g. 'a', 'span' or 'div') + + text + The text to enclose between opening and closing tag. If no text is specified then only + the opening tag is returned. + + Example:: + make_html_tag('a', text="Hello", href="/another/page") + -> Hello + + To use reserved Python keywords like "class" as a parameter prepend it with + an underscore. Instead of "class='green'" use "_class='green'". + + Warning: Quotes and apostrophes are not escaped.""" + params_string = "" + + # Parameters are passed. Turn the dict into a string like "a=1 b=2 c=3" string. + for key, value in sorted(params.items()): + # Strip off a leading underscore from the attribute's key to allow attributes like '_class' + # to be used as a CSS class specification instead of the reserved Python keyword 'class'. + key = key.lstrip("_") + + params_string += u' {0}="{1}"'.format(key, value) + + # Create the tag string + tag_string = u"<{0}{1}>".format(tag, params_string) + + # Add text and closing tag if required. + if text: + tag_string += u"{0}".format(text, tag) + + return tag_string + + +# Since the items on a page are mainly a list we subclass the "list" type +class _Page(list): + """A list/iterator representing the items on one page of a larger collection. + + An instance of the "Page" class is created from a _collection_ which is any + list-like object that allows random access to its elements. + + The instance works as an iterator running from the first item to the last item on the given + page. The Page.pager() method creates a link list allowing the user to go to other pages. + + A "Page" does not only carry the items on a certain page. It gives you additional information + about the page in these "Page" object attributes: + + item_count + Number of items in the collection + + **WARNING:** Unless you pass in an item_count, a count will be + performed on the collection every time a Page instance is created. + + page + Number of the current page + + items_per_page + Maximal number of items displayed on a page + + first_page + Number of the first page - usually 1 :) + + last_page + Number of the last page + + previous_page + Number of the previous page. If this is the first page it returns None. + + next_page + Number of the next page. If this is the last page it returns None. + + page_count + Number of pages + + items + Sequence/iterator of items on the current page + + first_item + Index of first item on the current page - starts with 1 + + last_item + Index of last item on the current page + """ + + def __init__( + self, + collection, + page=1, + items_per_page=20, + item_count=None, + wrapper_class=None, + url_maker=None, + bar_size=10, + **kwargs + ): + """Create a "Page" instance. + + Parameters: + + collection + Sequence representing the collection of items to page through. + + page + The requested page number - starts with 1. Default: 1. + + items_per_page + The maximal number of items to be displayed per page. + Default: 20. + + item_count (optional) + The total number of items in the collection - if known. + If this parameter is not given then the paginator will count + the number of elements in the collection every time a "Page" + is created. Giving this parameter will speed up things. In a busy + real-life application you may want to cache the number of items. + + url_maker (optional) + Callback to generate the URL of other pages, given its numbers. + Must accept one int parameter and return a URI string. + + bar_size + maximum size of rendered pages numbers within radius + + """ + if collection is not None: + if wrapper_class is None: + # Default case. The collection is already a list-type object. + self.collection = collection + else: + # Special case. A custom wrapper class is used to access elements of the collection. + self.collection = wrapper_class(collection) + else: + self.collection = [] + + self.collection_type = type(collection) + + if url_maker is not None: + self.url_maker = url_maker + else: + self.url_maker = self._default_url_maker + self.bar_size = bar_size + # Assign kwargs to self + self.kwargs = kwargs + + # 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 + # normally page should be always at least 1 but the original maintainer + # decided that for empty collection and empty page it can be...0? (based on tests) + # preserving behavior for BW compat + if self.page < 1: + self.page = 1 + + self.items_per_page = items_per_page + + # We subclassed "list" so we need to call its init() method + # and fill the new list with the items to be displayed on the page. + # We use list() so that the items on the current page are retrieved + # only once. In an SQL context that could otherwise lead to running the + # same SQL query every time items would be accessed. + # We do this here, prior to calling len() on the collection so that a + # wrapper class can execute a query with the knowledge of what the + # slice will be (for efficiency) and, in the same query, ask for the + # total number of items and only execute one query. + try: + first = (self.page - 1) * items_per_page + last = first + items_per_page + self.items = list(self.collection[first:last]) + except TypeError: + raise TypeError( + "Your collection of type {} cannot be handled " + "by paginate.".format(type(self.collection)) + ) + + # 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 = ((self.item_count - 1) // self.items_per_page) + 1 + 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 = (self.page - 1) * items_per_page + 1 + self.last_item = min(self.first_item + items_per_page - 1, self.item_count) + + # 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, self.items) + + def __str__(self): + return ( + "Page:\n" + "Collection type: {0.collection_type}\n" + "Current page: {0.page}\n" + "First item: {0.first_item}\n" + "Last item: {0.last_item}\n" + "First page: {0.first_page}\n" + "Last page: {0.last_page}\n" + "Previous page: {0.previous_page}\n" + "Next page: {0.next_page}\n" + "Items per page: {0.items_per_page}\n" + "Total number of items: {0.item_count}\n" + "Number of pages: {0.page_count}\n" + ).format(self) + + def __repr__(self): + return "".format(self.page, self.page_count) + + def pager( + self, + tmpl_format="~2~", + url=None, + show_if_single_page=False, + separator=" ", + symbol_first="<<", + symbol_last=">>", + symbol_previous="<", + symbol_next=">", + link_attr=None, + curpage_attr=None, + dotdot_attr=None, + link_tag=None, + ): + """ + Return string with links to other pages (e.g. '1 .. 5 6 7 [8] 9 10 11 .. 50'). + + tmpl_format: + Format string that defines how the pager is rendered. The string + can contain the following $-tokens that are substituted by the + string.Template module: + + - $first_page: number of first reachable page + - $last_page: number of last reachable page + - $page: number of currently selected page + - $page_count: number of reachable pages + - $items_per_page: maximal number of items per page + - $first_item: index of first item on the current page + - $last_item: index of last item on the current page + - $item_count: total number of items + - $link_first: link to first page (unless this is first page) + - $link_last: link to last page (unless this is last page) + - $link_previous: link to previous page (unless this is first page) + - $link_next: link to next page (unless this is last page) + + To render a range of pages the token '~3~' can be used. The + number sets the radius of pages around the current page. + Example for a range with radius 3: + + '1 .. 5 6 7 [8] 9 10 11 .. 50' + + Default: '~2~' + + url + The URL that page links will point to. Make sure it contains the string + $page which will be replaced by the actual page number. + Must be given unless a url_maker is specified to __init__, in which + case this parameter is ignored. + + symbol_first + String to be displayed as the text for the $link_first link above. + + Default: '<<' (<<) + + symbol_last + String to be displayed as the text for the $link_last link above. + + Default: '>>' (>>) + + symbol_previous + String to be displayed as the text for the $link_previous link above. + + Default: '<' (<) + + symbol_next + String to be displayed as the text for the $link_next link above. + + Default: '>' (>) + + separator: + String that is used to separate page links/numbers in the above range of pages. + + Default: ' ' + + show_if_single_page: + if True the navigator will be shown even if there is only one page. + + Default: False + + link_attr (optional) + A dictionary of attributes that get added to A-HREF links pointing to other pages. Can + be used to define a CSS style or class to customize the look of links. + + Example: { 'style':'border: 1px solid green' } + Example: { 'class':'pager_link' } + + curpage_attr (optional) + A dictionary of attributes that get added to the current page number in the pager (which + is obviously not a link). If this dictionary is not empty then the elements will be + wrapped in a SPAN tag with the given attributes. + + Example: { 'style':'border: 3px solid blue' } + Example: { 'class':'pager_curpage' } + + dotdot_attr (optional) + A dictionary of attributes that get added to the '..' string in the pager (which is + obviously not a link). If this dictionary is not empty then the elements will be wrapped + in a SPAN tag with the given attributes. + + Example: { 'style':'color: #808080' } + Example: { 'class':'pager_dotdot' } + + link_tag (optional) + A callable that accepts single argument `page` (page link information) + and generates string with html that represents the link for specific page. + Page objects are supplied from `link_map()` so the keys are the same. + + + """ + link_attr = link_attr or {} + curpage_attr = curpage_attr or {} + dotdot_attr = dotdot_attr or {} + self.curpage_attr = curpage_attr + self.separator = separator + self.link_attr = link_attr + self.dotdot_attr = dotdot_attr + self.url = url + self.link_tag = link_tag or self.default_link_tag + + # 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 "" + + regex_res = re.search(r"~(\d+)~", tmpl_format) + if regex_res: + radius = regex_res.group(1) + else: + radius = 2 + + self.radius = int(radius) + link_map = self.link_map( + tmpl_format=tmpl_format, + url=url, + show_if_single_page=show_if_single_page, + separator=separator, + symbol_first=symbol_first, + symbol_last=symbol_last, + symbol_previous=symbol_previous, + symbol_next=symbol_next, + link_attr=link_attr, + curpage_attr=curpage_attr, + dotdot_attr=dotdot_attr, + link_tag=link_tag, + ) + links_markup = self._range(link_map, self.radius) + + # Replace ~...~ in token tmpl_format by range of pages + result = re.sub(r"~(\d+)~", links_markup, tmpl_format) + + link_first = ( + self.page > self.first_page and self.link_tag(link_map["first_page"]) or "" + ) + link_last = ( + self.page < self.last_page and self.link_tag(link_map["last_page"]) or "" + ) + link_previous = ( + self.previous_page and self.link_tag(link_map["previous_page"]) or "" + ) + link_next = self.next_page and self.link_tag(link_map["next_page"]) or "" + # 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": link_first, + "link_last": link_last, + "link_previous": link_previous, + "link_next": link_next, + } + ) + + return result + + def _get_edges(self, cur_page, max_page, items): + cur_page = int(cur_page) + 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) - 1 + + left = max(1, (cur_page - radius)) + right = min(max_page, cur_page + radius) + return left, right + + def link_map( + self, + tmpl_format="~2~", + url=None, + show_if_single_page=False, + separator=" ", + symbol_first="<<", + symbol_last=">>", + symbol_previous="<", + symbol_next=">", + link_attr=None, + curpage_attr=None, + dotdot_attr=None, + link_tag=None + ): + """ Return map with links to other pages if default pager() function is not suitable solution. + tmpl_format: + Format string that defines how the pager would be normally rendered rendered. Uses same arguments as pager() + method, but returns a simple dictionary in form of: + {'current_page': {'attrs': {}, + 'href': 'http://example.org/foo/page=1', + 'value': 1}, + 'first_page': {'attrs': {}, + 'href': 'http://example.org/foo/page=1', + 'type': 'first_page', + 'value': 1}, + 'last_page': {'attrs': {}, + 'href': 'http://example.org/foo/page=8', + 'type': 'last_page', + 'value': 8}, + 'next_page': {'attrs': {}, 'href': 'HREF', 'type': 'next_page', 'value': 2}, + 'previous_page': None, + 'range_pages': [{'attrs': {}, + 'href': 'http://example.org/foo/page=1', + 'type': 'current_page', + 'value': 1}, + .... + {'attrs': {}, 'href': '', 'type': 'span', 'value': '..'}]} + + + The string can contain the following $-tokens that are substituted by the + string.Template module: + + - $first_page: number of first reachable page + - $last_page: number of last reachable page + - $page: number of currently selected page + - $page_count: number of reachable pages + - $items_per_page: maximal number of items per page + - $first_item: index of first item on the current page + - $last_item: index of last item on the current page + - $item_count: total number of items + - $link_first: link to first page (unless this is first page) + - $link_last: link to last page (unless this is last page) + - $link_previous: link to previous page (unless this is first page) + - $link_next: link to next page (unless this is last page) + + To render a range of pages the token '~3~' can be used. The + number sets the radius of pages around the current page. + Example for a range with radius 3: + + '1 .. 5 6 7 [8] 9 10 11 .. 50' + + Default: '~2~' + + url + The URL that page links will point to. Make sure it contains the string + $page which will be replaced by the actual page number. + Must be given unless a url_maker is specified to __init__, in which + case this parameter is ignored. + + symbol_first + String to be displayed as the text for the $link_first link above. + + Default: '<<' (<<) + + symbol_last + String to be displayed as the text for the $link_last link above. + + Default: '>>' (>>) + + symbol_previous + String to be displayed as the text for the $link_previous link above. + + Default: '<' (<) + + symbol_next + String to be displayed as the text for the $link_next link above. + + Default: '>' (>) + + separator: + String that is used to separate page links/numbers in the above range of pages. + + Default: ' ' + + show_if_single_page: + if True the navigator will be shown even if there is only one page. + + Default: False + + link_attr (optional) + A dictionary of attributes that get added to A-HREF links pointing to other pages. Can + be used to define a CSS style or class to customize the look of links. + + Example: { 'style':'border: 1px solid green' } + Example: { 'class':'pager_link' } + + curpage_attr (optional) + A dictionary of attributes that get added to the current page number in the pager (which + is obviously not a link). If this dictionary is not empty then the elements will be + wrapped in a SPAN tag with the given attributes. + + Example: { 'style':'border: 3px solid blue' } + Example: { 'class':'pager_curpage' } + + dotdot_attr (optional) + A dictionary of attributes that get added to the '..' string in the pager (which is + obviously not a link). If this dictionary is not empty then the elements will be wrapped + in a SPAN tag with the given attributes. + + Example: { 'style':'color: #808080' } + Example: { 'class':'pager_dotdot' } + """ + link_attr = link_attr or {} + curpage_attr = curpage_attr or {} + dotdot_attr = dotdot_attr or {} + self.curpage_attr = curpage_attr + self.separator = separator + self.link_attr = link_attr + self.dotdot_attr = dotdot_attr + self.url = url + + regex_res = re.search(r"~(\d+)~", tmpl_format) + if regex_res: + radius = regex_res.group(1) + else: + radius = 2 + + self.radius = int(radius) + + # 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, rightmost_page = self._get_edges( + self.page, self.last_page, (self.radius * 2) + 1) + + nav_items = { + "first_page": None, + "last_page": None, + "previous_page": None, + "next_page": None, + "current_page": None, + "radius": self.radius, + "range_pages": [], + } + + if leftmost_page is None or rightmost_page is None: + return nav_items + + nav_items["first_page"] = { + "type": "first_page", + "value": unicode(symbol_first), + "attrs": self.link_attr, + "number": self.first_page, + "href": self.url_maker(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 dotdot_attr is set + nav_items["range_pages"].append( + { + "type": "span", + "value": "..", + "attrs": self.dotdot_attr, + "href": "", + "number": None, + } + ) + + for this_page in range(leftmost_page, rightmost_page + 1): + # Highlight the current page number and do not use a link + if this_page == self.page: + # Wrap in a SPAN tag if curpage_attr is set + nav_items["range_pages"].append( + { + "type": "current_page", + "value": unicode(this_page), + "number": this_page, + "attrs": self.curpage_attr, + "href": self.url_maker(this_page), + } + ) + nav_items["current_page"] = { + "value": this_page, + "attrs": self.curpage_attr, + "type": "current_page", + "href": self.url_maker(this_page), + } + # Otherwise create just a link to that page + else: + nav_items["range_pages"].append( + { + "type": "page", + "value": unicode(this_page), + "number": this_page, + "attrs": self.link_attr, + "href": self.url_maker(this_page), + } + ) + + # 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: + # Wrap in a SPAN tag if dotdot_attr is set + nav_items["range_pages"].append( + { + "type": "span", + "value": "..", + "attrs": self.dotdot_attr, + "href": "", + "number": None, + } + ) + + # Create a link to the very last page (unless we are on the last + # page or there would be no need to insert '..' spacers) + nav_items["last_page"] = { + "type": "last_page", + "value": unicode(symbol_last), + "attrs": self.link_attr, + "href": self.url_maker(self.last_page), + "number": self.last_page, + } + + nav_items["previous_page"] = { + "type": "previous_page", + "value": unicode(symbol_previous), + "attrs": self.link_attr, + "number": self.previous_page or self.first_page, + "href": self.url_maker(self.previous_page or self.first_page), + } + + nav_items["next_page"] = { + "type": "next_page", + "value": unicode(symbol_next), + "attrs": self.link_attr, + "number": self.next_page or self.last_page, + "href": self.url_maker(self.next_page or self.last_page), + } + + return nav_items + + def _range(self, link_map, radius): + """ + Return range of linked pages to substitute placeholder in pattern + """ + # 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, rightmost_page = self._get_edges( + 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.first_page and self.page != self.first_page and self.first_page < leftmost_page: + page = link_map["first_page"].copy() + page["value"] = unicode(page["number"]) + nav_items.append(self.link_tag(page)) + + for item in link_map["range_pages"]: + nav_items.append(self.link_tag(item)) + + # 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.last_page and self.page != self.last_page and rightmost_page < self.last_page: + page = link_map["last_page"].copy() + page["value"] = unicode(page["number"]) + nav_items.append(self.link_tag(page)) + + return self.separator.join(nav_items) + + def _default_url_maker(self, page_number): + if self.url is None: + raise Exception( + "You need to specify a 'url' parameter containing a '$page' placeholder." + ) + + if "$page" not in self.url: + raise Exception("The 'url' parameter must contain a '$page' placeholder.") + + return self.url.replace("$page", unicode(page_number)) + + @staticmethod + def default_link_tag(item): + """ + Create an A-HREF tag that points to another page. + """ + text = item["value"] + target_url = item["href"] + + if not item["href"] or item["type"] in ("span", "current_page"): + if item["attrs"]: + text = make_html_tag("span", **item["attrs"]) + text + "" + return text + + return make_html_tag("a", text=text, href=target_url, **item["attrs"]) + +# Below is RhodeCode custom code + # Copyright (C) 2010-2019 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -17,149 +894,163 @@ # This program is dual-licensed. If you wish to learn more about the # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -import re + + +PAGE_FORMAT = '$link_previous ~3~ $link_next' + + +class SqlalchemyOrmWrapper(object): + """Wrapper class to access elements of a collection.""" -from webhelpers.paginate import Page as _Page -from webhelpers.paginate import PageURL -from webhelpers2.html import literal, HTML + def __init__(self, pager, collection): + self.pager = pager + self.collection = collection + + def __getitem__(self, range): + # Return a range of objects of an sqlalchemy.orm.query.Query object + return self.collection[range] + + def __len__(self): + # Count the number of objects in an sqlalchemy.orm.query.Query object + return self.collection.count() -class Page(_Page): +class CustomPager(_Page): + + @staticmethod + def disabled_link_tag(item): + """ + Create an A-HREF tag that is disabled + """ + text = item['value'] + attrs = item['attrs'].copy() + attrs['class'] = 'disabled ' + attrs['class'] + + return make_html_tag('a', text=text, **attrs) + + def render(self): + # Don't show navigator if there is no more than one page + if self.page_count == 0: + return "" + + self.link_tag = self.default_link_tag + + link_map = self.link_map( + tmpl_format=PAGE_FORMAT, url=None, + show_if_single_page=False, separator=' ', + symbol_first='<<', symbol_last='>>', + symbol_previous='<', symbol_next='>', + link_attr={'class': 'pager_link'}, + curpage_attr={'class': 'pager_curpage'}, + dotdot_attr={'class': 'pager_dotdot'}) + + links_markup = self._range(link_map, self.radius) + + link_first = ( + self.page > self.first_page and self.link_tag(link_map['first_page']) or '' + ) + link_last = ( + self.page < self.last_page and self.link_tag(link_map['last_page']) or '' + ) + + link_previous = ( + self.previous_page and self.link_tag(link_map['previous_page']) + or self.disabled_link_tag(link_map['previous_page']) + ) + link_next = ( + self.next_page and self.link_tag(link_map['next_page']) + or self.disabled_link_tag(link_map['next_page']) + ) + + # Interpolate '$' variables + # Replace ~...~ in token tmpl_format by range of pages + result = re.sub(r"~(\d+)~", links_markup, PAGE_FORMAT) + result = Template(result).safe_substitute( + { + "links": links_markup, + "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": link_first, + "link_last": link_last, + "link_previous": link_previous, + "link_next": link_next, + } + ) + + return literal(result) + + +class Page(CustomPager): """ Custom pager to match rendering style with paginator """ - 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): + def __init__(self, collection, page=1, items_per_page=20, item_count=None, + url_maker=None, **kwargs): + """ + Special type of pager. We intercept collection to wrap it in our custom + logic instead of using wrapper_class """ - 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 + super(Page, self).__init__(collection=collection, page=page, + items_per_page=items_per_page, item_count=item_count, + wrapper_class=None, url_maker=url_maker, **kwargs) - 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)) +class SqlPage(CustomPager): + """ + Custom pager to match rendering style with paginator + """ - # 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) + def __init__(self, collection, page=1, items_per_page=20, item_count=None, + url_maker=None, **kwargs): + """ + Special type of pager. We intercept collection to wrap it in our custom + logic instead of using wrapper_class + """ + collection = SqlalchemyOrmWrapper(self, collection) - for thispage in xrange(leftmost_page, rightmost_page + 1): - # Hilight the current page number and do not use a link - if thispage == self.page: - text = '%s' % (thispage,) - # 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: - text = '%s' % (thispage,) - nav_items.append(self._pagerlink(thispage, text)) + super(SqlPage, self).__init__(collection=collection, page=page, + items_per_page=items_per_page, item_count=item_count, + wrapper_class=None, url_maker=url_maker, **kwargs) + - # 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) +class RepoCommitsWrapper(object): + """Wrapper class to access elements of a collection.""" - # 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)) + def __init__(self, pager, collection): + self.pager = pager + self.collection = collection - ## prerender links - #_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 __getitem__(self, range): + cur_page = self.pager.page + items_per_page = self.pager.items_per_page + first_item = max(0, (len(self.collection) - (cur_page * items_per_page))) + last_item = ((len(self.collection) - 1) - items_per_page * (cur_page - 1)) + return reversed(list(self.collection[first_item:last_item + 1])) - 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={'class': 'pager_link', 'rel': 'prerender'}, - curpage_attr={'class': 'pager_curpage'}, - dotdot_attr={'class': 'pager_dotdot'}, **kwargs): + def __len__(self): + return len(self.collection) - self.curpage_attr = curpage_attr - 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 - self.dotdot_attr = dotdot_attr - # 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) +class RepoPage(CustomPager): + """ + Create a "RepoPage" instance. special pager for paging repository + """ - # 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_="pg-previous disabled"), - 'link_next': self.next_page and \ - self._pagerlink(self.next_page, symbol_next) \ - or HTML.span(symbol_next, class_="pg-next disabled") - }) - - return literal(result) + def __init__(self, collection, page=1, items_per_page=20, item_count=None, + url_maker=None, **kwargs): + """ + Special type of pager. We intercept collection to wrap it in our custom + logic instead of using wrapper_class + """ + collection = RepoCommitsWrapper(self, collection) + super(RepoPage, self).__init__(collection=collection, page=page, + items_per_page=items_per_page, item_count=item_count, + wrapper_class=None, url_maker=url_maker, **kwargs) diff --git a/rhodecode/public/css/navigation.less b/rhodecode/public/css/navigation.less --- a/rhodecode/public/css/navigation.less +++ b/rhodecode/public/css/navigation.less @@ -625,8 +625,9 @@ ul#context-pages { top: 95px; } -.dataTables_paginate, .pagination-wh { - text-align: left; +.dataTables_paginate, +.pagination-wh { + text-align: center; display: inline-block; border-left: 1px solid @grey5; float: none; @@ -638,10 +639,15 @@ ul#context-pages { display: inline-block; padding: @menupadding/4 @menupadding; border: 1px solid @grey5; - border-left: 0; + margin-left: -1px; color: @grey2; cursor: pointer; float: left; + font-weight: 600; + white-space: nowrap; + vertical-align: middle; + user-select: none; + min-width: 15px; &:hover { color: @rcdarkblue; diff --git a/rhodecode/templates/admin/admin_log_base.mako b/rhodecode/templates/admin/admin_log_base.mako --- a/rhodecode/templates/admin/admin_log_base.mako +++ b/rhodecode/templates/admin/admin_log_base.mako @@ -62,7 +62,7 @@
-${c.audit_logs.pager('$link_previous ~2~ $link_next')} +${c.audit_logs.render()}
%else: ${_('No actions yet')} diff --git a/rhodecode/templates/admin/integrations/list.mako b/rhodecode/templates/admin/integrations/list.mako --- a/rhodecode/templates/admin/integrations/list.mako +++ b/rhodecode/templates/admin/integrations/list.mako @@ -188,7 +188,7 @@
- ${c.integrations_list.pager('$link_previous ~2~ $link_next')} + ${c.integrations_list.render()}
diff --git a/rhodecode/templates/admin/notifications/notifications_data.mako b/rhodecode/templates/admin/notifications/notifications_data.mako --- a/rhodecode/templates/admin/notifications/notifications_data.mako +++ b/rhodecode/templates/admin/notifications/notifications_data.mako @@ -34,7 +34,7 @@
- ${c.notifications.pager('$link_previous ~2~ $link_next')} + ${c.notifications.render()}
diff --git a/rhodecode/templates/commits/changelog.mako b/rhodecode/templates/commits/changelog.mako --- a/rhodecode/templates/commits/changelog.mako +++ b/rhodecode/templates/commits/changelog.mako @@ -130,7 +130,7 @@
- ${c.pagination.pager('$link_previous ~2~ $link_next')} + ${c.pagination.render()}
${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)} diff --git a/rhodecode/templates/journal/journal.mako b/rhodecode/templates/journal/journal.mako --- a/rhodecode/templates/journal/journal.mako +++ b/rhodecode/templates/journal/journal.mako @@ -37,7 +37,9 @@
  • - + + RSS +
  • @@ -45,14 +47,6 @@ diff --git a/rhodecode/templates/journal/journal_data.mako b/rhodecode/templates/journal/journal_data.mako --- a/rhodecode/templates/journal/journal_data.mako +++ b/rhodecode/templates/journal/journal_data.mako @@ -36,17 +36,10 @@ %endfor %endfor -
    - ${c.journal_pager.pager('$link_previous ~2~ $link_next')} +
    + ${c.journal_pager.render()}
    - + %else:
    ${_('No entries yet')} diff --git a/rhodecode/templates/search/search.mako b/rhodecode/templates/search/search.mako --- a/rhodecode/templates/search/search.mako +++ b/rhodecode/templates/search/search.mako @@ -65,11 +65,11 @@ <% if c.sort.startswith('asc:'): - return c.url_generator(sort='desc:{}'.format(field_name)) + return h.current_route_path(request, sort='desc:{}'.format(field_name)) elif c.sort.startswith('desc:'): - return c.url_generator(sort='asc:{}'.format(field_name)) + return h.current_route_path(request, sort='asc:{}'.format(field_name)) - return c.url_generator(sort='asc:{}'.format(field_name)) + return h.current_route_path(request, sort='asc:{}'.format(field_name)) %> diff --git a/rhodecode/templates/search/search_commit.mako b/rhodecode/templates/search/search_commit.mako --- a/rhodecode/templates/search/search_commit.mako +++ b/rhodecode/templates/search/search_commit.mako @@ -63,7 +63,7 @@ %if c.cur_query:
    - ${c.formatted_results.pager('$link_previous ~2~ $link_next')} + ${c.formatted_results.render()}
    %endif diff --git a/rhodecode/templates/search/search_content.mako b/rhodecode/templates/search/search_content.mako --- a/rhodecode/templates/search/search_content.mako +++ b/rhodecode/templates/search/search_content.mako @@ -145,7 +145,7 @@
    %if c.cur_query and c.formatted_results:
    - ${c.formatted_results.pager('$link_previous ~2~ $link_next')} + ${c.formatted_results.render()}
    %endif diff --git a/rhodecode/templates/search/search_path.mako b/rhodecode/templates/search/search_path.mako --- a/rhodecode/templates/search/search_path.mako +++ b/rhodecode/templates/search/search_path.mako @@ -46,7 +46,7 @@ %if c.cur_query:
    - ${c.formatted_results.pager('$link_previous ~2~ $link_next')} + ${c.formatted_results.render()}
    %endif diff --git a/rhodecode/templates/summary/summary_commits.mako b/rhodecode/templates/summary/summary_commits.mako --- a/rhodecode/templates/summary/summary_commits.mako +++ b/rhodecode/templates/summary/summary_commits.mako @@ -107,7 +107,7 @@
    -${c.repo_commits.pager('$link_previous ~2~ $link_next')} +${c.repo_commits.render()}
    %else: