Paginator Tag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from django import template

register = template.Library()

def paginator(context, adjacent_pages=2):
    """
    To be used in conjunction with the object_list generic view.

    Adds pagination context variables for use in displaying first, adjacent and
    last page links in addition to those created by the object_list generic
    view.

    """
    page_numbers = [n for n in \
                    range(context['page'] - adjacent_pages, context['page'] + adjacent_pages + 1) \
                    if n > 0 and n <= context['pages']]
    return {
        'hits': context['hits'],
        'results_per_page': context['results_per_page'],
        'page': context['page'],
        'pages': context['pages'],
        'page_numbers': page_numbers,
        'next': context['next'],
        'previous': context['previous'],
        'has_next': context['has_next'],
        'has_previous': context['has_previous'],
        'show_first': 1 not in page_numbers,
        'show_last': context['pages'] not in page_numbers,
    }

register.inclusion_tag('paginator.html', takes_context=True)(paginator)

Comments

derivin (on March 12, 2007):

VERY COOL! This saved me a bunch of work. I did make a minor change (just a style difference really):

page_numbers = range(max(0, context['page']-adjacent_pages), min(context['pages'], context['page']+adjacent_pages)+1)

#

RichardBronosky (on July 18, 2007):

(This is my first python contribution. I hope it is correct and useful. Please email me at [myFirstName]@[myLastName].com with any corrections.)

I've patched this to add:

  • results_this_page - On the last page, there will usually be less than results_per_page results to display
  • first_this_page - the starting point of the slice that object_list represents
  • last_this_page - the ending point of the slice that object_list represents

As a result, I can use the following paginator.html template:

Showing {{ first_this_page }}-{{ last_this_page }} of {{ hits }} hits on page {{ page }} of {{ pages }}

To get these results on the pages:

  1. Showing 1-20 of 46 hits on page 1 of 3
  2. Showing 21-40 of 46 hits on page 2 of 3
  3. Showing 41-46 of 46 hits on page 3 of 3

I'd also like to throw out a NOTICE that in order to implement this snippet, you must follow http://www.djangoproject.com/documentation/templates_python/#extending-the-template-system As a new user to both Python and Django, I was trying to out this in my views.py, which does not work. I hope that saves someone from wasting the time I did.

BEGIN PATCH

--- pagination.original.py  2007-07-18 13:33:02.000000000 -0400
+++ pagination.py   2007-07-18 13:31:10.000000000 -0400
@@ -2,7 +2,9 @@

 register = template.Library()

+@register.inclusion_tag('paginator.html', takes_context=True)
 def paginator(context, adjacent_pages=2):
