← Back to articles

Django · Pagination

Django Pagination: Order Rows and Choose an Invalid-Page Policy

Give paginated archives an explicit order, deliberate invalid-page handling and navigation that preserves active filters.

Ordered emerald record tiles pass a cyan sorting gate into three blue page trays with navigation arrows.
Order the records before splitting them into pages.

An article archive needs more than a limit on the number of rows. Readers also need a predictable sequence and a clear result when a page parameter is invalid.

Order before dividing

For an illustrative Article model, start with published rows and an explicit order:

from django.core.paginator import Paginator

articles = Article.objects.filter(is_published=True).order_by(
    "-published_at", "-pk"
)
page_obj = Paginator(articles, 12).get_page(request.GET.get("page"))

The primary key breaks ties when articles share a publication date. This makes the ordering unambiguous for a fixed dataset; inserts and deletions can still shift records between page-number requests.

Choose the invalid-input policy

Django's get_page() returns the first page for nonnumeric input and the last page for negative or excessively high numbers. That forgiving behavior suits many browser archives. Use page() and handle its exceptions when the interface requires an explicit invalid-page response instead.

Keep navigation tied to the page

Render page_obj and show next or previous links only when the corresponding has_next() or has_previous() check succeeds. Preserve active search and topic parameters when constructing those links so moving forward does not reset the reader's filters.

Check realistic boundaries

Try an empty archive, tied publication dates, the final partial page and malformed page input. For a large archive, measure high page numbers: database OFFSET work can grow even though each response displays only twelve records.