1.PageNumberPagination, LimitOffsetPagination, and the metadata
Pagination cuts a list response into pages. `PageNumberPagination` numbers them — `?page=3` — and `LimitOffsetPagination` takes a window — `?limit=50&offset=100`. Both wrap the results in the same metadata envelope: `count` (how many rows matched in total), `next` and `previous` (full URLs, or `null` at the ends), and `results` (the rows themselves). Both are opt-in: `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` are both `None` out of the box, so an unpaginated list endpoint returns every row it matched — which is fine on a table of twenty and a serious problem on a table of two million.
Key features
- `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` both default to `None`; setting the class without also setting `PAGE_SIZE` (or `page_size` on the class) leaves pagination inactive
- `PageNumberPagination` reads `?page=`; `page_size_query_param` is `None` by default, so clients cannot change the page size until you name it
- `LimitOffsetPagination` reads `?limit=` and `?offset=`, with `default_limit` falling back to `PAGE_SIZE` and `max_limit` defaulting to `None` — unbounded until you set it
- The envelope is `{"count", "next", "previous", "results"}` for both; `next`/`previous` are absolute URLs built from the current request, and are `null` at each end
- `count` is a separate `SELECT COUNT(*)` on the filtered queryset, run on every request — on a large table it can dominate the response time
- Pagination runs after filtering and ordering, so `count` and the links always describe the *filtered* set
The two offset-based classes
- Page 1 — cheap — The database reads the first 50 rows in order and stops. An index on the ordering column serves this directly.
- Page 2 — still cheap — Fifty rows are produced and discarded before the fifty that are returned. Barely measurable at this depth.
- Page 900 — expensive — Forty-four thousand nine hundred and fifty rows are produced, sorted, and thrown away to return fifty. Cost grows linearly with depth.
- And the response shape never changes — The client sees the same envelope whether the page cost 8ms or 12s — which is exactly why deep pagination goes unnoticed until it is a production incident.
Command
class OrderPagination(PageNumberPagination):
page_size = 50
page_size_query_param = "page_size"
max_page_size = 200Both offset classes compile to `LIMIT … OFFSET …`, and `OFFSET n` produces and discards n rows — so page cost grows with depth, and an insert between requests shifts every later position, letting a row appear twice or not at all. `count` is a separate `COUNT(*)` on every request. Setting `DEFAULT_PAGINATION_CLASS` without `PAGE_SIZE` leaves pagination inert, and exposing `page_size_query_param` without `max_page_size` hands the caller your memory limit.