Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Copyright (c) 2007-2012 Christoph Haas <email@christoph-haas.de> 

2# See the file LICENSE for copying permission. 

3 

4""" 

5paginate: helps split up large collections into individual pages 

6================================================================ 

7 

8What is pagination? 

9--------------------- 

10 

11This module helps split large lists of items into pages. The user is shown one page at a time and 

12can navigate to other pages. Imagine you are offering a company phonebook and let the user search 

13the entries. The entire search result may contains 23 entries but you want to display no more than 

1410 entries at once. The first page contains entries 1-10, the second 11-20 and the third 21-23. 

15Each "Page" instance represents the items of one of these three pages. 

16 

17See the documentation of the "Page" class for more information. 

18 

19How do I use it? 

20------------------ 

21 

22A page of items is represented by the *Page* object. A *Page* gets initialized with these arguments: 

23 

24- The collection of items to pick a range from. Usually just a list. 

25- The page number you want to display. Default is 1: the first page. 

26 

27Now we can make up a collection and create a Page instance of it:: 

28 

29 # Create a sample collection of 1000 items 

30 >> my_collection = range(1000) 

31 

32 # Create a Page object for the 3rd page (20 items per page is the default) 

33 >> my_page = Page(my_collection, page=3) 

34 

35 # The page object can be printed as a string to get its details 

36 >> str(my_page) 

37 Page: 

38 Collection type: <type 'range'> 

39 Current page: 3 

40 First item: 41 

41 Last item: 60 

42 First page: 1 

43 Last page: 50 

44 Previous page: 2 

45 Next page: 4 

46 Items per page: 20 

47 Number of items: 1000 

48 Number of pages: 50 

49 

50 # Print a list of items on the current page 

51 >> my_page.items 

52 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59] 

53 

54 # The *Page* object can be used as an iterator: 

55 >> for my_item in my_page: print(my_item) 

56 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 

57 

58 # The .pager() method returns an HTML fragment with links to surrounding pages. 

59 >> my_page.pager(url="http://example.org/foo/page=$page") 

60 

61 <a href="http://example.org/foo/page=1">1</a> 

62 <a href="http://example.org/foo/page=2">2</a> 

63 3 

64 <a href="http://example.org/foo/page=4">4</a> 

65 <a href="http://example.org/foo/page=5">5</a> 

66 .. 

67 <a href="http://example.org/foo/page=50">50</a>' 

68 

69 # Without the HTML it would just look like: 

70 # 1 2 [3] 4 5 .. 50 

71 

72 # The pager can be customized: 

73 >> my_page.pager('$link_previous ~3~ $link_next (Page $page of $page_count)', 

74 url="http://example.org/foo/page=$page") 

75 

76 <a href="http://example.org/foo/page=2">&lt;</a> 

77 <a href="http://example.org/foo/page=1">1</a> 

78 <a href="http://example.org/foo/page=2">2</a> 

79 3 

80 <a href="http://example.org/foo/page=4">4</a> 

81 <a href="http://example.org/foo/page=5">5</a> 

82 <a href="http://example.org/foo/page=6">6</a> 

83 .. 

84 <a href="http://example.org/foo/page=50">50</a> 

85 <a href="http://example.org/foo/page=4">&gt;</a> 

86 (Page 3 of 50) 

87 

88 # Without the HTML it would just look like: 

89 # 1 2 [3] 4 5 6 .. 50 > (Page 3 of 50) 

90 

91 # The url argument to the pager method can be omitted when an url_maker is 

92 # given during instantiation: 

93 >> my_page = Page(my_collection, page=3, 

94 url_maker=lambda p: "http://example.org/%s" % p) 

95 >> page.pager() 

96 

97There are some interesting parameters that customize the Page's behavior. See the documentation on 

98``Page`` and ``Page.pager()``. 

99 

100 

101Notes 

102------- 

103 

104Page numbers and item numbers start at 1. This concept has been used because users expect that the 

105first page has number 1 and the first item on a page also has number 1. So if you want to use the 

106page's items by their index number please note that you have to subtract 1. 

107""" 

108 

