Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def view():
# you can attach your objects to the paginator
p.objects = objects
return render_all(p=p)

def render_all(p):
print "Your objects:"
for o in p.objects:
Expand Down Expand Up @@ -52,22 +52,43 @@ def render(p):
if p.next: # pythonic, of course you can check p.has_next too
print "<li><a href='%s'>next</a></li>" % p.next
print "</ul>"

def render_bootstrap(p):
print "Your objects:"
for o in p.objects:
print o
print "Your pages:"
print "<ul class='pagination'>"
if p.pageset_previous:
print '<li><a href="%s"><span aria-hidden="true">&laquo;</span><span class="sr-only">Previous</span></a></li>' % p.pageset_previous
else:
print '<li><a href="#" class="disabled"><span aria-hidden="true">&laquo;</span><span class="sr-only">Previous</span></a></li>'
for i in p.pages:
print '<li><a href="%s">%s</a></li>' % (i, i)
if p.pageset_next:
print '<li><a href="%s"><span aria-hidden="true">&raquo;</span><span class="sr-only">Next</span></a></li>' % p.pageset_next
else:
print '<li><a href="#" class="disabled"><span aria-hidden="true">&raquo;</span><span class="sr-only">Next</span></a></li>'
print "</ul>"
```

##api

*class* pypages.**Paginator**(*object_num, per_page=10, current=1, start=None, range_num=10*)
***Parameters:***
*class* pypages.**Paginator**(*object_num, per_page=10, current=1, start=None, range_num=10, ensure_page_1=False*)
***Parameters:***

* **object_num** – the total number of items
* **per_page** – the maximum number of items to include on a page, default 10
* **current** – the current page number, default 1
* **start** – the start index for your page range, default to be current page minus half of the page range length
* **range_num** – the maximum page range length, default 10
* **range_num** – the maximum page range length, default 10
* **ensure_page_1** – if set to `True` (default is `False`) this will return `1` for page_num instead of `0`. Sometimes you need to have 1 page, even if there is no data on it.


NOTICE: **page range** is the pages that will be displayed, like you have one page "5, 6, 7, 8, 9, 10", then your page range ``start`` is 5 and ``range_num`` is 6.

***Attributes:***
***Attributes:***

* **object_num** - the total number of items
* **per_page** – the maximum number of items to include on a page
Expand All @@ -79,5 +100,10 @@ def render(p):
* **pages** - the page range, a list like `[4, 5, 6, 7, 8]`
* **has_previous** - bool value to indicate whether current page have previous page
* **has_next** - bool value to indicate whether current page have next page
* **previous** - the previous page number, if do not exist, will be `None`
* **next** - the next page number, if do not exist, will be `None`
* **previous** - the previous page number; if does not exist, will be `None`
* **next** - the next page number; if does not exist, will be `None`
* **pageset_next** - the page number of the start of the next range; if does not exist, will be `None`
* **pageset_previous** - the page number of the start of the previous range; if does not exist, will be `None`
* **pageset_centered** - an alternate version of `pages`, in which the current page is centered within the range
* **pageset_centered_next** - the page number of the start of the next `pageset_centered` range; if does not exist, will be `None`
* **pageset_centered_previous** - the page number of the start of the previous `pageset_centered` range; if does not exist, will be `None`
108 changes: 107 additions & 1 deletion pypages.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class Paginator(object):
p.has_next
p.previous
p.next
p.pageset_next
p.pageset_previous
p.pageset_centered
p.pageset_centered_previous
p.pageset_centered_next

:param object_num: The total number of items.
:param per_page: The maximum number of items to include on a page,
Expand All @@ -42,8 +47,10 @@ class Paginator(object):
"""

def __init__(self, object_num, per_page=10, current=1, start=None,
range_num=10):
range_num=10, ensure_page_1=False):
self._start = self._end = self._current = self._page_num = None
self._pageset_centered = None
self._ensure_page_1 = bool(ensure_page_1) # set this first, because setting `self.current` will access it
self.object_num = int(object_num)
self.per_page = int(per_page)
self.current = current
Expand Down Expand Up @@ -107,6 +114,8 @@ def page_num(self):
if self._page_num is None:
self._page_num = int(math.ceil(self.object_num /
float(self.per_page)))
if (self._page_num == 0) and self._ensure_page_1:
self._page_num = 1
return self._page_num

@property
Expand Down Expand Up @@ -136,3 +145,100 @@ def pages(self):
"""Returns a 1-based range of pages for loop.
"""
return range(self.start, self.end + 1)

@property
def pageset_next(self):
"""Returns the id of the start of the next pagination set, or `None`"""
# return early if we don't have enough pages
if self.page_num < self.range_num:
return None
_next = self.end + 1
return _next if _next <= self.page_num else None

@property
def pageset_previous(self):
"""Returns the id of the previous pagination set, or `None`"""
# return early if we haven't started a pagination yet
if self.start <= 1:
return None
_prev = self.start - self.range_num
return max(_prev, 1)

@property
def pageset_centered(self):
"""Returns a 1-based range of pages for looping. the current page is centered in the list
example: page 10 would look like this: [ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
"""
if self._pageset_centered is None:
def _pageset_centered():
# exit early if we don't need any wrapping
if self.page_num <= self.range_num:
return range(1, self.end + 1)
_mid = int(self.range_num / 2) # the middle of a range
_extra = 1 if (self.range_num % 2 == 0) else 0
_mid = _mid - _extra # show more after than before
_start = self.start - _mid
if (_start) <= 0:
_start = 1
_end = _start + self.range_num - 1
if (_end) > self.page_num:
_end = self.page_num
_start = _end - self.range_num + 1
return range(_start, _end + 1)
self._pageset_centered = _pageset_centered()
return self._pageset_centered

@property
def pageset_centered_next(self):
"""Returns the id of the next pagination set, or `None`
This method does a bit more grouping, trying to make the display a bit nicer.
"""
# exit early if we don't need any wrapping
pageset = self.pageset_centered
if not pageset:
return None

_mid = int(self.range_num / 2) # the middle of a range
_extra = 1 if (self.range_num % 2 == 0) else 0

next_mid = pageset[-1] + _mid
max_mid = self.page_num - _mid + _extra

if (self.start + _mid) >= self.page_num:
return None

if next_mid >= max_mid:
if max_mid < self.start:
return None
return max_mid
return next_mid

@property
def pageset_centered_previous(self):
"""Returns the id of the previous pagination set, or `None`

1-4:
1,2,3,4,5,6,7
[]

5:
2,3,4,5,6,7,8
[1]

6:
3,4,5,6,7,8,9
[2]

"""
# exit early if we don't need any wrapping
pageset = self.pageset_centered
if not pageset:
return None

_intended_start = self.start - self.range_num
if _intended_start <= 0:
_intended_start = 1
_mid = int(self.range_num / 2) # the middle of a range
if self.start >= (_intended_start + _mid):
return _intended_start
return None
114 changes: 114 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from pypages import Paginator

import pdb


class PyPagesTestCase(unittest.TestCase):

Expand Down Expand Up @@ -158,6 +160,118 @@ def test_exception(self):
self.assertRaises(AssertionError, Paginator, 145, range_num=-1)
self.assertRaises(ValueError, Paginator, "value")

def test_pageset(self):
p = Paginator(100,)
self.assertEquals(p.pageset_next, None)
self.assertEquals(p.pageset_previous, None)

if False:
for i in range(1, 15):
p = Paginator(150, start=i)
print "----= %s" % i
print p.pages
if p.pageset_next:
pp = Paginator(150, start=p.pageset_next)
print ">> %s" % pp.pages
else:
print ">> %s" % []
if p.pageset_previous:
pp = Paginator(150, start=p.pageset_previous)
print "<< %s" % pp.pages
else:
print "<< %s" % []

p = Paginator(150,)
self.assertEquals(p.pageset_next, 11)
self.assertEquals(p.pageset_previous, None)

p = Paginator(150, start=2)
self.assertEquals(p.pageset_next, 12)
self.assertEquals(p.pageset_previous, 1)

p = Paginator(150, start=3)
self.assertEquals(p.pageset_next, 13)
self.assertEquals(p.pageset_previous, 1)

p = Paginator(150, start=4)
self.assertEquals(p.pageset_next, 14)
self.assertEquals(p.pageset_previous, 1)

p = Paginator(150, start=5)
self.assertEquals(p.pageset_next, 15)
self.assertEquals(p.pageset_previous, 1)

p = Paginator(150, start=6)
self.assertEquals(p.pageset_next, None)
self.assertEquals(p.pageset_previous, 1)

# this range is all `None, 1`

p = Paginator(150, start=11)
self.assertEquals(p.pageset_next, None)
self.assertEquals(p.pageset_previous, 1)

# we finally bump up here

p = Paginator(150, start=12)
self.assertEquals(p.pageset_next, None)
self.assertEquals(p.pageset_previous, 2)

def test_pageset_centered(self):

for i in range(1, 86):
p = Paginator(850, start=i)
pageset_centered = p.pageset_centered

# all should have a length of 10
self.assertEquals(len(pageset_centered), 10)

# check the first item
if i <= 5:
# for the first 5, should be 1
self.assertEquals(pageset_centered[0], 1)
elif i >= 80:
# thereafter, should be 4 items less than the current loop
self.assertEquals(pageset_centered[0], 76)
else:
# thereafter, should be 4 items less than the current loop
self.assertEquals(pageset_centered[0], i - 4)

# check the pagination - previous
if i <= 5:
# for the first 5, should be None
self.assertEquals(p.pageset_centered_previous, None)
elif (i >= 6) and (i <= 11):
# for the next 5, should be 1
self.assertEquals(p.pageset_centered_previous, 1)
else:
# thereafter, should be 10 items less than i
self.assertEquals(p.pageset_centered_previous, i - 10)

# check the pagination - next
if i <= 5:
# for the first 5, should be None
self.assertEquals(p.pageset_centered_next, 15)
elif (i > 5) and (i < 71):
self.assertEquals(p.pageset_centered_next, i + 10)
elif (i >= 71) and (i < 80):
# for the last 5, should be 81
self.assertEquals(p.pageset_centered_next, 81)
else:
# thereafter, should be 10 items more than i
self.assertEquals(p.pageset_centered_next, None)

# ensure we have the right lengths of pages
test_sets = (
# items, length
(9, 1),
(10, 1),
(11, 2),
)
for test_set in test_sets:
p = Paginator(test_set[0], start=1)
self.assertEquals(len(p.pageset_centered), test_set[1])


if __name__ == "__main__":
unittest.main()