##// END OF EJS Templates
webhelpers: replaced paginate library with custom lib
dan -
r4091:4e2f3dca default
parent child Browse files
Show More
@@ -1829,7 +1829,6 b' self: super: {'
1829 self."venusian"
1829 self."venusian"
1830 self."weberror"
1830 self."weberror"
1831 self."webhelpers2"
1831 self."webhelpers2"
1832 self."webhelpers"
1833 self."webob"
1832 self."webob"
1834 self."whoosh"
1833 self."whoosh"
1835 self."wsgiref"
1834 self."wsgiref"
@@ -2212,20 +2211,6 b' self: super: {'
2212 license = [ pkgs.lib.licenses.mit ];
2211 license = [ pkgs.lib.licenses.mit ];
2213 };
2212 };
2214 };
2213 };
2215 "webhelpers" = super.buildPythonPackage {
2216 name = "webhelpers-1.3";
2217 doCheck = false;
2218 propagatedBuildInputs = [
2219 self."markupsafe"
2220 ];
2221 src = fetchurl {
2222 url = "https://files.pythonhosted.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
2223 sha256 = "10x5i82qdkrvyw18gsybwggfhfpl869siaab89vnndi9x62g51pa";
2224 };
2225 meta = {
2226 license = [ pkgs.lib.licenses.bsdOriginal ];
2227 };
2228 };
2229 "webhelpers2" = super.buildPythonPackage {
2214 "webhelpers2" = super.buildPythonPackage {
2230 name = "webhelpers2-2.0";
2215 name = "webhelpers2-2.0";
2231 doCheck = false;
2216 doCheck = false;
@@ -71,7 +71,6 b' urlobject==2.4.3'
71 venusian==1.2.0
71 venusian==1.2.0
72 weberror==0.10.3
72 weberror==0.10.3
73 webhelpers2==2.0
73 webhelpers2==2.0
74 webhelpers==1.3
75 webob==1.8.5
74 webob==1.8.5
76 whoosh==2.7.4
75 whoosh==2.7.4
77 wsgiref==0.1.2
76 wsgiref==0.1.2
@@ -28,7 +28,7 b' from rhodecode.model.db import joinedloa'
28 from rhodecode.lib.user_log_filter import user_log_filter
28 from rhodecode.lib.user_log_filter import user_log_filter
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from rhodecode.lib.utils2 import safe_int
30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import SqlPage
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
@@ -62,13 +62,16 b' class AdminAuditLogsView(BaseAppView):'
62
62
63 p = safe_int(self.request.GET.get('page', 1), 1)
63 p = safe_int(self.request.GET.get('page', 1), 1)
64
64
65 def url_generator(**kw):
65 def url_generator(page_num):
66 query_params = {
67 'page': page_num
68 }
66 if c.search_term:
69 if c.search_term:
67 kw['filter'] = c.search_term
70 query_params['filter'] = c.search_term
68 return self.request.current_route_path(_query=kw)
71 return self.request.current_route_path(_query=query_params)
69
72
70 c.audit_logs = Page(users_log, page=p, items_per_page=10,
73 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
71 url=url_generator)
74 url_maker=url_generator)
72 return self._get_template_context(c)
75 return self._get_template_context(c)
73
76
74 @LoginRequired()
77 @LoginRequired()
@@ -44,6 +44,7 b' from rhodecode.lib.ext_json import json'
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.helpers import SqlPage
47 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
@@ -1228,13 +1229,16 b' class UsersView(UserAppView):'
1228 filter_term = self.request.GET.get('filter')
1229 filter_term = self.request.GET.get('filter')
1229 user_log = UserModel().get_user_log(c.user, filter_term)
1230 user_log = UserModel().get_user_log(c.user, filter_term)
1230
1231
1231 def url_generator(**kw):
1232 def url_generator(page_num):
1233 query_params = {
1234 'page': page_num
1235 }
1232 if filter_term:
1236 if filter_term:
1233 kw['filter'] = filter_term
1237 query_params['filter'] = filter_term
1234 return self.request.current_route_path(_query=kw)
1238 return self.request.current_route_path(_query=query_params)
1235
1239
1236 c.audit_logs = h.Page(
1240 c.audit_logs = SqlPage(
1237 user_log, page=p, items_per_page=10, url=url_generator)
1241 user_log, page=p, items_per_page=10, url_maker=url_generator)
1238 c.filter_term = filter_term
1242 c.filter_term = filter_term
1239 return self._get_template_context(c)
1243 return self._get_template_context(c)
1240
1244
@@ -34,7 +34,7 b' from rhodecode.model.db import ('
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import SqlPage
38 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
@@ -232,15 +232,15 b' class JournalView(BaseAppView):'
232
232
233 journal = self._get_journal_data(following, c.search_term)
233 journal = self._get_journal_data(following, c.search_term)
234
234
235 def url_generator(**kw):
235 def url_generator(page_num):
236 query_params = {
236 query_params = {
237 'page': page_num,
237 'filter': c.search_term
238 'filter': c.search_term
238 }
239 }
239 query_params.update(kw)
240 return self.request.current_route_path(_query=query_params)
240 return self.request.current_route_path(_query=query_params)
241
241
242 c.journal_pager = Page(
242 c.journal_pager = SqlPage(
243 journal, page=p, items_per_page=20, url=url_generator)
243 journal, page=p, items_per_page=20, url_maker=url_generator)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245
245
246 c.journal_data = render(
246 c.journal_data = render(
@@ -333,13 +333,14 b' class JournalView(BaseAppView):'
333
333
334 journal = self._get_journal_data(c.following, c.search_term)
334 journal = self._get_journal_data(c.following, c.search_term)
335
335
336 def url_generator(**kw):
336 def url_generator(page_num):
337 query_params = {}
337 query_params = {
338 query_params.update(kw)
338 'page': page_num
339 }
339 return self.request.current_route_path(_query=query_params)
340 return self.request.current_route_path(_query=query_params)
340
341
341 c.journal_pager = Page(
342 c.journal_pager = SqlPage(
342 journal, page=p, items_per_page=20, url=url_generator)
343 journal, page=p, items_per_page=20, url_maker=url_generator)
343 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344
345
345 c.journal_data = render(
346 c.journal_data = render(
@@ -28,7 +28,7 b' from rhodecode.apps._base import BaseApp'
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29
29
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import SqlPage
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import Notification
33 from rhodecode.model.db import Notification
34 from rhodecode.model.notification import NotificationModel
34 from rhodecode.model.notification import NotificationModel
@@ -74,13 +74,16 b' class MyAccountNotificationsView(BaseApp'
74
74
75 p = safe_int(self.request.GET.get('page', 1), 1)
75 p = safe_int(self.request.GET.get('page', 1), 1)
76
76
77 def url_generator(**kw):
77 def url_generator(page_num):
78 query_params = {
79 'page': page_num
80 }
78 _query = self.request.GET.mixed()
81 _query = self.request.GET.mixed()
79 _query.update(kw)
82 query_params.update(_query)
80 return self.request.current_route_path(_query=_query)
83 return self.request.current_route_path(_query=query_params)
81
84
82 c.notifications = Page(notifications, page=p, items_per_page=10,
85 c.notifications = SqlPage(notifications, page=p, items_per_page=10,
83 url=url_generator)
86 url_maker=url_generator)
84
87
85 c.unread_type = 'unread'
88 c.unread_type = 'unread'
86 c.all_type = 'all'
89 c.all_type = 'all'
@@ -22,6 +22,7 b' import logging'
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib.helpers import SqlPage
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.lib.utils2 import safe_int
@@ -33,8 +34,6 b' log = logging.getLogger(__name__)'
33 class AuditLogsView(RepoAppView):
34 class AuditLogsView(RepoAppView):
34 def load_default_context(self):
35 def load_default_context(self):
35 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
36
37
38 return c
37 return c
39
38
40 @LoginRequired()
39 @LoginRequired()
@@ -54,12 +53,15 b' class AuditLogsView(RepoAppView):'
54 filter_term = self.request.GET.get('filter')
53 filter_term = self.request.GET.get('filter')
55 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
54 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
56
55
57 def url_generator(**kw):
56 def url_generator(page_num):
57 query_params = {
58 'page': page_num
59 }
58 if filter_term:
60 if filter_term:
59 kw['filter'] = filter_term
61 query_params['filter'] = filter_term
60 return self.request.current_route_path(_query=kw)
62 return self.request.current_route_path(_query=query_params)
61
63
62 c.audit_logs = h.Page(
64 c.audit_logs = SqlPage(
63 user_log, page=p, items_per_page=10, url=url_generator)
65 user_log, page=p, items_per_page=10, url_maker=url_generator)
64 c.filter_term = filter_term
66 c.filter_term = filter_term
65 return self._get_template_context(c)
67 return self._get_template_context(c)
@@ -121,9 +121,16 b' class RepoChangelogView(RepoAppView):'
121 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
122 dynamic=False, f_path=None, commit_id=None):
122 dynamic=False, f_path=None, commit_id=None):
123
123
124 def url_generator(**kw):
124 def url_generator(page_num):
125 query_params = {}
125 query_params = {
126 query_params.update(kw)
126 'page': page_num
127 }
128
129 if branch_name:
130 query_params.update({
131 'branch': branch_name
132 })
133
127 if f_path:
134 if f_path:
128 # changelog for file
135 # changelog for file
129 return h.route_path(
136 return h.route_path(
@@ -139,8 +146,7 b' class RepoChangelogView(RepoAppView):'
139 c.total_cs = len(collection)
146 c.total_cs = len(collection)
140 c.showing_commits = min(chunk_size, c.total_cs)
147 c.showing_commits = min(chunk_size, c.total_cs)
141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 items_per_page=chunk_size, branch=branch_name,
149 items_per_page=chunk_size, url_maker=url_generator)
143 url=url_generator)
144
150
145 c.next_page = c.pagination.next_page
151 c.next_page = c.pagination.next_page
146 c.prev_page = c.pagination.previous_page
152 c.prev_page = c.pagination.previous_page
@@ -56,11 +56,11 b' class RepoSummaryView(RepoAppView):'
56 p = safe_int(self.request.GET.get('page'), 1)
56 p = safe_int(self.request.GET.get('page'), 1)
57 size = safe_int(self.request.GET.get('size'), 10)
57 size = safe_int(self.request.GET.get('size'), 10)
58
58
59 def url_generator(**kw):
59 def url_generator(page_num):
60 query_params = {
60 query_params = {
61 'page': page_num,
61 'size': size
62 'size': size
62 }
63 }
63 query_params.update(kw)
64 return h.route_path(
64 return h.route_path(
65 'repo_summary_commits',
65 'repo_summary_commits',
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
@@ -73,7 +73,7 b' class RepoSummaryView(RepoAppView):'
73 collection = self.rhodecode_vcs_repo
73 collection = self.rhodecode_vcs_repo
74
74
75 c.repo_commits = h.RepoPage(
75 c.repo_commits = h.RepoPage(
76 collection, page=p, items_per_page=size, url=url_generator)
76 collection, page=p, items_per_page=size, url_maker=url_generator)
77 page_ids = [x.raw_id for x in c.repo_commits]
77 page_ids = [x.raw_id for x in c.repo_commits]
78 c.comments = self.db_repo.get_comments(page_ids)
78 c.comments = self.db_repo.get_comments(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
@@ -59,11 +59,19 b' def perform_search(request, tmpl_context'
59 except validation_schema.Invalid as e:
59 except validation_schema.Invalid as e:
60 errors = e.children
60 errors = e.children
61
61
62 def url_generator(**kw):
62 def url_generator(page_num):
63 q = urllib.quote(safe_str(search_query))
63 q = urllib.quote(safe_str(search_query))
64 return update_params(
64
65 "?q=%s&type=%s&max_lines=%s&sort=%s" % (
65 query_params = {
66 q, safe_str(search_type), search_max_lines, search_sort), **kw)
66 'page': page_num,
67 'q': q,
68 'type': safe_str(search_type),
69 'max_lines': search_max_lines,
70 'sort': search_sort
71 }
72
73 return '?' + urllib.urlencode(query_params)
74
67
75
68 c = tmpl_context
76 c = tmpl_context
69 search_query = search_params.get('search_query')
77 search_query = search_params.get('search_query')
@@ -82,7 +90,7 b' def perform_search(request, tmpl_context'
82 formatted_results = Page(
90 formatted_results = Page(
83 search_result['results'], page=requested_page,
91 search_result['results'], page=requested_page,
84 item_count=search_result['count'],
92 item_count=search_result['count'],
85 items_per_page=page_limit, url=url_generator)
93 items_per_page=page_limit, url_maker=url_generator)
86 finally:
94 finally:
87 searcher.cleanup()
95 searcher.cleanup()
88
96
@@ -100,7 +108,6 b' def perform_search(request, tmpl_context'
100 c.perm_user = c.auth_user
108 c.perm_user = c.auth_user
101 c.repo_name = repo_name
109 c.repo_name = repo_name
102 c.repo_group_name = repo_group_name
110 c.repo_group_name = repo_group_name
103 c.url_generator = url_generator
104 c.errors = errors
111 c.errors = errors
105 c.formatted_results = formatted_results
112 c.formatted_results = formatted_results
106 c.runtime = execution_time
113 c.runtime = execution_time
@@ -27,11 +27,11 b' from pyramid.httpexceptions import HTTPF'
27 from rhodecode.integrations import integration_type_registry
27 from rhodecode.integrations import integration_type_registry
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib.paginate import PageURL
31 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
31 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
32 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.helpers import Page
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
@@ -219,11 +219,16 b' class IntegrationSettingsViewBase(BaseAp'
219 key=lambda x: getattr(x[1], sort_field),
219 key=lambda x: getattr(x[1], sort_field),
220 reverse=(sort_dir == 'desc'))
220 reverse=(sort_dir == 'desc'))
221
221
222 page_url = PageURL(self.request.path, self.request.GET)
222 def url_generator(page_num):
223 query_params = {
224 'page': page_num
225 }
226 return self.request.current_route_path(_query=query_params)
227
223 page = safe_int(self.request.GET.get('page', 1), 1)
228 page = safe_int(self.request.GET.get('page', 1), 1)
224
229
225 integrations = h.Page(
230 integrations = Page(
226 integrations, page=page, items_per_page=10, url=page_url)
231 integrations, page=page, items_per_page=10, url_maker=url_generator)
227
232
228 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
233 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
229
234
@@ -75,7 +75,7 b' from webhelpers2.html.tags import ('
75 from webhelpers2.number import format_byte_size
75 from webhelpers2.number import format_byte_size
76
76
77 from rhodecode.lib.action_parser import action_parser
77 from rhodecode.lib.action_parser import action_parser
78 from rhodecode.lib.paginate import Page
78 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
79 from rhodecode.lib.ext_json import json
79 from rhodecode.lib.ext_json import json
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 from rhodecode.lib.utils2 import (
81 from rhodecode.lib.utils2 import (
@@ -1309,94 +1309,6 b' def gravatar_url(email_address, size=30,'
1309 return initials_gravatar(email_address, '', '', size=size)
1309 return initials_gravatar(email_address, '', '', size=size)
1310
1310
1311
1311
1312
1313
1314 #==============================================================================
1315 # REPO PAGER, PAGER FOR REPOSITORY
1316 #==============================================================================
1317 class RepoPage(Page):
1318
1319 def __init__(self, collection, page=1, items_per_page=20,
1320 item_count=None, url=None, **kwargs):
1321
1322 """Create a "RepoPage" instance. special pager for paging
1323 repository
1324 """
1325 self._url_generator = url
1326
1327 # Safe the kwargs class-wide so they can be used in the pager() method
1328 self.kwargs = kwargs
1329
1330 # Save a reference to the collection
1331 self.original_collection = collection
1332
1333 self.collection = collection
1334
1335 # The self.page is the number of the current page.
1336 # The first page has the number 1!
1337 try:
1338 self.page = int(page) # make it int() if we get it as a string
1339 except (ValueError, TypeError):
1340 self.page = 1
1341
1342 self.items_per_page = items_per_page
1343
1344 # Unless the user tells us how many items the collections has
1345 # we calculate that ourselves.
1346 if item_count is not None:
1347 self.item_count = item_count
1348 else:
1349 self.item_count = len(self.collection)
1350
1351 # Compute the number of the first and last available page
1352 if self.item_count > 0:
1353 self.first_page = 1
1354 self.page_count = int(math.ceil(float(self.item_count) /
1355 self.items_per_page))
1356 self.last_page = self.first_page + self.page_count - 1
1357
1358 # Make sure that the requested page number is the range of
1359 # valid pages
1360 if self.page > self.last_page:
1361 self.page = self.last_page
1362 elif self.page < self.first_page:
1363 self.page = self.first_page
1364
1365 # Note: the number of items on this page can be less than
1366 # items_per_page if the last page is not full
1367 self.first_item = max(0, (self.item_count) - (self.page *
1368 items_per_page))
1369 self.last_item = ((self.item_count - 1) - items_per_page *
1370 (self.page - 1))
1371
1372 self.items = list(self.collection[self.first_item:self.last_item + 1])
1373
1374 # Links to previous and next page
1375 if self.page > self.first_page:
1376 self.previous_page = self.page - 1
1377 else:
1378 self.previous_page = None
1379
1380 if self.page < self.last_page:
1381 self.next_page = self.page + 1
1382 else:
1383 self.next_page = None
1384
1385 # No items available
1386 else:
1387 self.first_page = None
1388 self.page_count = 0
1389 self.last_page = None
1390 self.first_item = None
1391 self.last_item = None
1392 self.previous_page = None
1393 self.next_page = None
1394 self.items = []
1395
1396 # This is a subclass of the 'list' type. Initialise the list now.
1397 list.__init__(self, reversed(self.items))
1398
1399
1400 def breadcrumb_repo_link(repo):
1312 def breadcrumb_repo_link(repo):
1401 """
1313 """
1402 Makes a breadcrumbs path link to repo
1314 Makes a breadcrumbs path link to repo
This diff has been collapsed as it changes many lines, (1143 lines changed) Show them Hide them
@@ -1,5 +1,882 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (c) 2007-2012 Christoph Haas <email@christoph-haas.de>
4 # NOTE: MIT license based code, backported and edited by RhodeCode GmbH
5
6 """
7 paginate: helps split up large collections into individual pages
8 ================================================================
9
10 What is pagination?
11 ---------------------
12
13 This module helps split large lists of items into pages. The user is shown one page at a time and
14 can navigate to other pages. Imagine you are offering a company phonebook and let the user search
15 the entries. The entire search result may contains 23 entries but you want to display no more than
16 10 entries at once. The first page contains entries 1-10, the second 11-20 and the third 21-23.
17 Each "Page" instance represents the items of one of these three pages.
18
19 See the documentation of the "Page" class for more information.
20
21 How do I use it?
22 ------------------
23
24 A page of items is represented by the *Page* object. A *Page* gets initialized with these arguments:
25
26 - The collection of items to pick a range from. Usually just a list.
27 - The page number you want to display. Default is 1: the first page.
28
29 Now we can make up a collection and create a Page instance of it::
30
31 # Create a sample collection of 1000 items
32 >> my_collection = range(1000)
33
34 # Create a Page object for the 3rd page (20 items per page is the default)
35 >> my_page = Page(my_collection, page=3)
36
37 # The page object can be printed as a string to get its details
38 >> str(my_page)
39 Page:
40 Collection type: <type 'range'>
41 Current page: 3
42 First item: 41
43 Last item: 60
44 First page: 1
45 Last page: 50
46 Previous page: 2
47 Next page: 4
48 Items per page: 20
49 Number of items: 1000
50 Number of pages: 50
51
52 # Print a list of items on the current page
53 >> my_page.items
54 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
55
56 # The *Page* object can be used as an iterator:
57 >> for my_item in my_page: print(my_item)
58 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
59
60 # The .pager() method returns an HTML fragment with links to surrounding pages.
61 >> my_page.pager(url="http://example.org/foo/page=$page")
62
63 <a href="http://example.org/foo/page=1">1</a>
64 <a href="http://example.org/foo/page=2">2</a>
65 3
66 <a href="http://example.org/foo/page=4">4</a>
67 <a href="http://example.org/foo/page=5">5</a>
68 ..
69 <a href="http://example.org/foo/page=50">50</a>'
70
71 # Without the HTML it would just look like:
72 # 1 2 [3] 4 5 .. 50
73
74 # The pager can be customized:
75 >> my_page.pager('$link_previous ~3~ $link_next (Page $page of $page_count)',
76 url="http://example.org/foo/page=$page")
77
78 <a href="http://example.org/foo/page=2">&lt;</a>
79 <a href="http://example.org/foo/page=1">1</a>
80 <a href="http://example.org/foo/page=2">2</a>
81 3
82 <a href="http://example.org/foo/page=4">4</a>
83 <a href="http://example.org/foo/page=5">5</a>
84 <a href="http://example.org/foo/page=6">6</a>
85 ..
86 <a href="http://example.org/foo/page=50">50</a>
87 <a href="http://example.org/foo/page=4">&gt;</a>
88 (Page 3 of 50)
89
90 # Without the HTML it would just look like:
91 # 1 2 [3] 4 5 6 .. 50 > (Page 3 of 50)
92
93 # The url argument to the pager method can be omitted when an url_maker is
94 # given during instantiation:
95 >> my_page = Page(my_collection, page=3,
96 url_maker=lambda p: "http://example.org/%s" % p)
97 >> page.pager()
98
99 There are some interesting parameters that customize the Page's behavior. See the documentation on
100 ``Page`` and ``Page.pager()``.
101
102
103 Notes
104 -------
105
106 Page numbers and item numbers start at 1. This concept has been used because users expect that the
107 first page has number 1 and the first item on a page also has number 1. So if you want to use the
108 page's items by their index number please note that you have to subtract 1.
109 """
110
111 import re
112 import sys
113 from string import Template
114 from webhelpers2.html import literal
115
116 # are we running at least python 3.x ?
117 PY3 = sys.version_info[0] >= 3
118
119 if PY3:
120 unicode = str
121
122
123 def make_html_tag(tag, text=None, **params):
124 """Create an HTML tag string.
125
126 tag
127 The HTML tag to use (e.g. 'a', 'span' or 'div')
128
129 text
130 The text to enclose between opening and closing tag. If no text is specified then only
131 the opening tag is returned.
132
133 Example::
134 make_html_tag('a', text="Hello", href="/another/page")
135 -> <a href="/another/page">Hello</a>
136
137 To use reserved Python keywords like "class" as a parameter prepend it with
138 an underscore. Instead of "class='green'" use "_class='green'".
139
140 Warning: Quotes and apostrophes are not escaped."""
141 params_string = ""
142
143 # Parameters are passed. Turn the dict into a string like "a=1 b=2 c=3" string.
144 for key, value in sorted(params.items()):
145 # Strip off a leading underscore from the attribute's key to allow attributes like '_class'
146 # to be used as a CSS class specification instead of the reserved Python keyword 'class'.
147 key = key.lstrip("_")
148
149 params_string += u' {0}="{1}"'.format(key, value)
150
151 # Create the tag string
152 tag_string = u"<{0}{1}>".format(tag, params_string)
153
154 # Add text and closing tag if required.
155 if text:
156 tag_string += u"{0}</{1}>".format(text, tag)
157
158 return tag_string
159
160
161 # Since the items on a page are mainly a list we subclass the "list" type
162 class _Page(list):
163 """A list/iterator representing the items on one page of a larger collection.
164
165 An instance of the "Page" class is created from a _collection_ which is any
166 list-like object that allows random access to its elements.
167
168 The instance works as an iterator running from the first item to the last item on the given
169 page. The Page.pager() method creates a link list allowing the user to go to other pages.
170
171 A "Page" does not only carry the items on a certain page. It gives you additional information
172 about the page in these "Page" object attributes:
173
174 item_count
175 Number of items in the collection
176
177 **WARNING:** Unless you pass in an item_count, a count will be
178 performed on the collection every time a Page instance is created.
179
180 page
181 Number of the current page
182
183 items_per_page
184 Maximal number of items displayed on a page
185
186 first_page
187 Number of the first page - usually 1 :)
188
189 last_page
190 Number of the last page
191
192 previous_page
193 Number of the previous page. If this is the first page it returns None.
194
195 next_page
196 Number of the next page. If this is the last page it returns None.
197
198 page_count
199 Number of pages
200
201 items
202 Sequence/iterator of items on the current page
203
204 first_item
205 Index of first item on the current page - starts with 1
206
207 last_item
208 Index of last item on the current page
209 """
210
211 def __init__(
212 self,
213 collection,
214 page=1,
215 items_per_page=20,
216 item_count=None,
217 wrapper_class=None,
218 url_maker=None,
219 bar_size=10,
220 **kwargs
221 ):
222 """Create a "Page" instance.
223
224 Parameters:
225
226 collection
227 Sequence representing the collection of items to page through.
228
229 page
230 The requested page number - starts with 1. Default: 1.
231
232 items_per_page
233 The maximal number of items to be displayed per page.
234 Default: 20.
235
236 item_count (optional)
237 The total number of items in the collection - if known.
238 If this parameter is not given then the paginator will count
239 the number of elements in the collection every time a "Page"
240 is created. Giving this parameter will speed up things. In a busy
241 real-life application you may want to cache the number of items.
242
243 url_maker (optional)
244 Callback to generate the URL of other pages, given its numbers.
245 Must accept one int parameter and return a URI string.
246
247 bar_size
248 maximum size of rendered pages numbers within radius
249
250 """
251 if collection is not None:
252 if wrapper_class is None:
253 # Default case. The collection is already a list-type object.
254 self.collection = collection
255 else:
256 # Special case. A custom wrapper class is used to access elements of the collection.
257 self.collection = wrapper_class(collection)
258 else:
259 self.collection = []
260
261 self.collection_type = type(collection)
262
263 if url_maker is not None:
264 self.url_maker = url_maker
265 else:
266 self.url_maker = self._default_url_maker
267 self.bar_size = bar_size
268 # Assign kwargs to self
269 self.kwargs = kwargs
270
271 # The self.page is the number of the current page.
272 # The first page has the number 1!
273 try:
274 self.page = int(page) # make it int() if we get it as a string
275 except (ValueError, TypeError):
276 self.page = 1
277 # normally page should be always at least 1 but the original maintainer
278 # decided that for empty collection and empty page it can be...0? (based on tests)
279 # preserving behavior for BW compat
280 if self.page < 1:
281 self.page = 1
282
283 self.items_per_page = items_per_page
284
285 # We subclassed "list" so we need to call its init() method
286 # and fill the new list with the items to be displayed on the page.
287 # We use list() so that the items on the current page are retrieved
288 # only once. In an SQL context that could otherwise lead to running the
289 # same SQL query every time items would be accessed.
290 # We do this here, prior to calling len() on the collection so that a
291 # wrapper class can execute a query with the knowledge of what the
292 # slice will be (for efficiency) and, in the same query, ask for the
293 # total number of items and only execute one query.
294 try:
295 first = (self.page - 1) * items_per_page
296 last = first + items_per_page
297 self.items = list(self.collection[first:last])
298 except TypeError:
299 raise TypeError(
300 "Your collection of type {} cannot be handled "
301 "by paginate.".format(type(self.collection))
302 )
303
304 # Unless the user tells us how many items the collections has
305 # we calculate that ourselves.
306 if item_count is not None:
307 self.item_count = item_count
308 else:
309 self.item_count = len(self.collection)
310
311 # Compute the number of the first and last available page
312 if self.item_count > 0:
313 self.first_page = 1
314 self.page_count = ((self.item_count - 1) // self.items_per_page) + 1
315 self.last_page = self.first_page + self.page_count - 1
316
317 # Make sure that the requested page number is the range of valid pages
318 if self.page > self.last_page:
319 self.page = self.last_page
320 elif self.page < self.first_page:
321 self.page = self.first_page
322
323 # Note: the number of items on this page can be less than
324 # items_per_page if the last page is not full
325 self.first_item = (self.page - 1) * items_per_page + 1
326 self.last_item = min(self.first_item + items_per_page - 1, self.item_count)
327
328 # Links to previous and next page
329 if self.page > self.first_page:
330 self.previous_page = self.page - 1
331 else:
332 self.previous_page = None
333
334 if self.page < self.last_page:
335 self.next_page = self.page + 1
336 else:
337 self.next_page = None
338
339 # No items available
340 else:
341 self.first_page = None
342 self.page_count = 0
343 self.last_page = None
344 self.first_item = None
345 self.last_item = None
346 self.previous_page = None
347 self.next_page = None
348 self.items = []
349
350 # This is a subclass of the 'list' type. Initialise the list now.
351 list.__init__(self, self.items)
352
353 def __str__(self):
354 return (
355 "Page:\n"
356 "Collection type: {0.collection_type}\n"
357 "Current page: {0.page}\n"
358 "First item: {0.first_item}\n"
359 "Last item: {0.last_item}\n"
360 "First page: {0.first_page}\n"
361 "Last page: {0.last_page}\n"
362 "Previous page: {0.previous_page}\n"
363 "Next page: {0.next_page}\n"
364 "Items per page: {0.items_per_page}\n"
365 "Total number of items: {0.item_count}\n"
366 "Number of pages: {0.page_count}\n"
367 ).format(self)
368
369 def __repr__(self):
370 return "<paginate.Page: Page {0}/{1}>".format(self.page, self.page_count)
371
372 def pager(
373 self,
374 tmpl_format="~2~",
375 url=None,
376 show_if_single_page=False,
377 separator=" ",
378 symbol_first="&lt;&lt;",
379 symbol_last="&gt;&gt;",
380 symbol_previous="&lt;",
381 symbol_next="&gt;",
382 link_attr=None,
383 curpage_attr=None,
384 dotdot_attr=None,
385 link_tag=None,
386 ):
387 """
388 Return string with links to other pages (e.g. '1 .. 5 6 7 [8] 9 10 11 .. 50').
389
390 tmpl_format:
391 Format string that defines how the pager is rendered. The string
392 can contain the following $-tokens that are substituted by the
393 string.Template module:
394
395 - $first_page: number of first reachable page
396 - $last_page: number of last reachable page
397 - $page: number of currently selected page
398 - $page_count: number of reachable pages
399 - $items_per_page: maximal number of items per page
400 - $first_item: index of first item on the current page
401 - $last_item: index of last item on the current page
402 - $item_count: total number of items
403 - $link_first: link to first page (unless this is first page)
404 - $link_last: link to last page (unless this is last page)
405 - $link_previous: link to previous page (unless this is first page)
406 - $link_next: link to next page (unless this is last page)
407
408 To render a range of pages the token '~3~' can be used. The
409 number sets the radius of pages around the current page.
410 Example for a range with radius 3:
411
412 '1 .. 5 6 7 [8] 9 10 11 .. 50'
413
414 Default: '~2~'
415
416 url
417 The URL that page links will point to. Make sure it contains the string
418 $page which will be replaced by the actual page number.
419 Must be given unless a url_maker is specified to __init__, in which
420 case this parameter is ignored.
421
422 symbol_first
423 String to be displayed as the text for the $link_first link above.
424
425 Default: '&lt;&lt;' (<<)
426
427 symbol_last
428 String to be displayed as the text for the $link_last link above.
429
430 Default: '&gt;&gt;' (>>)
431
432 symbol_previous
433 String to be displayed as the text for the $link_previous link above.
434
435 Default: '&lt;' (<)
436
437 symbol_next
438 String to be displayed as the text for the $link_next link above.
439
440 Default: '&gt;' (>)
441
442 separator:
443 String that is used to separate page links/numbers in the above range of pages.
444
445 Default: ' '
446
447 show_if_single_page:
448 if True the navigator will be shown even if there is only one page.
449
450 Default: False
451
452 link_attr (optional)
453 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can
454 be used to define a CSS style or class to customize the look of links.
455
456 Example: { 'style':'border: 1px solid green' }
457 Example: { 'class':'pager_link' }
458
459 curpage_attr (optional)
460 A dictionary of attributes that get added to the current page number in the pager (which
461 is obviously not a link). If this dictionary is not empty then the elements will be
462 wrapped in a SPAN tag with the given attributes.
463
464 Example: { 'style':'border: 3px solid blue' }
465 Example: { 'class':'pager_curpage' }
466
467 dotdot_attr (optional)
468 A dictionary of attributes that get added to the '..' string in the pager (which is
469 obviously not a link). If this dictionary is not empty then the elements will be wrapped
470 in a SPAN tag with the given attributes.
471
472 Example: { 'style':'color: #808080' }
473 Example: { 'class':'pager_dotdot' }
474
475 link_tag (optional)
476 A callable that accepts single argument `page` (page link information)
477 and generates string with html that represents the link for specific page.
478 Page objects are supplied from `link_map()` so the keys are the same.
479
480
481 """
482 link_attr = link_attr or {}
483 curpage_attr = curpage_attr or {}
484 dotdot_attr = dotdot_attr or {}
485 self.curpage_attr = curpage_attr
486 self.separator = separator
487 self.link_attr = link_attr
488 self.dotdot_attr = dotdot_attr
489 self.url = url
490 self.link_tag = link_tag or self.default_link_tag
491
492 # Don't show navigator if there is no more than one page
493 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
494 return ""
495
496 regex_res = re.search(r"~(\d+)~", tmpl_format)
497 if regex_res:
498 radius = regex_res.group(1)
499 else:
500 radius = 2
501
502 self.radius = int(radius)
503 link_map = self.link_map(
504 tmpl_format=tmpl_format,
505 url=url,
506 show_if_single_page=show_if_single_page,
507 separator=separator,
508 symbol_first=symbol_first,
509 symbol_last=symbol_last,
510 symbol_previous=symbol_previous,
511 symbol_next=symbol_next,
512 link_attr=link_attr,
513 curpage_attr=curpage_attr,
514 dotdot_attr=dotdot_attr,
515 link_tag=link_tag,
516 )
517 links_markup = self._range(link_map, self.radius)
518
519 # Replace ~...~ in token tmpl_format by range of pages
520 result = re.sub(r"~(\d+)~", links_markup, tmpl_format)
521
522 link_first = (
523 self.page > self.first_page and self.link_tag(link_map["first_page"]) or ""
524 )
525 link_last = (
526 self.page < self.last_page and self.link_tag(link_map["last_page"]) or ""
527 )
528 link_previous = (
529 self.previous_page and self.link_tag(link_map["previous_page"]) or ""
530 )
531 link_next = self.next_page and self.link_tag(link_map["next_page"]) or ""
532 # Interpolate '$' variables
533 result = Template(result).safe_substitute(
534 {
535 "first_page": self.first_page,
536 "last_page": self.last_page,
537 "page": self.page,
538 "page_count": self.page_count,
539 "items_per_page": self.items_per_page,
540 "first_item": self.first_item,
541 "last_item": self.last_item,
542 "item_count": self.item_count,
543 "link_first": link_first,
544 "link_last": link_last,
545 "link_previous": link_previous,
546 "link_next": link_next,
547 }
548 )
549
550 return result
551
552 def _get_edges(self, cur_page, max_page, items):
553 cur_page = int(cur_page)
554 edge = (items / 2) + 1
555 if cur_page <= edge:
556 radius = max(items / 2, items - cur_page)
557 elif (max_page - cur_page) < edge:
558 radius = (items - 1) - (max_page - cur_page)
559 else:
560 radius = (items / 2) - 1
561
562 left = max(1, (cur_page - radius))
563 right = min(max_page, cur_page + radius)
564 return left, right
565
566 def link_map(
567 self,
568 tmpl_format="~2~",
569 url=None,
570 show_if_single_page=False,
571 separator=" ",
572 symbol_first="&lt;&lt;",
573 symbol_last="&gt;&gt;",
574 symbol_previous="&lt;",
575 symbol_next="&gt;",
576 link_attr=None,
577 curpage_attr=None,
578 dotdot_attr=None,
579 link_tag=None
580 ):
581 """ Return map with links to other pages if default pager() function is not suitable solution.
582 tmpl_format:
583 Format string that defines how the pager would be normally rendered rendered. Uses same arguments as pager()
584 method, but returns a simple dictionary in form of:
585 {'current_page': {'attrs': {},
586 'href': 'http://example.org/foo/page=1',
587 'value': 1},
588 'first_page': {'attrs': {},
589 'href': 'http://example.org/foo/page=1',
590 'type': 'first_page',
591 'value': 1},
592 'last_page': {'attrs': {},
593 'href': 'http://example.org/foo/page=8',
594 'type': 'last_page',
595 'value': 8},
596 'next_page': {'attrs': {}, 'href': 'HREF', 'type': 'next_page', 'value': 2},
597 'previous_page': None,
598 'range_pages': [{'attrs': {},
599 'href': 'http://example.org/foo/page=1',
600 'type': 'current_page',
601 'value': 1},
602 ....
603 {'attrs': {}, 'href': '', 'type': 'span', 'value': '..'}]}
604
605
606 The string can contain the following $-tokens that are substituted by the
607 string.Template module:
608
609 - $first_page: number of first reachable page
610 - $last_page: number of last reachable page
611 - $page: number of currently selected page
612 - $page_count: number of reachable pages
613 - $items_per_page: maximal number of items per page
614 - $first_item: index of first item on the current page
615 - $last_item: index of last item on the current page
616 - $item_count: total number of items
617 - $link_first: link to first page (unless this is first page)
618 - $link_last: link to last page (unless this is last page)
619 - $link_previous: link to previous page (unless this is first page)
620 - $link_next: link to next page (unless this is last page)
621
622 To render a range of pages the token '~3~' can be used. The
623 number sets the radius of pages around the current page.
624 Example for a range with radius 3:
625
626 '1 .. 5 6 7 [8] 9 10 11 .. 50'
627
628 Default: '~2~'
629
630 url
631 The URL that page links will point to. Make sure it contains the string
632 $page which will be replaced by the actual page number.
633 Must be given unless a url_maker is specified to __init__, in which
634 case this parameter is ignored.
635
636 symbol_first
637 String to be displayed as the text for the $link_first link above.
638
639 Default: '&lt;&lt;' (<<)
640
641 symbol_last
642 String to be displayed as the text for the $link_last link above.
643
644 Default: '&gt;&gt;' (>>)
645
646 symbol_previous
647 String to be displayed as the text for the $link_previous link above.
648
649 Default: '&lt;' (<)
650
651 symbol_next
652 String to be displayed as the text for the $link_next link above.
653
654 Default: '&gt;' (>)
655
656 separator:
657 String that is used to separate page links/numbers in the above range of pages.
658
659 Default: ' '
660
661 show_if_single_page:
662 if True the navigator will be shown even if there is only one page.
663
664 Default: False
665
666 link_attr (optional)
667 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can
668 be used to define a CSS style or class to customize the look of links.
669
670 Example: { 'style':'border: 1px solid green' }
671 Example: { 'class':'pager_link' }
672
673 curpage_attr (optional)
674 A dictionary of attributes that get added to the current page number in the pager (which
675 is obviously not a link). If this dictionary is not empty then the elements will be
676 wrapped in a SPAN tag with the given attributes.
677
678 Example: { 'style':'border: 3px solid blue' }
679 Example: { 'class':'pager_curpage' }
680
681 dotdot_attr (optional)
682 A dictionary of attributes that get added to the '..' string in the pager (which is
683 obviously not a link). If this dictionary is not empty then the elements will be wrapped
684 in a SPAN tag with the given attributes.
685
686 Example: { 'style':'color: #808080' }
687 Example: { 'class':'pager_dotdot' }
688 """
689 link_attr = link_attr or {}
690 curpage_attr = curpage_attr or {}
691 dotdot_attr = dotdot_attr or {}
692 self.curpage_attr = curpage_attr
693 self.separator = separator
694 self.link_attr = link_attr
695 self.dotdot_attr = dotdot_attr
696 self.url = url
697
698 regex_res = re.search(r"~(\d+)~", tmpl_format)
699 if regex_res:
700 radius = regex_res.group(1)
701 else:
702 radius = 2
703
704 self.radius = int(radius)
705
706 # Compute the first and last page number within the radius
707 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
708 # -> leftmost_page = 5
709 # -> rightmost_page = 9
710 leftmost_page, rightmost_page = self._get_edges(
711 self.page, self.last_page, (self.radius * 2) + 1)
712
713 nav_items = {
714 "first_page": None,
715 "last_page": None,
716 "previous_page": None,
717 "next_page": None,
718 "current_page": None,
719 "radius": self.radius,
720 "range_pages": [],
721 }
722
723 if leftmost_page is None or rightmost_page is None:
724 return nav_items
725
726 nav_items["first_page"] = {
727 "type": "first_page",
728 "value": unicode(symbol_first),
729 "attrs": self.link_attr,
730 "number": self.first_page,
731 "href": self.url_maker(self.first_page),
732 }
733
734 # Insert dots if there are pages between the first page
735 # and the currently displayed page range
736 if leftmost_page - self.first_page > 1:
737 # Wrap in a SPAN tag if dotdot_attr is set
738 nav_items["range_pages"].append(
739 {
740 "type": "span",
741 "value": "..",
742 "attrs": self.dotdot_attr,
743 "href": "",
744 "number": None,
745 }
746 )
747
748 for this_page in range(leftmost_page, rightmost_page + 1):
749 # Highlight the current page number and do not use a link
750 if this_page == self.page:
751 # Wrap in a SPAN tag if curpage_attr is set
752 nav_items["range_pages"].append(
753 {
754 "type": "current_page",
755 "value": unicode(this_page),
756 "number": this_page,
757 "attrs": self.curpage_attr,
758 "href": self.url_maker(this_page),
759 }
760 )
761 nav_items["current_page"] = {
762 "value": this_page,
763 "attrs": self.curpage_attr,
764 "type": "current_page",
765 "href": self.url_maker(this_page),
766 }
767 # Otherwise create just a link to that page
768 else:
769 nav_items["range_pages"].append(
770 {
771 "type": "page",
772 "value": unicode(this_page),
773 "number": this_page,
774 "attrs": self.link_attr,
775 "href": self.url_maker(this_page),
776 }
777 )
778
779 # Insert dots if there are pages between the displayed
780 # page numbers and the end of the page range
781 if self.last_page - rightmost_page > 1:
782 # Wrap in a SPAN tag if dotdot_attr is set
783 nav_items["range_pages"].append(
784 {
785 "type": "span",
786 "value": "..",
787 "attrs": self.dotdot_attr,
788 "href": "",
789 "number": None,
790 }
791 )
792
793 # Create a link to the very last page (unless we are on the last
794 # page or there would be no need to insert '..' spacers)
795 nav_items["last_page"] = {
796 "type": "last_page",
797 "value": unicode(symbol_last),
798 "attrs": self.link_attr,
799 "href": self.url_maker(self.last_page),
800 "number": self.last_page,
801 }
802
803 nav_items["previous_page"] = {
804 "type": "previous_page",
805 "value": unicode(symbol_previous),
806 "attrs": self.link_attr,
807 "number": self.previous_page or self.first_page,
808 "href": self.url_maker(self.previous_page or self.first_page),
809 }
810
811 nav_items["next_page"] = {
812 "type": "next_page",
813 "value": unicode(symbol_next),
814 "attrs": self.link_attr,
815 "number": self.next_page or self.last_page,
816 "href": self.url_maker(self.next_page or self.last_page),
817 }
818
819 return nav_items
820
821 def _range(self, link_map, radius):
822 """
823 Return range of linked pages to substitute placeholder in pattern
824 """
825 # Compute the first and last page number within the radius
826 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
827 # -> leftmost_page = 5
828 # -> rightmost_page = 9
829 leftmost_page, rightmost_page = self._get_edges(
830 self.page, self.last_page, (radius * 2) + 1)
831
832 nav_items = []
833 # Create a link to the first page (unless we are on the first page
834 # or there would be no need to insert '..' spacers)
835 if self.first_page and self.page != self.first_page and self.first_page < leftmost_page:
836 page = link_map["first_page"].copy()
837 page["value"] = unicode(page["number"])
838 nav_items.append(self.link_tag(page))
839
840 for item in link_map["range_pages"]:
841 nav_items.append(self.link_tag(item))
842
843 # Create a link to the very last page (unless we are on the last
844 # page or there would be no need to insert '..' spacers)
845 if self.last_page and self.page != self.last_page and rightmost_page < self.last_page:
846 page = link_map["last_page"].copy()
847 page["value"] = unicode(page["number"])
848 nav_items.append(self.link_tag(page))
849
850 return self.separator.join(nav_items)
851
852 def _default_url_maker(self, page_number):
853 if self.url is None:
854 raise Exception(
855 "You need to specify a 'url' parameter containing a '$page' placeholder."
856 )
857
858 if "$page" not in self.url:
859 raise Exception("The 'url' parameter must contain a '$page' placeholder.")
860
861 return self.url.replace("$page", unicode(page_number))
862
863 @staticmethod
864 def default_link_tag(item):
865 """
866 Create an A-HREF tag that points to another page.
867 """
868 text = item["value"]
869 target_url = item["href"]
870
871 if not item["href"] or item["type"] in ("span", "current_page"):
872 if item["attrs"]:
873 text = make_html_tag("span", **item["attrs"]) + text + "</span>"
874 return text
875
876 return make_html_tag("a", text=text, href=target_url, **item["attrs"])
877
878 # Below is RhodeCode custom code
879
3 # Copyright (C) 2010-2019 RhodeCode GmbH
880 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
881 #
5 # This program is free software: you can redistribute it and/or modify
882 # This program is free software: you can redistribute it and/or modify
@@ -17,149 +894,163 b''
17 # This program is dual-licensed. If you wish to learn more about the
894 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
895 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
896 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import re
897
898
899 PAGE_FORMAT = '$link_previous ~3~ $link_next'
900
901
902 class SqlalchemyOrmWrapper(object):
903 """Wrapper class to access elements of a collection."""
21
904
22 from webhelpers.paginate import Page as _Page
905 def __init__(self, pager, collection):
23 from webhelpers.paginate import PageURL
906 self.pager = pager
24 from webhelpers2.html import literal, HTML
907 self.collection = collection
908
909 def __getitem__(self, range):
910 # Return a range of objects of an sqlalchemy.orm.query.Query object
911 return self.collection[range]
912
913 def __len__(self):
914 # Count the number of objects in an sqlalchemy.orm.query.Query object
915 return self.collection.count()
25
916
26
917
27 class Page(_Page):
918 class CustomPager(_Page):
919
920 @staticmethod
921 def disabled_link_tag(item):
922 """
923 Create an A-HREF tag that is disabled
924 """
925 text = item['value']
926 attrs = item['attrs'].copy()
927 attrs['class'] = 'disabled ' + attrs['class']
928
929 return make_html_tag('a', text=text, **attrs)
930
931 def render(self):
932 # Don't show navigator if there is no more than one page
933 if self.page_count == 0:
934 return ""
935
936 self.link_tag = self.default_link_tag
937
938 link_map = self.link_map(
939 tmpl_format=PAGE_FORMAT, url=None,
940 show_if_single_page=False, separator=' ',
941 symbol_first='<<', symbol_last='>>',
942 symbol_previous='<', symbol_next='>',
943 link_attr={'class': 'pager_link'},
944 curpage_attr={'class': 'pager_curpage'},
945 dotdot_attr={'class': 'pager_dotdot'})
946
947 links_markup = self._range(link_map, self.radius)
948
949 link_first = (
950 self.page > self.first_page and self.link_tag(link_map['first_page']) or ''
951 )
952 link_last = (
953 self.page < self.last_page and self.link_tag(link_map['last_page']) or ''
954 )
955
956 link_previous = (
957 self.previous_page and self.link_tag(link_map['previous_page'])
958 or self.disabled_link_tag(link_map['previous_page'])
959 )
960 link_next = (
961 self.next_page and self.link_tag(link_map['next_page'])
962 or self.disabled_link_tag(link_map['next_page'])
963 )
964
965 # Interpolate '$' variables
966 # Replace ~...~ in token tmpl_format by range of pages
967 result = re.sub(r"~(\d+)~", links_markup, PAGE_FORMAT)
968 result = Template(result).safe_substitute(
969 {
970 "links": links_markup,
971 "first_page": self.first_page,
972 "last_page": self.last_page,
973 "page": self.page,
974 "page_count": self.page_count,
975 "items_per_page": self.items_per_page,
976 "first_item": self.first_item,
977 "last_item": self.last_item,
978 "item_count": self.item_count,
979 "link_first": link_first,
980 "link_last": link_last,
981 "link_previous": link_previous,
982 "link_next": link_next,
983 }
984 )
985
986 return literal(result)
987
988
989 class Page(CustomPager):
28 """
990 """
29 Custom pager to match rendering style with paginator
991 Custom pager to match rendering style with paginator
30 """
992 """
31
993
32 def _get_pos(self, cur_page, max_page, items):
994 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
33 edge = (items / 2) + 1
995 url_maker=None, **kwargs):
34 if (cur_page <= edge):
996 """
35 radius = max(items / 2, items - cur_page)
997 Special type of pager. We intercept collection to wrap it in our custom
36 elif (max_page - cur_page) < edge:
998 logic instead of using wrapper_class
37 radius = (items - 1) - (max_page - cur_page)
38 else:
39 radius = items / 2
40
41 left = max(1, (cur_page - (radius)))
42 right = min(max_page, cur_page + (radius))
43 return left, cur_page, right
44
45 def _range(self, regexp_match):
46 """
999 """
47 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
48
49 Arguments:
50
1000
51 regexp_match
1001 super(Page, self).__init__(collection=collection, page=page,
52 A "re" (regular expressions) match object containing the
1002 items_per_page=items_per_page, item_count=item_count,
53 radius of linked pages around the current page in
1003 wrapper_class=None, url_maker=url_maker, **kwargs)
54 regexp_match.group(1) as a string
55
1004
56 This function is supposed to be called as a callable in
57 re.sub.
58
59 """
60 radius = int(regexp_match.group(1))
61
1005
62 # Compute the first and last page number within the radius
1006 class SqlPage(CustomPager):
63 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1007 """
64 # -> leftmost_page = 5
1008 Custom pager to match rendering style with paginator
65 # -> rightmost_page = 9
1009 """
66 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
67 self.last_page,
68 (radius * 2) + 1)
69 nav_items = []
70
71 # Create a link to the first page (unless we are on the first page
72 # or there would be no need to insert '..' spacers)
73 if self.page != self.first_page and self.first_page < leftmost_page:
74 nav_items.append(self._pagerlink(self.first_page, self.first_page))
75
1010
76 # Insert dots if there are pages between the first page
1011 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
77 # and the currently displayed page range
1012 url_maker=None, **kwargs):
78 if leftmost_page - self.first_page > 1:
1013 """
79 # Wrap in a SPAN tag if nolink_attr is set
1014 Special type of pager. We intercept collection to wrap it in our custom
80 text = '..'
1015 logic instead of using wrapper_class
81 if self.dotdot_attr:
1016 """
82 text = HTML.span(c=text, **self.dotdot_attr)
1017 collection = SqlalchemyOrmWrapper(self, collection)
83 nav_items.append(text)
84
1018
85 for thispage in xrange(leftmost_page, rightmost_page + 1):
1019 super(SqlPage, self).__init__(collection=collection, page=page,
86 # Hilight the current page number and do not use a link
1020 items_per_page=items_per_page, item_count=item_count,
87 if thispage == self.page:
1021 wrapper_class=None, url_maker=url_maker, **kwargs)
88 text = '%s' % (thispage,)
1022
89 # Wrap in a SPAN tag if nolink_attr is set
90 if self.curpage_attr:
91 text = HTML.span(c=text, **self.curpage_attr)
92 nav_items.append(text)
93 # Otherwise create just a link to that page
94 else:
95 text = '%s' % (thispage,)
96 nav_items.append(self._pagerlink(thispage, text))
97
1023
98 # Insert dots if there are pages between the displayed
1024 class RepoCommitsWrapper(object):
99 # page numbers and the end of the page range
1025 """Wrapper class to access elements of a collection."""
100 if self.last_page - rightmost_page > 1:
101 text = '..'
102 # Wrap in a SPAN tag if nolink_attr is set
103 if self.dotdot_attr:
104 text = HTML.span(c=text, **self.dotdot_attr)
105 nav_items.append(text)
106
1026
107 # Create a link to the very last page (unless we are on the last
1027 def __init__(self, pager, collection):
108 # page or there would be no need to insert '..' spacers)
1028 self.pager = pager
109 if self.page != self.last_page and rightmost_page < self.last_page:
1029 self.collection = collection
110 nav_items.append(self._pagerlink(self.last_page, self.last_page))
111
1030
112 ## prerender links
1031 def __getitem__(self, range):
113 #_page_link = url.current()
1032 cur_page = self.pager.page
114 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1033 items_per_page = self.pager.items_per_page
115 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1034 first_item = max(0, (len(self.collection) - (cur_page * items_per_page)))
116 return self.separator.join(nav_items)
1035 last_item = ((len(self.collection) - 1) - items_per_page * (cur_page - 1))
1036 return reversed(list(self.collection[first_item:last_item + 1]))
117
1037
118 def pager(self, format='~2~', page_param='page', partial_param='partial',
1038 def __len__(self):
119 show_if_single_page=False, separator=' ', onclick=None,
1039 return len(self.collection)
120 symbol_first='<<', symbol_last='>>',
121 symbol_previous='<', symbol_next='>',
122 link_attr={'class': 'pager_link', 'rel': 'prerender'},
123 curpage_attr={'class': 'pager_curpage'},
124 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
125
1040
126 self.curpage_attr = curpage_attr
127 self.separator = separator
128 self.pager_kwargs = kwargs
129 self.page_param = page_param
130 self.partial_param = partial_param
131 self.onclick = onclick
132 self.link_attr = link_attr
133 self.dotdot_attr = dotdot_attr
134
1041
135 # Don't show navigator if there is no more than one page
1042 class RepoPage(CustomPager):
136 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1043 """
137 return ''
1044 Create a "RepoPage" instance. special pager for paging repository
138
1045 """
139 from string import Template
140 # Replace ~...~ in token format by range of pages
141 result = re.sub(r'~(\d+)~', self._range, format)
142
1046
143 # Interpolate '%' variables
1047 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
144 result = Template(result).safe_substitute({
1048 url_maker=None, **kwargs):
145 'first_page': self.first_page,
1049 """
146 'last_page': self.last_page,
1050 Special type of pager. We intercept collection to wrap it in our custom
147 'page': self.page,
1051 logic instead of using wrapper_class
148 'page_count': self.page_count,
1052 """
149 'items_per_page': self.items_per_page,
1053 collection = RepoCommitsWrapper(self, collection)
150 'first_item': self.first_item,
1054 super(RepoPage, self).__init__(collection=collection, page=page,
151 'last_item': self.last_item,
1055 items_per_page=items_per_page, item_count=item_count,
152 'item_count': self.item_count,
1056 wrapper_class=None, url_maker=url_maker, **kwargs)
153 'link_first': self.page > self.first_page and \
154 self._pagerlink(self.first_page, symbol_first) or '',
155 'link_last': self.page < self.last_page and \
156 self._pagerlink(self.last_page, symbol_last) or '',
157 'link_previous': self.previous_page and \
158 self._pagerlink(self.previous_page, symbol_previous) \
159 or HTML.span(symbol_previous, class_="pg-previous disabled"),
160 'link_next': self.next_page and \
161 self._pagerlink(self.next_page, symbol_next) \
162 or HTML.span(symbol_next, class_="pg-next disabled")
163 })
164
165 return literal(result)
@@ -625,8 +625,9 b' ul#context-pages {'
625 top: 95px;
625 top: 95px;
626 }
626 }
627
627
628 .dataTables_paginate, .pagination-wh {
628 .dataTables_paginate,
629 text-align: left;
629 .pagination-wh {
630 text-align: center;
630 display: inline-block;
631 display: inline-block;
631 border-left: 1px solid @grey5;
632 border-left: 1px solid @grey5;
632 float: none;
633 float: none;
@@ -638,10 +639,15 b' ul#context-pages {'
638 display: inline-block;
639 display: inline-block;
639 padding: @menupadding/4 @menupadding;
640 padding: @menupadding/4 @menupadding;
640 border: 1px solid @grey5;
641 border: 1px solid @grey5;
641 border-left: 0;
642 margin-left: -1px;
642 color: @grey2;
643 color: @grey2;
643 cursor: pointer;
644 cursor: pointer;
644 float: left;
645 float: left;
646 font-weight: 600;
647 white-space: nowrap;
648 vertical-align: middle;
649 user-select: none;
650 min-width: 15px;
645
651
646 &:hover {
652 &:hover {
647 color: @rcdarkblue;
653 color: @rcdarkblue;
@@ -62,7 +62,7 b''
62 </table>
62 </table>
63
63
64 <div class="pagination-wh pagination-left">
64 <div class="pagination-wh pagination-left">
65 ${c.audit_logs.pager('$link_previous ~2~ $link_next')}
65 ${c.audit_logs.render()}
66 </div>
66 </div>
67 %else:
67 %else:
68 ${_('No actions yet')}
68 ${_('No actions yet')}
@@ -188,7 +188,7 b''
188 </table>
188 </table>
189 <div class="integrations-paginator">
189 <div class="integrations-paginator">
190 <div class="pagination-wh pagination-left">
190 <div class="pagination-wh pagination-left">
191 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
191 ${c.integrations_list.render()}
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
@@ -34,7 +34,7 b''
34
34
35 <div class="notification-paginator">
35 <div class="notification-paginator">
36 <div class="pagination-wh pagination-left">
36 <div class="pagination-wh pagination-left">
37 ${c.notifications.pager('$link_previous ~2~ $link_next')}
37 ${c.notifications.render()}
38 </div>
38 </div>
39 </div>
39 </div>
40
40
@@ -130,7 +130,7 b''
130 </div>
130 </div>
131 </div>
131 </div>
132 <div class="pagination-wh pagination-left">
132 <div class="pagination-wh pagination-left">
133 ${c.pagination.pager('$link_previous ~2~ $link_next')}
133 ${c.pagination.render()}
134 </div>
134 </div>
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
@@ -37,7 +37,9 b''
37 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
37 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
38 </li>
38 </li>
39 <li>
39 <li>
40 <span><a href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a></span>
40 <span>
41 <a href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="RSS Feed" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
42 </span>
41 </li>
43 </li>
42 </ul>
44 </ul>
43 </div>
45 </div>
@@ -45,14 +47,6 b''
45 </div>
47 </div>
46
48
47 <script type="text/javascript">
49 <script type="text/javascript">
48
50 $('#j_filter').autoGrowInput();
49 $('#j_filter').autoGrowInput();
50 $(document).on('pjax:success',function(){
51 show_more_event();
52 });
53 $(document).pjax(
54 '#refresh', '#journal',
55 {url: "${request.current_route_path(_query=dict(filter=c.search_term))}", push: false});
56
57 </script>
51 </script>
58 </%def>
52 </%def>
@@ -36,17 +36,10 b''
36 %endfor
36 %endfor
37 %endfor
37 %endfor
38
38
39 <div class="pagination-wh pagination-left" >
39 <div class="pagination-wh pagination-left">
40 ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
40 ${c.journal_pager.render()}
41 </div>
41 </div>
42 <script type="text/javascript">
42
43 $(document).pjax('#journal .pager_link','#journal');
44 $(document).on('pjax:success',function(){
45 show_more_event();
46 timeagoActivate();
47 tooltipActivate();
48 });
49 </script>
50 %else:
43 %else:
51 <div>
44 <div>
52 ${_('No entries yet')}
45 ${_('No entries yet')}
@@ -65,11 +65,11 b''
65
65
66 <%
66 <%
67 if c.sort.startswith('asc:'):
67 if c.sort.startswith('asc:'):
68 return c.url_generator(sort='desc:{}'.format(field_name))
68 return h.current_route_path(request, sort='desc:{}'.format(field_name))
69 elif c.sort.startswith('desc:'):
69 elif c.sort.startswith('desc:'):
70 return c.url_generator(sort='asc:{}'.format(field_name))
70 return h.current_route_path(request, sort='asc:{}'.format(field_name))
71
71
72 return c.url_generator(sort='asc:{}'.format(field_name))
72 return h.current_route_path(request, sort='asc:{}'.format(field_name))
73 %>
73 %>
74 </%def>
74 </%def>
75
75
@@ -63,7 +63,7 b''
63
63
64 %if c.cur_query:
64 %if c.cur_query:
65 <div class="pagination-wh pagination-left">
65 <div class="pagination-wh pagination-left">
66 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
66 ${c.formatted_results.render()}
67 </div>
67 </div>
68 %endif
68 %endif
69
69
@@ -145,7 +145,7 b''
145 </div>
145 </div>
146 %if c.cur_query and c.formatted_results:
146 %if c.cur_query and c.formatted_results:
147 <div class="pagination-wh pagination-left" >
147 <div class="pagination-wh pagination-left" >
148 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
148 ${c.formatted_results.render()}
149 </div>
149 </div>
150 %endif
150 %endif
151
151
@@ -46,7 +46,7 b''
46
46
47 %if c.cur_query:
47 %if c.cur_query:
48 <div class="pagination-wh pagination-left">
48 <div class="pagination-wh pagination-left">
49 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
49 ${c.formatted_results.render()}
50 </div>
50 </div>
51 %endif
51 %endif
52
52
@@ -107,7 +107,7 b''
107 </script>
107 </script>
108
108
109 <div class="pagination-wh pagination-left">
109 <div class="pagination-wh pagination-left">
110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
110 ${c.repo_commits.render()}
111 </div>
111 </div>
112 %else:
112 %else:
113
113
General Comments 0
You need to be logged in to leave comments. Login now