109__author__ = "Christoph Haas" 

110__copyright__ = "Copyright 2007-2016 Christoph Haas and contributors" 

111__credits__ = ["Mike Orr"] 

112__license__ = "MIT" 

113__version__ = "0.5.4" 

114__maintainer__ = "Marcin Lulek, Luke Crooks" 

115__email__ = "info@webreactor.eu, luke@pumalo.org" 

116__status__ = "Beta" 

117 

118 

119import re 

120from string import Template 

121import sys 

122 

123# are we running at least python 3.x ? 

124PY3 = sys.version_info[0] >= 3 

125 

126if PY3: 

127 unicode = str 

128 

129 

130# Since the items on a page are mainly a list we subclass the "list" type 

131class Page(list): 

132 """A list/iterator representing the items on one page of a larger collection. 

133 

134 An instance of the "Page" class is created from a _collection_ which is any 

135 list-like object that allows random access to its elements. 

136 

137 The instance works as an iterator running from the first item to the last item on the given 

138 page. The Page.pager() method creates a link list allowing the user to go to other pages. 

139 

140 A "Page" does not only carry the items on a certain page. It gives you additional information 

141 about the page in these "Page" object attributes: 

142 

143 item_count 

144 Number of items in the collection 

145 

146 **WARNING:** Unless you pass in an item_count, a count will be 

147 performed on the collection every time a Page instance is created. 

148 

149 page 

150 Number of the current page 

151 

152 items_per_page 

153 Maximal number of items displayed on a page 

154 

155 first_page 

156 Number of the first page - usually 1 :) 

157 

158 last_page 

159 Number of the last page 

160 

161 previous_page 

162 Number of the previous page. If this is the first page it returns None. 

163 

164 next_page 

165 Number of the next page. If this is the first page it returns None. 

166 

167 page_count 

168 Number of pages 

169 

170 items 

171 Sequence/iterator of items on the current page 

172 

173 first_item 

174 Index of first item on the current page - starts with 1 

175 

176 last_item 

177 Index of last item on the current page 

178 """ 

179 def __init__(self, collection, page=1, items_per_page=20, item_count=None, 

180 wrapper_class=None, url_maker=None, **kwargs): 

181 """Create a "Page" instance. 

182 

183 Parameters: 

184 

185 collection 

186 Sequence representing the collection of items to page through. 

187 

188 page 

189 The requested page number - starts with 1. Default: 1. 

190 

191 items_per_page 

192 The maximal number of items to be displayed per page. 

193 Default: 20. 

194 

195 item_count (optional) 

196 The total number of items in the collection - if known. 

197 If this parameter is not given then the paginator will count 

198 the number of elements in the collection every time a "Page" 

199 is created. Giving this parameter will speed up things. In a busy 

200 real-life application you may want to cache the number of items. 

201 

202 url_maker (optional) 

203 Callback to generate the URL of other pages, given its numbers. 

204 Must accept one int parameter and return a URI string. 

205 """ 

206 if collection is not None: 

207 if wrapper_class is None: 

208 # Default case. The collection is already a list-type object. 

209 self.collection = collection 

210 else: 

211 # Special case. A custom wrapper class is used to access elements of the collection. 

212 self.collection = wrapper_class(collection) 

213 else: 

214 self.collection = [] 

215 

216 self.collection_type = type(collection) 

217 

218 if url_maker is not None: 

219 self.url_maker = url_maker 

220 else: 

221 self.url_maker = self._default_url_maker 

222 

223 # Assign kwargs to self 

224 self.kwargs = kwargs 

225 

226 # The self.page is the number of the current page. 

227 # The first page has the number 1! 

228 try: 

229 self.page = int(page) # make it int() if we get it as a string 

230 except (ValueError, TypeError): 

231 self.page = 1 

232 # normally page should be always at least 1 but the original maintainer 

233 # decided that for empty collection and empty page it can be...0? (based on tests) 

234 # preserving behavior for BW compat 

235 if self.page < 1: 

236 self.page = 1 

237 

238 self.items_per_page = items_per_page 

239 

240 # We subclassed "list" so we need to call its init() method 

