aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/pagination.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/pagination.py')
-rw-r--r--rest_framework/pagination.py126
1 files changed, 96 insertions, 30 deletions
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 69d0f77d..2b78f1f7 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -29,6 +29,15 @@ def _strict_positive_int(integer_string, cutoff=None):
return ret
+def _divide_with_ceil(a, b):
+ """
+ Returns 'a' divded by 'b', with any remainder rounded up.
+ """
+ if a % b:
+ return (a / b) + 1
+ return a / b
+
+
def _get_count(queryset):
"""
Determine an object count, supporting either querysets or regular lists.
@@ -48,14 +57,21 @@ def _get_displayed_page_numbers(current, final):
current=14, final=16 -> [1, None, 13, 14, 15, 16]
This implementation gives one page to each side of the cursor,
- for an implementation which gives two pages to each side of the cursor,
- which is a copy of how GitHub treat pagination in their issue lists, see:
+ or two pages to the side when the cursor is at the edge, then
+ ensures that any breaks between non-continous page numbers never
+ remove only a single page.
+
+ For an alernativative implementation which gives two pages to each side of
+ the cursor, eg. as in GitHub issue list pagination, see:
https://gist.github.com/tomchristie/321140cebb1c4a558b15
"""
assert current >= 1
assert final >= current
+ if final <= 5:
+ return range(1, final + 1)
+
# We always include the first two pages, last two pages, and
# two pages either side of the current page.
included = set((
@@ -87,16 +103,46 @@ def _get_displayed_page_numbers(current, final):
return included
+def _get_page_links(page_numbers, current, url_func):
+ """
+ Given a list of page numbers and `None` page breaks,
+ return a list of `PageLink` objects.
+ """
+ page_links = []
+ for page_number in page_numbers:
+ if page_number is None:
+ page_link = PageLink(
+ url=None,
+ number=None,
+ is_active=False,
+ is_break=True
+ )
+ else:
+ page_link = PageLink(
+ url=url_func(page_number),
+ number=page_number,
+ is_active=(page_number == current),
+ is_break=False
+ )
+ page_links.append(page_link)
+ return page_links
+
+
PageLink = namedtuple('PageLink', ['url', 'number', 'is_active', 'is_break'])
class BasePagination(object):
+ display_page_controls = False
+
def paginate_queryset(self, queryset, request, view):
raise NotImplemented('paginate_queryset() must be implemented.')
def get_paginated_response(self, data):
raise NotImplemented('get_paginated_response() must be implemented.')
+ def to_html(self):
+ raise NotImplemented('to_html() must be implemented to display page controls.')
+
class PageNumberPagination(BasePagination):
"""
@@ -161,8 +207,9 @@ class PageNumberPagination(BasePagination):
)
raise NotFound(msg)
- # Indicate that the browsable API should display pagination controls.
- self.mark_as_used = True
+ if paginator.count > 1:
+ # The browsable API should display pagination controls.
+ self.display_page_controls = True
self.request = request
return self.page
@@ -203,31 +250,17 @@ class PageNumberPagination(BasePagination):
return replace_query_param(url, self.page_query_param, page_number)
def to_html(self):
- current = self.page.number
- final = self.page.paginator.num_pages
-
- page_links = []
base_url = self.request.build_absolute_uri()
- for page_number in _get_displayed_page_numbers(current, final):
- if page_number is None:
- page_link = PageLink(
- url=None,
- number=None,
- is_active=False,
- is_break=True
- )
+ def page_number_to_url(page_number):
+ if page_number == 1:
+ return remove_query_param(base_url, self.page_query_param)
else:
- if page_number == 1:
- url = remove_query_param(base_url, self.page_query_param)
- else:
- url = replace_query_param(url, self.page_query_param, page_number)
- page_link = PageLink(
- url=url,
- number=page_number,
- is_active=(page_number == current),
- is_break=False
- )
- page_links.append(page_link)
+ return replace_query_param(base_url, self.page_query_param, page_number)
+
+ current = self.page.number
+ final = self.page.paginator.num_pages
+ page_numbers = _get_displayed_page_numbers(current, final)
+ page_links = _get_page_links(page_numbers, current, page_number_to_url)
template = loader.get_template(self.template)
context = Context({
@@ -250,11 +283,15 @@ class LimitOffsetPagination(BasePagination):
offset_query_param = 'offset'
max_limit = None
+ template = 'rest_framework/pagination/numbers.html'
+
def paginate_queryset(self, queryset, request, view):
self.limit = self.get_limit(request)
self.offset = self.get_offset(request)
self.count = _get_count(queryset)
self.request = request
+ if self.count > self.limit:
+ self.display_page_controls = True
return queryset[self.offset:self.offset + self.limit]
def get_paginated_response(self, data):
@@ -285,16 +322,45 @@ class LimitOffsetPagination(BasePagination):
except (KeyError, ValueError):
return 0
- def get_next_link(self, page):
+ def get_next_link(self):
if self.offset + self.limit >= self.count:
return None
+
url = self.request.build_absolute_uri()
offset = self.offset + self.limit
return replace_query_param(url, self.offset_query_param, offset)
- def get_previous_link(self, page):
- if self.offset - self.limit < 0:
+ def get_previous_link(self):
+ if self.offset <= 0:
return None
+
url = self.request.build_absolute_uri()
+
+ if self.offset - self.limit <= 0:
+ return remove_query_param(url, self.offset_query_param)
+
offset = self.offset - self.limit
return replace_query_param(url, self.offset_query_param, offset)
+
+ def to_html(self):
+ base_url = self.request.build_absolute_uri()
+ current = _divide_with_ceil(self.offset, self.limit) + 1
+ final = _divide_with_ceil(self.count, self.limit)
+
+ def page_number_to_url(page_number):
+ if page_number == 1:
+ return remove_query_param(base_url, self.offset_query_param)
+ else:
+ offset = self.offset + ((page_number - current) * self.limit)
+ return replace_query_param(base_url, self.offset_query_param, offset)
+
+ page_numbers = _get_displayed_page_numbers(current, final)
+ page_links = _get_page_links(page_numbers, current, page_number_to_url)
+
+ template = loader.get_template(self.template)
+ context = Context({
+ 'previous_url': self.get_previous_link(),
+ 'next_url': self.get_next_link(),
+ 'page_links': page_links
+ })
+ return template.render(context) \ No newline at end of file