+    print context
     """ 
     To be used in conjunction with the object_list generic view.

@@ -14,9 +16,14 @@
     page_numbers = [n for n in \
                     range(context['page'] - adjacent_pages, context['page'] + adjacent_pages + 1) \
                     if n > 0 and n <= context['pages']]
+    results_this_page = context['object_list'].__len__()
+    range_base = ((context['page'] - 1) * context['results_per_page'])
     return {
         'hits': context['hits'],
         'results_per_page': context['results_per_page'],
+        'results_this_page': results_this_page,
+        'first_this_page': range_base + 1,
+        'last_this_page': range_base + results_this_page,
         'page': context['page'],
         'pages': context['pages'],
         'page_numbers': page_numbers,
@@ -27,5 +34,3 @@
         'show_first': 1 not in page_numbers,
         'show_last': context['pages'] not in page_numbers,
     }   
-   
-register.inclusion_tag('paginator.html', takes_context=True)(paginator)

#

RichardBronosky (on July 18, 2007):

Oh yea, in that patch I also changed it to use a decorator. I don't know much about python, but I think that is appropriate.

#

RichardBronosky (on July 20, 2007):

I think this is a very important improvement. I hate having to keep adding to the context list as my app matured. I should only have to add new context to my views. So, I came up with this.

To pass the entire context with modifications/additions to the template of an inclusion tag, you can use:

return dict(context.dicts[5], existing_key='val to overwrite old val', new_key='new_val')

#

msurdi (on April 10, 2008):

Could you please post a "view" example for this snippet?

#

Romain Hardouin (on June 11, 2008):

Example of a view

def news_by_category(request, category_name, page=1):
    category = get_object_or_404(Category, name__iexact=category_name)
    news = News.objects.select_related(depth=1).filter(category=category.id)

    from django.views.generic import list_detail
    return list_detail.object_list(request,
                               queryset = news,
                               template_name = 'template_name.html',
                               paginate_by = 10,
                               page = page,
                               allow_empty = False,
                               extra_context = {'category': category}
                           )

View parameters and paginator.html?

How this template could be design in order to paginate any resource?

For instance, if you want to paginate instance of News model, then instance of Category model and so forth, links in paginator must be dynamics. Till now I can't figure out how to do this neat and clean.

  • paginator.html -- and thus its template Context -- must be 'view aware' to build links
  • Then, with a paginator template view aware, we can use the template tag {% url %} to generate links
  • Sounds good but this template tag requires view parameters...

So, how transmit those parameters to the template?

#

mzee (on October 8, 2008):

nice stuff

#

sugi (on November 14, 2008):

Can you suggest some more examples on using templatetags and how to use URLS for this.

Help me

#

sylvain (on January 11, 2009):

For an example, see the djblets datagrid template tag: http://svn.navi.cx/misc/trunk/djblets/djblets/datagrid/

Note that in Django 1.0, the paginator attributes should be accessed through page_obj and paginator.

So the paginator function should look like:

page_obj = context['page_obj']
paginator = context['paginator']
...
return {
    'page_obj': page_obj,
    'paginator': paginator,
    'page_numbers': page_nums,
    'show_last': context['pages'] not in page_nums,
}

And the template can access properties through page_obj and paginator. For instance, instead of {{ pages }} you would use {{ paginator.num_pages }}

#

jafo (on October 30, 2009):

I changed the page_numbers so that it would be a bit smarter and include the first page and the last page if it would otherwise include the page or two next to the first or last page. So, instead of getting page_numbers being [2,3,4] or [3,4,5], it would be [1,2,3,4] and [1,2,3,4,5].

I did this for doing flickr (and apparently digg)-like paginators, so you don't get things like [1, '...', 2, 3, 4] or [1, '...', 3 4 5] -- might as well just put a "2" page link in there instead of "...".

Here is the code change I came up with:

startPage = max(context['page'] - adjacent_pages, 1)
if startPage &lt;= 3: startPage = 1
endPage = context['page'] + adjacent_pages + 1
if endPage &gt;= context['pages'] - 1: endPage = context['pages'] + 1
page_numbers = [n for n in range(startPage, endPage) \
        if n > 0 and n &lt;= context['pages']]

So for my paginator.html I use:

<div class="pager">
   {% if has_previous %}
      <span class="page">
      <a href="?page={{ previous }}">&lt; Prev</a>
      </span>
   {% endif %}

   {% if show_first %}
      <span class="page"><a href="?page=1">1</a></span>
      <span class="elipsis">...</span>
   {% endif %}
   {% for linkpage in page_numbers %}
      {% ifequal linkpage page %}
         <span class="current">{{ page }}</span>
      {% else %}
         <span class="page"><a href="?page={{ linkpage }}"
               >{{ linkpage }}</a></span>
      {% endifequal %}
   {% endfor %}
   {% if show_last %}
      <span class="elipsis">...</span>
      <span class="page"><a href="?page=last">{{ pages }}</a></span>
   {% endif %}
   {% if has_next %}
      <span class="page"><a href="?page={{ next }}">Next &gt;</a></span>
   {% endif %}
</div>

Sean

#

jafo (on November 1, 2009):

FYI: I have created full documentation on using my slightly modified version of this, including where to put all the pieces, and examples of the HTML and CSS for the paginator.html and tag calls, as well as the view.

You can find it at A Recipe for Pagination in Django.

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.