241 # and fill the new list with the items to be displayed on the page. 

242 # We use list() so that the items on the current page are retrieved 

243 # only once. In an SQL context that could otherwise lead to running the 

244 # same SQL query every time items would be accessed. 

245 # We do this here, prior to calling len() on the collection so that a 

246 # wrapper class can execute a query with the knowledge of what the 

247 # slice will be (for efficiency) and, in the same query, ask for the 

248 # total number of items and only execute one query. 

249 try: 

250 first = (self.page - 1) * items_per_page 

251 last = first + items_per_page 

252 self.items = list(self.collection[first:last]) 

253 except TypeError: 

254 raise TypeError("Your collection of type {} cannot be handled " 

255 "by paginate.".format(type(self.collection))) 

256 

257 # Unless the user tells us how many items the collections has 

258 # we calculate that ourselves. 

259 if item_count is not None: 

260 self.item_count = item_count 

261 else: 

262 self.item_count = len(self.collection) 

263 

264 # Compute the number of the first and last available page 

265 if self.item_count > 0: 

266 self.first_page = 1 

267 self.page_count = ((self.item_count - 1) // self.items_per_page) + 1 

268 self.last_page = self.first_page + self.page_count - 1 

269 

270 # Make sure that the requested page number is the range of valid pages 

271 if self.page > self.last_page: 

272 self.page = self.last_page 

273 elif self.page < self.first_page: 

274 self.page = self.first_page 

275 

276 # Note: the number of items on this page can be less than 

277 # items_per_page if the last page is not full 

278 self.first_item = (self.page - 1) * items_per_page + 1 

279 self.last_item = min(self.first_item + items_per_page - 1, self.item_count) 

280 

281 # Links to previous and next page 

282 if self.page > self.first_page: 

283 self.previous_page = self.page-1 

284 else: 

285 self.previous_page = None 

286 

287 if self.page < self.last_page: 

288 self.next_page = self.page+1 

289 else: 

290 self.next_page = None 

291 

292 # No items available 

293 else: 

294 self.first_page = None 

295 self.page_count = 0 

296 self.last_page = None 

297 self.first_item = None 

298 self.last_item = None 

299 self.previous_page = None 

300 self.next_page = None 

301 self.items = [] 

302 

303 # This is a subclass of the 'list' type. Initialise the list now. 

304 list.__init__(self, self.items) 

305 

306 def __str__(self): 

307 return ("Page:\n" 

308 "Collection type: {0.collection_type}\n" 

309 "Current page: {0.page}\n" 

310 "First item: {0.first_item}\n" 

311 "Last item: {0.last_item}\n" 

312 "First page: {0.first_page}\n" 

313 "Last page: {0.last_page}\n" 

314 "Previous page: {0.previous_page}\n" 

315 "Next page: {0.next_page}\n" 

316 "Items per page: {0.items_per_page}\n" 

317 "Total number of items: {0.item_count}\n" 

318 "Number of pages: {0.page_count}\n" 

319 ).format(self) 

320 

321 def __repr__(self): 

322 return("<paginate.Page: Page {0}/{1}>".format(self.page, self.page_count)) 

323 

324 def pager(self, format='~2~', url=None, show_if_single_page=False, separator=' ', 

325 symbol_first='&lt;&lt;', symbol_last='&gt;&gt;', symbol_previous='&lt;', symbol_next='&gt;', 

326 link_attr=dict(), curpage_attr=dict(), dotdot_attr=dict(), link_tag=None): 

327 """ 

328 Return string with links to other pages (e.g. '1 .. 5 6 7 [8] 9 10 11 .. 50'). 

329 

330 format: 

331 Format string that defines how the pager is rendered. The string 

332 can contain the following $-tokens that are substituted by the 

333 string.Template module: 

334 

335 - $first_page: number of first reachable page 

336 - $last_page: number of last reachable page 

337 - $page: number of currently selected page 

338 - $page_count: number of reachable pages 

339 - $items_per_page: maximal number of items per page 

340 - $first_item: index of first item on the current page 

341 - $last_item: index of last item on the current page 

342 - $item_count: total number of items 

343 - $link_first: link to first page (unless this is first page) 

344 - $link_last: link to last page (unless this is last page) 

345 - $link_previous: link to previous page (unless this is first page) 

346 - $link_next: link to next page (unless this is last page) 

347 

348 To render a range of pages the token '~3~' can be used. The 

349 number sets the radius of pages around the current page. 

350 Example for a range with radius 3: 

351 

352 '1 .. 5 6 7 [8] 9 10 11 .. 50' 

353 

354 Default: '~2~' 

355 

356 url 

357 The URL that page links will point to. Make sure it contains the string 

358 $page which will be replaced by the actual page number. 

359 Must be given unless a url_maker is specified to __init__, in which 

360 case this parameter is ignored. 

361 

362 symbol_first 

363 String to be displayed as the text for the $link_first link above. 

364 

365 Default: '&lt;&lt;' (<<) 

366 

367 symbol_last 

368 String to be displayed as the text for the $link_last link above. 

369 

370 Default: '&gt;&gt;' (>>) 

371 

372 symbol_previous 

373 String to be displayed as the text for the $link_previous link above. 

374 

375 Default: '&lt;' (<) 

376 

377 symbol_next 

378 String to be displayed as the text for the $link_next link above. 

379 

380 Default: '&gt;' (>) 

381 

382 separator: 

383 String that is used to separate page links/numbers in the above range of pages. 

384 

385 Default: ' ' 

386 

387 show_if_single_page: 

388 if True the navigator will be shown even if there is only one page. 

389 

390 Default: False 

391 

392 link_attr (optional) 

393 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can 

394 be used to define a CSS style or class to customize the look of links. 

395 

396 Example: { 'style':'border: 1px solid green' } 

397 Example: { 'class':'pager_link' } 

398 

399 curpage_attr (optional) 

400 A dictionary of attributes that get added to the current page number in the pager (which 

401 is obviously not a link). If this dictionary is not empty then the elements will be 

402 wrapped in a SPAN tag with the given attributes. 

403 

404 Example: { 'style':'border: 3px solid blue' } 

405 Example: { 'class':'pager_curpage' } 

406 

407 dotdot_attr (optional) 

408 A dictionary of attributes that get added to the '..' string in the pager (which is 

409 obviously not a link). If this dictionary is not empty then the elements will be wrapped 

410 in a SPAN tag with the given attributes. 

411 

412 Example: { 'style':'color: #808080' } 

413 Example: { 'class':'pager_dotdot' } 

414 

415 link_tag (optional) 

416 A callable that accepts single argument `page` (page link information) 

417 and generates string with html that represents the link for specific page. 

418 Page objects are supplied from `link_map()` so the keys are the same. 

419 

420 

421 """ 

422 self.curpage_attr = curpage_attr 

423 self.separator = separator 

424 self.link_attr = link_attr 

425 self.dotdot_attr = dotdot_attr 

426 self.url = url 

427 self.link_tag = link_tag or self.default_link_tag 

428 

429 # Don't show navigator if there is no more than one page 

430 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): 

