2

(this is me sharing a solution i came up with for this fundamental issue i ran into when attempting to create page navigation)

This seems like such a basic thing that I can't believe it was so hard to find a basic solution to this.

Basically Pagination in django is pretty good, until you need to display the pages as navigation in a template. Then you have to create some obscene logic to accomplish the following

  • display adjacent x page numbers to the current one. e.g. 2 before and 2 after
  • determine when to display first page number
  • determine when to display last page number
  • highlight current page number
  • style this according to however you want

With a small number of pages the default pagination is fine, but as soon as you have a signficant number it becomes difficult to accomplish the above requirements.

I've posted a solution i found and modified to work with django 1.5+. If you have other solutions to share around this topic would love to see them. Thanks

w--
  • 6,427
  • 12
  • 54
  • 92
  • heh sorry. guess i could have worded the question better. intended this to be a stackoverflow wiki post. just wanted to share a solution i found and modified to suit my needs. – w-- Aug 26 '14 at 06:54

2 Answers2

1

Django provides you the basics on using the paginator object in a 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 %}

        <span class="current">
            Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
        </span>

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

The reason this isn't wrapped up in a tag is probably because django doesn't want to enforce a certain style/library on you (with the exception of jquery, but even that's optional).

Further deciding the style of pagination can be opinionated and differ from application and viewport. However, there are plenty of others that have tackled this problem. Two that come to mind immediately are django-endless-pagination and django-bootstrap-pagination.

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • Thanks. I find the basics don't work well for large numbers of pages. I don't want endless pagination ( I want page numbers but where presentation meta data is provided so i can style it easily) and i don't want to be tied into bootstrap (as I'm not using it) anyway, I intended this to be a wiki post and just wanted to share a solution i repurposed. – w-- Aug 26 '14 at 06:59
1

Well actually there are a bunch of answers floating around and in django snippets.
I came across this post: http://www.tummy.com/articles/django-pagination/

Which had the end result i wanted. I just needed to modify it so it would work with Generic Class Based Views, or at least when the same context variables were available.

I'm sure this can be improved, but as it is, I think this is pretty reusable.

  1. first off create a new app. e.g. my_pagination
  2. make sure this app is added to INSTALLED_APPS in your settings
  3. in this app folder make two folders: templatetags and templates
  4. in the templatetags folder make a file called paginator.py
  5. in the templates folder make a subfolder called (in this case my_pagination) and add a file called _paginator.html. (putting an underscore in front of filenames is my own convention for included template files)

code for paginator.py

#  Based on: http://www.djangosnippets.org/snippets/73/
#
#  Modified by Sean Reifschneider to be smarter about surrounding page
#  link context.  For usage documentation see:
#
#     http://www.tummy.com/Community/Articles/django-pagination/

# modified again by me to include target_url and work with django 1.7

from django import template
from django.core.paginator import InvalidPage

register = template.Library()

def paginator(context, adjacent_pages=2, target_url=None):
    """
    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.

    """
    startPage = max(context['page_obj'].number - adjacent_pages, 1)
    if startPage <= 3: startPage = 1
    endPage = context['page_obj'].number + adjacent_pages + 1
    if endPage >= context['paginator'].num_pages - 1: endPage = context['paginator'].num_pages + 1
    page_numbers = [n for n in range(startPage, endPage) \
            if n > 0 and n <= context['paginator'].num_pages]
    page_obj = context['page_obj']
    paginator = context['paginator']

    try:
        previous = context['page_obj'].previous_page_number()
    except InvalidPage:
        previous = None

    try:
        next = context['page_obj'].next_page_number()
    except InvalidPage:
        next = None


    return {
        'page_obj': page_obj,
        'paginator': paginator,
        'page': context['page_obj'].number,
        'page_numbers': page_numbers,
        'next': next,
        'previous': previous,
        'has_next': context['page_obj'].has_next(),
        'has_previous': context['page_obj'].has_previous(),
        'show_first': 1 not in page_numbers,
        # show last if the last page number is not in "page_numbers"
        'show_last': context['paginator'].num_pages not in page_numbers,
        'target_url' : target_url
    }

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

the code for _paginator.html can be whatever you want as long as you understand the logic (which is pretty straightforward) I'm using foundation 5 and fontawesome so my _paginator.html looks like

<ul class="pagination">
   {% if has_previous %}
   <li class="arrow"><a href="{{ target_url }}?page={{ previous }}" target="_parent"><i class="fa fa-angle-double-left fa-lg fa-fw"></i></a></li>
   {% else %}
   <li class="arrow unavailable"><i class="fa fa-angle-double-left fa-lg fa-fw"></i></li>
   {% endif %}

   {% if show_first %}
      <li><a href="{{ target_url }}?page=1" target="_parent">1</a></li>
      <li>...</li>
   {% endif %}

   {% for linkpage in page_numbers %}
      {% if linkpage == page %}
          <li class="current">{{ page }}</li>
      {% else %}
         <li><a href="{{ target_url }}?page={{ linkpage }}" target="_parent">{{ linkpage }}</a></li>
      {% endif %}
   {% endfor %}

  {% if show_last %}
      <li>...</li>
      <li><a href="{{ target_url }}?page={{ paginator.num_pages }}" target="_parent">{{ paginator.num_pages }}</a></li>
   {% endif %}
   {% if has_next %}
       <li class="arrow"><a href="{{ target_url }}?page={{ next }}" target="_parent"><i class="fa fa-angle-double-right fa-lg fa-fw"></i></a></li>
   {% else %}
      <li class="arrow unavailable"><i class="fa fa-angle-double-right fa-lg fa-fw"></i></li>
   {% endif %}
</ul>

How to use: in your template file make sure you do:

{% load paginator %}

to load the custom template tag

then where you want the pages to show up you simply do:

{% paginator 3 %}

Notes
1. target_url is simply because of some wierd requirement i had around iframes. You can simply leave this param blank.
2. I took out many of the params from the original because they didn't seem relevant.

w--
  • 6,427
  • 12
  • 54
  • 92