This code will throw deprecation warnings in newer Django checkouts – see the Paginator Update post for an improved version that should work with the recent trunk.

These days I had to implement pagination with Django for the first time. Actually, that’s not entirely true – but thus far it had always been simple cases where the object_list() generic view and next/previous links were good enough. This time around, I wanted something more advanced.

It turns out that Django’s pagination code is not that great. It does it’s core job very well, but it is not stateful in that it doesn’t know anything about the current page – instead, you have to pass that value along with each function call. Which means that out of the box it’s pretty much unusable from within a template. Also, I wanted a more advanced pagination that would display a sensible range of pages for direct selection – apparently, this is now officially called “digg-style”.

There is at least one such paginator for Django around, but it’s based on an inclusion tag – a concept I am not too fond of. Somehow, I don’t like the idea of Python code (the view) referring to a template, referring back to Python code (the tag), referring back to a template, and maybe going even further from there. I like knowledge of templates and their locations to be restricted to my view layer.

So I decided to give a different approach a shot, putting an object into the template context that could be used to build the pagination. I’ve posted it to DjangoSnippets.

Use it like this:

objects = MyModel.objects.all() paginator = DiggPaginator(objects, 10, body=6, padding=2, page=7) return render_to_response('template.html', {'paginator': paginator}

The paginator will provide one to three blocks of pages (first pages, last pages, and adjacents of the current page). Depending on the total number of pages, the active page and the paginator options, those blocks may be merged together). body is the size of the block that contains the currently active page, tail the number of pages in the leading and trailing blocks. padding determines the minimum number of options to the left or right of the active page. This is only relevant when it comes to expanding a tail block into the middle block, as the padding of the latter is defined by it’s size (body). The last option, margin, defines when two blocks are merged. It’s the minimum number of pages required between two blocks. If margin is 3 while the start block ends with page 9, and the middle block starts with 11, the two blocks will end up as one.

A template would look something like the following. Pretty much all the variables the object_list generic view provides are available as attributes.

{% if paginator.has_next %}{# pagelink paginator.next #}{% endif %} {% for page in paginator.page_range %} {% if not page %} ... {% else %}{# pagelink page #} {% endif %} {% endfor %}

Here is an example in action.