diff options
| author | Tom Christie | 2015-01-15 16:52:07 +0000 |
|---|---|---|
| committer | Tom Christie | 2015-01-15 16:52:07 +0000 |
| commit | d76e83dd78627a0cf4bcd4b28a7710fb678d8d4e (patch) | |
| tree | eb17b5a60fb64a97b70d3aa268a3f97c6b542227 /rest_framework/pagination.py | |
| parent | 313aa727e3c44016e531a7af75051fc6e6d7cb96 (diff) | |
| download | django-rest-framework-d76e83dd78627a0cf4bcd4b28a7710fb678d8d4e.tar.bz2 | |
Tweaks, and add pagination controls for offset/limit.
Diffstat (limited to 'rest_framework/pagination.py')
| -rw-r--r-- | rest_framework/pagination.py | 126 |
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 |
