Super simple pagination, WITHOUT using Django's Paginator object

Posted by: barbara | Date: Sep 25, 2008 | Category: django python    
Once again, I've been struggling with pagination. It's not that I don't like Django's Paginator object ... it's that ... well, okay, I don't like it. Anyway, I just needed something painfully simple for this site, and Paginator seemed a little like overkill.

So here's my super-simple example - this is the code for the pagination that's on this very blog:

The view:

def list(request, category=None, username=None, date=None):
    """
    List all entries, paginated
    """
    template_name = 'list.html'
    context = {}
    per_page = 5
    page = INT(request.GET.get('page', '1'))

    if category:
        context['category'] = category
        grouped_list = entries_by_category(request, category)
    elif username:
        context['author'] = username
        try:
            user = User.objects.get(username=username)
            grouped_list = Post.objects.filter(author=user.id).order_by('-created_at')
        except ObjectDoesNotExist:
            grouped_list = None
    elif date:
        context['date'] = date
        grouped_list = Post.objects.filter(created_at__startswith=date, publish=True).order_by('-created_at')
    else:
        grouped_list = Post.objects.filter(publish=True).order_by('-created_at')

    for entry in grouped_list:
        entry.category_list = Category.objects.filter(postcategory__post__pk=entry.id)
        entry.comments = Comment.objects.filter(post=entry.id)

    total_entries = grouped_list.count()
    total_pages = (total_entries/per_page)+1
    context['page_range'] = range(1, total_pages+1)

    offset = (page * per_page) - per_page
    limit = offset + per_page
    entry_list = grouped_list[offset:limit]
    context['entry_list'] = entry_list

    return render_to_response(template_name, context, context_instance=RequestContext(request))

def entries_by_category(request, category):
    try:
        post_category = Category.objects.get(slug=category)
    except ObjectDoesNotExist:
        post_category = None
    if post_category: entry_list = Post.objects.filter(postcategory__category=post_category.id, publish=True)
    return entry_list


In the template:

<div class="pagination" align="center">
    {% for page in page_range %}
        <a href="/{% if category %}category/{{ category }}/{% endif %}{% if author %}author/{{ author }}/{% endif %}{% if date %}date/{{ date }}/{% endif %}?page={{ page }}">{{ page }}</a>
    {% endfor %}
</div>


And for what it's worth, although it's probably obvious, these are the corresponding patterns in my urls.py:

    url(r'^category/(?P.*?)/*$',    views.list,             name='entry_list'),
    url(r'^author/(?P.*?)/*$',      views.list,             name='entry_list'),
    url(r'^date/(?P.*?)/*$',            views.list,             name='entry_list'),
    url(r'^$',                                views.list,             name='entry_list'),
I'm guessing you left some keyword arguments out of the URL patterns, since, as written, they will always pass "category" and "username", never "date". Or maybe I'm missing something. However, the bit that did jump out here is using list() as a function name. Since that's also a Python builtin, at some point in the future you'll accidentally call list() inside that module to convert a tuple to a list and spend half an hour debugging the problem. Worth programming defensively here and choosing a different name from the start, I suspect.
Comment by Paul Kenjora on Oct 08, 2008:
Have you considered implementing pagination purely template side. A template tag would do the trick. Queries are conserved and the code is much more portable. http://blog.awarelabs.com/?p=29