Pagination with Paginator
Pagination is the technique of splitting a long list of database rows into numbered pages so a visitor sees a manageable slice at a time instead of one enormous scroll.
Learn Pagination with Paginator in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll use Django's Paginator to slice a QuerySet, read the requested page from the ?page= query parameter, build next and previous links from a page object, handle bad page numbers safely, and let a ListView paginate for you with a single attribute.
You create a Paginator by handing it two things: the list (or QuerySet) you want to split and how many items belong on each page. The Paginator then computes the total count , the number of pages ( num_pages ), and a page_range you can loop over to render page-number links.
paginator.page(number) returns a Page object. In a template you iterate it directly to render the rows, and you ask it questions to build navigation: page_obj.has_next , page_obj.has_previous , and the neighbouring numbers next_page_number and previous_page_number .
The ?page= value comes from the URL, so a user (or a crawler) can send anything: ?page=abc , ?page=999 , or nothing at all. Django gives you two exceptions to catch — PageNotAnInteger for non-numeric input and EmptyPage for out-of-range numbers — and the convention is to fall back to the first or last page.
A class-based ListView makes this even shorter. Set paginate_by and Django builds the Paginator, reads ?page= , and adds page_obj and is_paginated to the context automatically.
A ListView with paginate_by = 10 splits 95 items into pages of ten. Fill in the blank with the division operator so the count rounds up correctly.
You passed a page number larger than num_pages (often from ?page=999 ) straight to paginator.page() .
✅ Fix: wrap the call in try/except EmptyPage and fall back to paginator.page(paginator.num_pages) .
❌ PageNotAnInteger when ?page= is missing or text
request.GET.get("page") returns None or a string like "abc" , which paginator.page() can't use.
✅ Fix: catch PageNotAnInteger and fall back to paginator.page(1) .
❌ Next/Prev links break the filters or sort order
A link of just ?page=2 drops any other query parameters like ?q=django .
✅ Fix: preserve the rest of the query string, e.g. build links that keep request.GET and only swap the page value.
Write a helper that prints a full pagination bar — a Prev link, every page number with the current one highlighted, and a Next link — for any total, page size, and current page.
Lesson complete — your lists scale to any size!
You can build a Paginator , pull a single Page with paginator.page() , read ?page= from the URL, render Next/Prev links from has_next and has_previous , survive bad page numbers with EmptyPage and PageNotAnInteger , and let a ListView do it all with paginate_by .
🚀 Up next: The Sessions Framework — remember data about a visitor across requests using request.session .
Practice quiz
Which class splits a list or QuerySet into numbered pages?
- Paginator
- PageSplitter
- QuerySet
- ListView
Answer: Paginator. django.core.paginator.Paginator takes an object list and a per-page count.
How do you create a Paginator with 5 posts per page?
- Paginator(5, posts)
- Paginator(posts, 5)
- Paginator(posts).limit(5)
- Paginator(posts, page=5)
Answer: Paginator(posts, 5). The second positional argument is the number of items per page, e.g. Paginator(posts, 5).
Which attribute holds the total number of pages?
- paginator.count
- paginator.page_count
- paginator.total
- paginator.num_pages
Answer: paginator.num_pages. paginator.num_pages is the total number of pages; paginator.count is the total number of objects.
Which method returns a Page object for a given page number?
- paginator.get(n)
- paginator.page(n)
- paginator.at(n)
- paginator.fetch(n)
Answer: paginator.page(n). paginator.page(n) returns the Page object for page number n.
Which exception is raised when the requested page number is out of range?
- PageNotAnInteger
- IndexError
- EmptyPage
- DoesNotExist
Answer: EmptyPage. paginator.page() raises EmptyPage when the page number is too large or too small.
Which exception is raised when ?page= is not a number?
- EmptyPage
- PageNotAnInteger
- ValueError
- TypeError
Answer: PageNotAnInteger. paginator.page() raises PageNotAnInteger when the value cannot be read as an integer.
Which Page attribute is True when a following page exists?
- page_obj.has_next
- page_obj.more
- page_obj.next_exists
- page_obj.is_last
Answer: page_obj.has_next. page_obj.has_next is True when there is a page after the current one.
Where does the requested page number usually come from in a view?
- page
The page number arrives in the query string ?page=, read via request.GET.get('page').
Which ListView attribute turns pagination on?
- page_size
- per_page
- pagination
- paginate_by
Answer: paginate_by. Setting paginate_by on a ListView makes Django build the Paginator automatically.
How is num_pages computed from count and per_page?
- count divided down (floor)
- count minus per_page
- count divided and rounded up (ceil)
- count times per_page
Answer: count divided and rounded up (ceil). The page count is the total divided by per-page size, rounded up: ceil(count / per_page).