431 return '' 

432 

433 regex_res = re.search(r'~(\d+)~', format) 

434 if regex_res: 

435 radius = regex_res.group(1) 

436 else: 

437 radius = 2 

438 radius = int(radius) 

439 self.radius = radius 

440 link_map = self.link_map( 

441 format=format, url=url, show_if_single_page=show_if_single_page, separator=separator, 

442 symbol_first=symbol_first, symbol_last=symbol_last, symbol_previous=symbol_previous, 

443 symbol_next=symbol_next, link_attr=link_attr, curpage_attr=curpage_attr, dotdot_attr=dotdot_attr 

444 ) 

445 links_markup = self._range(link_map, radius) 

446 

447 # Replace ~...~ in token format by range of pages 

448 result = re.sub(r'~(\d+)~', links_markup, format) 

449 

450 # Interpolate '$' variables 

451 result = Template(result).safe_substitute({ 

452 'first_page': self.first_page, 

453 'last_page': self.last_page, 

454 'page': self.page, 

455 'page_count': self.page_count, 

456 'items_per_page': self.items_per_page, 

457 'first_item': self.first_item, 

458 'last_item': self.last_item, 

459 'item_count': self.item_count, 

460 'link_first': self.page>self.first_page and self.link_tag(link_map['first_page']) or '', 

461 'link_last': self.page<self.last_page and self.link_tag(link_map['last_page']) or '', 

462 'link_previous': self.previous_page and self.link_tag(link_map['previous_page']) or '', 

463 'link_next': self.next_page and self.link_tag(link_map['next_page']) or '' 

464 }) 

