diff options
| author | Tom Christie | 2015-01-14 16:51:26 +0000 | 
|---|---|---|
| committer | Tom Christie | 2015-01-14 16:51:26 +0000 | 
| commit | 3833a5bb8a9174e5fb09dac59a964eff24b6065e (patch) | |
| tree | f0d343961fc83ead4ea79566afba4df7baf1bada /rest_framework | |
| parent | f13fcba9a9f41f7e00e0ea8956fcc65ca168c76c (diff) | |
| download | django-rest-framework-3833a5bb8a9174e5fb09dac59a964eff24b6065e.tar.bz2 | |
Include pagination control in browsable API
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/pagination.py | 90 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 1 | ||||
| -rw-r--r-- | rest_framework/static/rest_framework/css/bootstrap-tweaks.css | 4 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/base.html | 9 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/pagination/numbers.html | 27 | ||||
| -rw-r--r-- | rest_framework/templatetags/rest_framework.py | 17 | 
6 files changed, 143 insertions, 5 deletions
| diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index b9d48796..bd343c0d 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -3,14 +3,18 @@ Pagination serializers determine the structure of the output that should  be used for paginated responses.  """  from __future__ import unicode_literals +from collections import namedtuple  from django.core.paginator import InvalidPage, Paginator as DjangoPaginator +from django.template import Context, loader  from django.utils import six  from django.utils.translation import ugettext as _  from rest_framework.compat import OrderedDict  from rest_framework.exceptions import NotFound  from rest_framework.response import Response  from rest_framework.settings import api_settings -from rest_framework.templatetags.rest_framework import replace_query_param +from rest_framework.templatetags.rest_framework import ( +    replace_query_param, remove_query_param +)  def _strict_positive_int(integer_string, cutoff=None): @@ -35,6 +39,49 @@ def _get_count(queryset):          return len(queryset) +def _get_displayed_page_numbers(current, final): +    """ +    This utility function determines a list of page numbers to display. +    This gives us a nice contextually relevant set of page numbers. + +    For example: +    current=14, final=16 -> [1, None, 13, 14, 15, 16] +    """ +    assert current >= 1 +    assert final >= current + +    # We always include the first two pages, last two pages, and +    # two pages either side of the current page. +    included = set(( +        1, +        current - 1, current, current + 1, +        final +    )) + +    # If the break would only exclude a single page number then we +    # may as well include the page number instead of the break. +    if current == 4: +        included.add(2) +    if current == final - 3: +        included.add(final - 1) + +    # Now sort the page numbers and drop anything outside the limits. +    included = [ +        idx for idx in sorted(list(included)) +        if idx > 0 and idx <= final +    ] + +    # Finally insert any `...` breaks +    if current > 4: +        included.insert(1, None) +    if current < final - 3: +        included.insert(len(included) - 1, None) +    return included + + +PageLink = namedtuple('PageLink', ['url', 'number', 'is_active', 'is_break']) + +  class BasePagination(object):      def paginate_queryset(self, queryset, request, view):          raise NotImplemented('paginate_queryset() must be implemented.') @@ -66,6 +113,8 @@ class PageNumberPagination(BasePagination):      # Only relevant if 'paginate_by_param' has also been set.      max_paginate_by = api_settings.MAX_PAGINATE_BY +    template = 'rest_framework/pagination/numbers.html' +      def paginate_queryset(self, queryset, request, view):          """          Paginate a queryset if required, either returning a @@ -104,6 +153,8 @@ class PageNumberPagination(BasePagination):              )              raise NotFound(msg) +        # Indicate that the browsable API should display pagination controls. +        self.mark_as_used = True          self.request = request          return self.page @@ -139,8 +190,45 @@ class PageNumberPagination(BasePagination):              return None          url = self.request.build_absolute_uri()          page_number = self.page.previous_page_number() +        if page_number == 1: +            return remove_query_param(url, self.page_query_param)          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 +                ) +            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) + +        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) +  class LimitOffsetPagination(BasePagination):      """ diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index c4de30db..4c002b16 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -592,6 +592,7 @@ class BrowsableAPIRenderer(BaseRenderer):              'description': self.get_description(view),              'name': self.get_name(view),              'version': VERSION, +            'pager': getattr(view, 'pager', None),              'breadcrumblist': self.get_breadcrumbs(request),              'allowed_methods': view.allowed_methods,              'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes], diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css index 36c7be48..d4a7d31a 100644 --- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css +++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css @@ -185,10 +185,6 @@ body a:hover {    color: #c20000;  } -#content a span { -  text-decoration: underline; - } -  .request-info {    clear:both;  } diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index e9668193..e0030981 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -119,9 +119,18 @@                      <div class="page-header">                          <h1>{{ name }}</h1>                      </div> +                    <div style="float:left">                      {% block description %}                          {{ description }}                      {% endblock %} +                    </div> + +                    {% if pager.mark_as_used %} +                        <nav style="float: right"> +                        {% get_pagination_html pager %} +                        </nav> +                    {% endif %} +                      <div class="request-info" style="clear: both" >                          <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>                      </div> diff --git a/rest_framework/templates/rest_framework/pagination/numbers.html b/rest_framework/templates/rest_framework/pagination/numbers.html new file mode 100644 index 00000000..04045810 --- /dev/null +++ b/rest_framework/templates/rest_framework/pagination/numbers.html @@ -0,0 +1,27 @@ +<ul class="pagination" style="margin: 5px 0 10px 0"> +    {% if previous_url %} +        <li><a href="{{ previous_url }}" aria-label="Previous"><span aria-hidden="true">«</span></a></li> +    {% else %} +        <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> +    {% endif %} + +    {% for page_link in page_links %} +        {% if page_link.is_break %} +            <li class="disabled"> +                <a href="#"><span aria-hidden="true">…</span></a> +            </li> +        {% else %} +            {% if page_link.is_active %} +                <li class="active"><a href="{{ page_link.url }}">{{ page_link.number }}</a></li> +            {% else %} +                <li><a href="{{ page_link.url }}">{{ page_link.number }}</a></li> +            {% endif %} +        {% endif %} +    {% endfor %} + +    {% if next_url %} +        <li><a href="{{ next_url }}" aria-label="Next"><span aria-hidden="true">»</span></a></li> +    {% else %} +        <li class="disabled"><a href="#" aria-label="Next"><span aria-hidden="true">»</span></a></li> +    {% endif %} +</ul> diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 69e03af4..bf159d8b 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -26,6 +26,23 @@ def replace_query_param(url, key, val):      return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) +def remove_query_param(url, key): +    """ +    Given a URL and a key/val pair, set or replace an item in the query +    parameters of the URL, and return the new URL. +    """ +    (scheme, netloc, path, query, fragment) = urlparse.urlsplit(url) +    query_dict = QueryDict(query).copy() +    query_dict.pop(key, None) +    query = query_dict.urlencode() +    return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + + +@register.simple_tag +def get_pagination_html(pager): +    return pager.to_html() + +  # Regex for adding classes to html snippets  class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])') | 
