Floating Page Numbers in Django

Posted (Updated ) in Uncategorized

If you’re paginating your data and have tens or hundreds of pages, chances are you’d prefer only a subset of these be displayed in your page number list (example – see page list at bottom). Django-paginator gets 90% of your work done for you however it doesn’t support this one very useful feature. To make a developers life easier, meet flynsarmy_paginator!

flynsarmy_pagination
flynsarmy_pagination in action

 

Download it here.

This app is a simple subclass of django’s built in Paginator and Page classes. The only changes I’ve made are to add an optional adjacent_pages parameter to FlynsarmyPaginator and a .page_range_data variable to the FlynsarmyPage object with the following contents:

  • page_range – same as Paginator.page_range – a list of page numbers but only showing a subset of pages if adjacent_pages was specified in the FlynsarmyPaginator constructor.
  • show_first – Boolean value set to True if ‘1’ isn’t present in the page_range list above.
  • show_last – Boolean vlaue set to True if the last page isn’t present in the page_range list above.

Usage

Add to your INSTALLED_APPS in settings.py:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    ...,
    'flynsarmy_paginator',
)

An example view (taken mostly from the docs)

from django.core.paginator import EmptyPage, PageNotAnInteger
from flynsarmy_paginator.paginator import FlynsarmyPaginator

def listing(request):
    contact_list = Contacts.objects.all()
    paginator = Paginator(contact_list, 25, adjacent_objects=6) # Show 25 contacts per page

    page = request.GET.get('page')
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        contacts = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        contacts = paginator.page(paginator.num_pages)

    return render_to_response('list.html', {"contacts": contacts})

and template:

{% for contact in contacts %}
	{# Each "contact" is a Contact model object. #}
	{{ contact.full_name|upper }}<br />
	...
{% endfor %}

<div class="pagination">
	<span class="step-links">
		{% if contacts.has_previous %}
			<a href="?page={{ contacts.previous_page_number }}">previous</a>
		{% endif %}

		{% if contacts.page_range_data.show_first %}
			<a href="?page=1">1</a>
			<span class="ellipsis">...</span>
		{% endif %}
		{% for i in contacts.page_range_data.page_range %}
			{% ifequal i contacts.number %}
				{{ i }}
			{% else %}
				<a href="?page={{ i }}">{{ i }}</a>
			{% endifequal %}
		{% endfor %}
		{% if contacts.page_range_data.show_last %}
			<span class="ellipsis">...</span>
			<a href="?page={{ contacts.paginator.num_pages }}">{{ contacts.paginator.num_pages }}</a>
		{% endif %}

		{% if contacts.has_next %}
			<a href="?page={{ contacts.next_page_number }}">next</a>
		{% endif %}
	</span>
</div>

I’d like to give a big thanks to the people of tummy.com for writing most of the logic behind the .page_range_data.page_range function so I didn’t have to!