465 

466 return result 

467 

468 def link_map(self, format='~2~', url=None, show_if_single_page=False, separator=' ', 

469 symbol_first='&lt;&lt;', symbol_last='&gt;&gt;', symbol_previous='&lt;', symbol_next='&gt;', 

470 link_attr=dict(), curpage_attr=dict(), dotdot_attr=dict()): 

471 """ Return map with links to other pages if default pager() function is not suitable solution. 

472 format: 

473 Format string that defines how the pager would be normally rendered rendered. Uses same arguments as pager() 

474 method, but returns a simple dictionary in form of: 

475 {'current_page': {'attrs': {}, 

476 'href': 'http://example.org/foo/page=1', 

477 'value': 1}, 

478 'first_page': {'attrs': {}, 

479 'href': 'http://example.org/foo/page=1', 

480 'type': 'first_page', 

481 'value': 1}, 

482 'last_page': {'attrs': {}, 

483 'href': 'http://example.org/foo/page=8', 

484 'type': 'last_page', 

485 'value': 8}, 

486 'next_page': {'attrs': {}, 'href': 'HREF', 'type': 'next_page', 'value': 2}, 

487 'previous_page': None, 

488 'range_pages': [{'attrs': {}, 

489 'href': 'http://example.org/foo/page=1', 

490 'type': 'current_page', 

491 'value': 1}, 

492 .... 

493 {'attrs': {}, 'href': '', 'type': 'span', 'value': '..'}]} 

494 

495 

496 The string can contain the following $-tokens that are substituted by the 

497 string.Template module: 

498 

499 - $first_page: number of first reachable page 

500 - $last_page: number of last reachable page 

501 - $page: number of currently selected page 

502 - $page_count: number of reachable pages 

503 - $items_per_page: maximal number of items per page 

504 - $first_item: index of first item on the current page 

505 - $last_item: index of last item on the current page 

506 - $item_count: total number of items 

507 - $link_first: link to first page (unless this is first page) 

508 - $link_last: link to last page (unless this is last page) 

509 - $link_previous: link to previous page (unless this is first page) 

510 - $link_next: link to next page (unless this is last page) 

511 

512 To render a range of pages the token '~3~' can be used. The 

513 number sets the radius of pages around the current page. 

514 Example for a range with radius 3: 

515 

516 '1 .. 5 6 7 [8] 9 10 11 .. 50' 

517 

518 Default: '~2~' 

519 

520 url 

521 The URL that page links will point to. Make sure it contains the string 

522 $page which will be replaced by the actual page number. 

523 Must be given unless a url_maker is specified to __init__, in which 

524 case this parameter is ignored. 

525 

526 symbol_first 

527 String to be displayed as the text for the $link_first link above. 

528 

529 Default: '&lt;&lt;' (<<) 

530 

531 symbol_last 

532 String to be displayed as the text for the $link_last link above. 

533 

534 Default: '&gt;&gt;' (>>) 

535 

536 symbol_previous 

537 String to be displayed as the text for the $link_previous link above. 

538 

539 Default: '&lt;' (<) 

540 

541 symbol_next 

542 String to be displayed as the text for the $link_next link above. 

543 

544 Default: '&gt;' (>) 

545 

546 separator: 

547 String that is used to separate page links/numbers in the above range of pages. 

548 

549 Default: ' ' 

550 

551 show_if_single_page: 

552 if True the navigator will be shown even if there is only one page. 

553 

554 Default: False 

555 

556 link_attr (optional) 

557 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can 

558 be used to define a CSS style or class to customize the look of links. 

559 

560 Example: { 'style':'border: 1px solid green' } 

561 Example: { 'class':'pager_link' } 

562 

563 curpage_attr (optional) 

564 A dictionary of attributes that get added to the current page number in the pager (which 

565 is obviously not a link). If this dictionary is not empty then the elements will be 

566 wrapped in a SPAN tag with the given attributes. 

567 

568 Example: { 'style':'border: 3px solid blue' } 

569 Example: { 'class':'pager_curpage' } 

570 

571 dotdot_attr (optional) 

572 A dictionary of attributes that get added to the '..' string in the pager (which is 

573 obviously not a link). If this dictionary is not empty then the elements will be wrapped 

574 in a SPAN tag with the given attributes. 

575 

576 Example: { 'style':'color: #808080' } 

577 Example: { 'class':'pager_dotdot' } 

578 """ 

