skip to content

What does setting paginate_by on a Django ListView add to the template context, and which attributes tune the pagination?

level: middleimportance: should knowfreq 48%

answer

  1. four context keys
  2. object_list becomes the page
  3. page_kwarg from URL or query string
  4. orphans and empty lists

basics

~10 s

With paginate_by set, ListView builds a Paginator and adds paginator, page_obj and is_paginated, and object_list becomes just the current page. page_kwarg, paginate_orphans, allow_empty and paginator_class tune it.

solid answer

~30 s

`MultipleObjectMixin.get_context_data()` checks `get_paginate_by()`. If it returns a number, `paginate_queryset()` builds `paginator_class(queryset, paginate_by, orphans=paginate_orphans, allow_empty_first_page=allow_empty)` and the context gets `paginator`, `page_obj`, `is_paginated` (true only when there is more than one page) and `object_list` replaced by the page's rows, which also feed the `<model>_list` name. Without pagination, `paginator` and `page_obj` are `None`. The page number comes from `self.kwargs[page_kwarg]` or `request.GET[page_kwarg]`, `page_kwarg` defaulting to `'page'`. `paginate_orphans` folds a short last page into the previous one; `allow_empty = False` turns an empty list into a 404. Override `get_paginate_by()` to let users pick a page size, clamped to a maximum.

code

python · 17 lines
python
from django.views.generic import ListView

from .models import AuditEntry


class AuditLogView(ListView):
    model = AuditEntry
    ordering = ['-created_at', '-id']
    paginate_by = 50
    paginate_orphans = 5

    def get_paginate_by(self, queryset):
        try:
            size = int(self.request.GET.get('per_page', self.paginate_by))
        except ValueError:
            return self.paginate_by
        return max(1, min(size, 200))

go deeper

for a junior

Recall that paginate_by turns on pagination and the template gets page_obj for the navigation links.

for a middle

List the four context keys, explain that object_list becomes the page, and describe page_kwarg, paginate_orphans and allow_empty.

for a senior

Clamp any user-controlled page size, swap paginator_class when the count is expensive, and know that orphans at or above the page size is deprecated since 6.0.

for a principal

Set site-wide defaults for page size, maximum size and URL style so every list endpoint behaves and performs the same.

## Turning pagination on `ListView` gets its list behaviour from `MultipleObjectMixin`. Pagination is off until `paginate_by` (or `get_paginate_by()`) returns a number. Then, during `get_context_data()`, the mixin calls `paginate_queryset()`, which builds a paginator and picks the page. ## What the template receives | Key | Paginated | Not paginated | |---|---|---| | `paginator` | the `Paginator` instance | `None` | | `page_obj` | the current `Page` | `None` | | `is_paginated` | `True` if there is more than one page | `False` | | `object_list` | only the current page's rows | the whole QuerySet | | `<model>_list` (e.g. `auditentry_list`) | same as `object_list` | same as `object_list` | Because `object_list` is already the page, a template written for an unpaginated list keeps working after `paginate_by` is added; only the navigation links need `page_obj`. ## The tuning attributes - **`paginate_by`**: the page size, or `None` for no pagination. - **`page_kwarg`** (default `'page'`): the name to read the page number from. The view looks in the URL keyword arguments first (`path('audit/page/<int:page>/', ...)`), then in the query string (`?page=3`). - **`paginate_orphans`** (default 0): passed to the paginator as `orphans`. If the last page would hold that many items or fewer, they are added to the previous page instead. - **`allow_empty`** (default `True`): passed as `allow_empty_first_page`. When `False`, an empty list raises `Http404` instead of rendering an empty page. - **`paginator_class`** (default `Paginator`): swap in a subclass, for example one with a cheaper `count`. - **Hooks:** `get_paginate_by(queryset)`, `get_paginate_orphans()`, `get_allow_empty()`, `get_paginator()` for per-request logic. ## How orphans change the page count The paginator computes `num_pages = ceil(max(1, count - orphans) / per_page)`. With 23 audit entries, `per_page=10`: 1. `orphans=0`: three pages of 10, 10 and 3. 2. `orphans=3`: two pages of 10 and 13, since the 3 leftovers join page 2. 3. `orphans=2`: three pages again, because 3 leftovers exceed the allowance. Since Django 6.0, an `orphans` value greater than or equal to `per_page` is deprecated and will raise `ValueError` in Django 7.0; it made little sense anyway. ## Invalid page numbers `ListView` is strict: the literal `last` means the final page, any other non-integer is a 404, and an out-of-range number is a 404, because it uses `Paginator.page()` rather than the forgiving `get_page()`. ## Keeping filters in the links Most list pages combine pagination with filters in the query string, such as `?actor=42&page=3`. `ListView` only reads the page number; building the "next" and "previous" links is the template's job. A link that writes only `?page=4` drops the filter and moves the user to page 4 of the unfiltered audit log. Build links that keep the other query parameters, for example with a small template tag or by passing the encoded filter string in the context. ## A user-selectable page size Override `get_paginate_by()` to read `?per_page=` from `request.GET`, convert it safely, and clamp it to a maximum such as 200. Without the clamp, one request with `per_page=2000000` asks the database for the entire audit log in one page.

  • Why does is_paginated matter if page_obj is always present when paginate_by is set?
    `page_obj` exists even when everything fits on one page. `is_paginated` is `True` only when there is more than one page, so templates use it to hide the navigation bar on short lists.
  • How would you put the page number in the path instead of the query string?
    Add a URL pattern with a keyword argument, such as `<int:page>`, whose name matches `page_kwarg`. `paginate_queryset()` reads `self.kwargs[page_kwarg]` before falling back to `request.GET`, so no view code changes.

saying these in an interview costs you the question

  • object_list still contains every row when paginate_by is set
  • ListView only reads the page number from the query string
  • paginate_orphans adds extra rows to every page
  • is_paginated is true whenever paginate_by is set
  • allow_empty = False hides the pagination links on empty pages