Pagination: it's not pretty, but it works
Django | 2008-07-30 |
I wanted a simpler solution than django-pagination. Django-pagination looks easy to implement, particularly thanks to Eric Florenzano's stellar screencast, but the actual installation process isn't very well-documented.
Besides, I wanted something that didn't require installing a lot of outside code, and I sure don't want to have to rely on symlinks. Django's documentation of the Paginator class makes it look so simple, after all.
In the urls.py
It's a little messy, but here are all of the possible urlpatterns I have that could lead to a categorized (or not) list of articles, all calling the list_all() method in my view:
urlpatterns = patterns('',
url(r'^category/(?P\w+)/$', views.list_all, name='articles_list_all'),
url(r'^category/(?P\w+)/page/(?P\w+)/$', views.list_all, name='articles_list_all'),
url(r'^page/(?P\w+)/$', views.list_all, name='articles_list_all'),
url(r'', views.list_all, name='articles_list_all'),
)
In the view
Import Django's Paginator, along with the models you'll need to populate the paginator class:
from django.core.paginator import Paginator
from articles.models import Article, Category
In that list method, I'm setting the category and page_id defaults as additional arguments:
def list_all(request, category=None, page_id=1):
template_name = 'list_all.html'
category_list = Category.objects.all().order_by('title')
article_list = article.objects.all().order_by('-created_at')
The 'category_list' you're seeing here is incidental to the pagination. It's used to populate a select in the template that allows users to filter the article list further.
If the user has selected a category to filter on, we set the category_id to that POST value, and reset the page_id to '1' (for now, that's the value of a hidden field in the template form). It probably makes just as much sense to set 'page_id = 1' here in the view, but I want the flexibility of being able to pass in a value from the form for now:
if request.method == 'POST':
category_id = request.POST['category']
page_id = request.POST['page_id']
else:
category_id = category
Filter the list of articles based on the category_id:
if category_id:
try:
article_list = article.objects.filter(articlecategory__category=category_id)
except ObjectDoesNotExist:
article_list = None
else:
category_id = 0
category_id = INT(category_id)
And then back to the pagination. Create a paginator object out of the article_list we've already defined, where '3' is the number of objects to display per page. The current page is the page attribute for the page_id we've passed in or set above. And the page range is a list of ... well, the range of pages (something like [1, 2, 3, 4] that we can iterate over in the template):
paginator = Paginator(article_list, 3)
page = paginator.page(page_id)
page_range = paginator.page_range
Then return all that stuff back to the template:
return render_to_response(template_name, { 'page_range': page_range, 'page': page,
'article_list': article_list, 'category_list': category_list, 'category_id': category_id, },
context_instance=RequestContext(request))
In the template
The form that lets a user select a category to filter by:
{% block content %}
Note the default page_id passed in with the POST.
If list_all() returns a page object, we loop over each object in that list and create a link to the detail view for each one:
{% if page.object_list %}
<ul>
{% for article in page.object_list %}
<li><a href="/articles/view/{{ article.id }}/">{{ article.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No articles available </p>
{% endif %}
Then here we're looping over the page_range to create a link for each available page with results:
Page:
{% for page_number in page_range %}
<a href="/articles/{% if category_id %}category/{{ category_id }}/{% endif %}page/{{ page_number }}/">{{ page_number }}</a>
{% endfor %}
{% endblock %}
Varying the number of items displayed
One more thing - if you wanted the option to show more than 3 articles at a time, instead of doing this in the view:
paginator = Paginator(article_list, 3)
You might do something like this instead:
paginator = Paginator(article_list, items_per_page)
Where 'items_per_page' is a value passed in from the template, allowing a user to select the number of items they want to see.