579 self.curpage_attr = curpage_attr 

580 self.separator = separator 

581 self.link_attr = link_attr 

582 self.dotdot_attr = dotdot_attr 

583 self.url = url 

584 

585 regex_res = re.search(r'~(\d+)~', format) 

586 if regex_res: 

587 radius = regex_res.group(1) 

588 else: 

589 radius = 2 

590 radius = int(radius) 

591 self.radius = radius 

592 

593 # Compute the first and last page number within the radius 

594 # e.g. '1 .. 5 6 [7] 8 9 .. 12' 

595 # -> leftmost_page = 5 

596 # -> rightmost_page = 9 

597 leftmost_page = max(self.first_page, (self.page-radius)) if \ 

598 self.first_page else None 

599 rightmost_page = min(self.last_page, (self.page+radius)) if \ 

600 self.last_page else None 

601 

602 nav_items = { 

603 "first_page": None, 

604 "last_page": None, 

605 "previous_page": None, 

606 "next_page": None, 

607 "current_page": None, 

608 "radius": self.radius, 

609 "range_pages": [] 

610 } 

611 

612 if leftmost_page is None or rightmost_page is None: 

613 return nav_items 

614 

615 nav_items["first_page"] = {"type": "first_page", "value": unicode(symbol_first), "attrs": self.link_attr, 

616 "number": self.first_page, "href": self.url_maker(self.first_page)} 

617 

618 # Insert dots if there are pages between the first page 

619 # and the currently displayed page range 

620 if leftmost_page - self.first_page > 1: 

621 # Wrap in a SPAN tag if dotdot_attr is set 

622 nav_items["range_pages"].append({"type": "span", "value": '..', "attrs": self.dotdot_attr, "href": "", 

623 "number": None}) 

624 

625 for thispage in range(leftmost_page, rightmost_page+1): 

626 # Highlight the current page number and do not use a link 

627 if thispage == self.page: 

628 # Wrap in a SPAN tag if curpage_attr is set 

629 nav_items["range_pages"].append({"type": "current_page", "value": unicode(thispage), "number": thispage, 

630 "attrs": self.curpage_attr, "href": self.url_maker(thispage)}) 

631 nav_items["current_page"] = {"value": thispage, "attrs": self.curpage_attr, 

632 "type": "current_page", "href": self.url_maker(thispage)} 

633 # Otherwise create just a link to that page 

634 else: 

635 nav_items["range_pages"].append({"type": "page", "value": unicode(thispage), "number": thispage, 

636 "attrs": self.link_attr, "href": self.url_maker(thispage)}) 

637 

638 # Insert dots if there are pages between the displayed 

639 # page numbers and the end of the page range 

640 if self.last_page - rightmost_page > 1: 

641 # Wrap in a SPAN tag if dotdot_attr is set 

642 nav_items["range_pages"].append({"type": "span", "value": '..', "attrs": self.dotdot_attr, "href": "", 

643 "number":None}) 

644 

645 # Create a link to the very last page (unless we are on the last 

646 # page or there would be no need to insert '..' spacers) 

647 nav_items["last_page"] = {"type": "last_page", "value": unicode(symbol_last), "attrs": self.link_attr, 

648 "href": self.url_maker(self.last_page), "number":self.last_page} 

