Pagination

A brand-new TaskFlow install might have a dozen tasks. A TaskFlow install six months into real use could easily have thousands. Rendering every single one of them on a single page load is a poor experience for whoever's scrolling through it, and a real performance problem for the server — fetching thousands of rows from the database and rendering thousands of HTML fragments on every request, for every visitor, every time. Pagination is the standard fix: split a long list into fixed-size pages, and only fetch and render the one page currently being viewed.

The Paginator Class

Django's built-in Paginator class takes any queryset (or any list-like object) and a page size, and handles the splitting for you:

# taskflow/views.py
from django.core.paginator import Paginator
from .models import Task

def task_list(request):
    tasks = Task.objects.all().order_by('due_date')
    paginator = Paginator(tasks, 10)
    page_obj = paginator.get_page(request.GET.get('page', 1))

    return render(request, 'taskflow/task_list.html', {'page_obj': page_obj})

Paginator(tasks, 10) doesn't fetch anything from the database by itself — it just knows the full queryset and the page size, and works out how many pages that adds up to.

Reading the Requested Page From the URL

request.GET.get('page', 1) reads a ?page= query string parameter off the URL — visiting /tasks/?page=3 requests the third page, and visiting /tasks/ with no query string at all falls back to page 1, thanks to that default argument. paginator.get_page(...) then does the actual database query, fetching only the ten rows that belong to that specific page — never the whole table.

The Page Object's Useful Properties

page_obj, the object get_page() returns, carries everything a template needs to render both the current page's rows and the navigation controls around them:

PropertyGives you
page_obj.object_listThe actual rows for this page — the ten Task objects to loop over
page_obj.has_next()True if a page after this one exists
page_obj.has_previous()True if a page before this one exists
page_obj.numberThe current page's number
page_obj.paginator.num_pagesThe total number of pages, across the whole queryset

Rendering Pagination Controls in a Template

page_obj can be looped over directly — Django lets it stand in for its own object_list in a {% for %} loop — and the has-next / has-previous checks drive the Previous and Next links, each one carrying the right page number in its own ?page= link:

<ul>
{% for task in page_obj %}
    <li>{{ task.title }} — due {{ task.due_date }}</li>
{% endfor %}
</ul>

<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
    {% endif %}

    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}

    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">Next</a>
    {% endif %}
</div>

The {% if %} checks around each link matter — without them, a Next link on the last page would point at a page number that doesn't exist. Wrapping each link in its matching has_next / has_previous check keeps the controls honest at both ends of the list.

The Shortcut for Class-Based Views

Everything above is the manual, function-based-view approach. If TaskFlow's task list is instead built as a ListView (covered in Class-Based Views), pagination collapses down to a single class attribute:

# taskflow/views.py
from django.views.generic import ListView
from .models import Task

class TaskListView(ListView):
    model = Task
    paginate_by = 10

That one line — paginate_by = 10 — does everything the manual Paginator setup above does: it reads ?page= from the URL, fetches only the current page's rows, and hands the template a page_obj with the exact same properties described above, all without a single line of pagination code in the view itself. For the common case of paginating one model's full list, this is significantly less code than wiring up Paginator by hand.

Next up: So far every TaskFlow view has treated every visitor the same way. User Authentication introduces logging in, logging out, and telling visitors apart — a genuinely big topic, next.