649 

650 nav_items["previous_page"] = {"type": "previous_page", "value": unicode(symbol_previous), 

651 "attrs": self.link_attr, "number": self.previous_page or self.first_page, 

652 "href": self.url_maker(self.previous_page or self.first_page)} 

653 

654 nav_items["next_page"] = {"type": "next_page", "value": unicode(symbol_next), 

655 "attrs": self.link_attr, "number": self.next_page or self.last_page, 

656 "href": self.url_maker(self.next_page or self.last_page)} 

657 

658 return nav_items 

659 

660 

661 def _range(self, link_map, radius): 

662 """ 

663 Return range of linked pages to substiture placeholder in pattern 

664 """ 

665 

666 

667 leftmost_page = max(self.first_page, (self.page-radius)) 

668 rightmost_page = min(self.last_page, (self.page+radius)) 

669 

670 nav_items = [] 

671 # Create a link to the first page (unless we are on the first page 

672 # or there would be no need to insert '..' spacers) 

673 if self.page != self.first_page and self.first_page < leftmost_page: 

674 page = link_map['first_page'].copy() 

675 page['value'] = unicode(page['number']) 

676 nav_items.append(self.link_tag(page)) 

677 

678 for item in link_map['range_pages']: 

679 nav_items.append(self.link_tag(item)) 

680 

681 # Create a link to the very last page (unless we are on the last 

682 # page or there would be no need to insert '..' spacers) 

683 if self.page != self.last_page and rightmost_page < self.last_page: 

684 page = link_map['last_page'].copy() 

685 page['value'] = unicode(page['number']) 

686 nav_items.append(self.link_tag(page)) 

687 

688 return self.separator.join(nav_items) 

689 

690 def _default_url_maker(self, page_number): 

691 if self.url is None: 

692 raise Exception( 

693 "You need to specify a 'url' parameter containing a '$page' placeholder.") 

694 

695 if "$page" not in self.url: 

696 raise Exception("The 'url' parameter must contain a '$page' placeholder.") 

697 

698 return self.url.replace('$page', unicode(page_number)) 

699 

700 @staticmethod 

701 def default_link_tag(item): 

702 """ 

703 Create an A-HREF tag that points to another page. 

704 """ 

705 text = item['value'] 

706 target_url = item['href'] 

707 

708 if not item['href'] or item['type'] in ('span', 'current_page'): 

709 if item['attrs']: 

710 text = make_html_tag('span', **item['attrs']) + text + '</span>' 

711 return text 

712 

713 return make_html_tag('a', text=text, href=target_url, **item['attrs']) 

714 

715 

716def make_html_tag(tag, text=None, **params): 

717 """Create an HTML tag string. 

718 

719 tag 

720 The HTML tag to use (e.g. 'a', 'span' or 'div') 

721 

722 text 

723 The text to enclose between opening and closing tag. If no text is specified then only 

724 the opening tag is returned. 

725 

726 Example:: 

727 make_html_tag('a', text="Hello", href="/another/page") 

728 -> <a href="/another/page">Hello</a> 

729 

730 To use reserved Python keywords like "class" as a parameter prepend it with 

731 an underscore. Instead of "class='green'" use "_class='green'". 

732 

733 Warning: Quotes and apostrophes are not escaped.""" 

734 params_string = '' 

735 

736 # Parameters are passed. Turn the dict into a string like "a=1 b=2 c=3" string. 

737 for key, value in sorted(params.items()): 

738 # Strip off a leading underscore from the attribute's key to allow attributes like '_class' 

739 # to be used as a CSS class specification instead of the reserved Python keyword 'class'. 

740 key = key.lstrip('_') 

741 

742 params_string += u' {0}="{1}"'.format(key, value) 

743 

744 # Create the tag string 

745 tag_string = u'<{0}{1}>'.format(tag, params_string) 

746 

747 # Add text and closing tag if required. 

748 if text: 

749 tag_string += u'{0}</{1}>'.format(text, tag) 

750 

751 return tag_string