In Django REST Framework, how do you enable pagination for list endpoints, and what does PageNumberPagination put in the response?
answer
- off by default, two settings
- one without the other does nothing
- ?page=N and a special word
- four keys wrap the results
basics
~10 sDRF paginates nothing by default: set DEFAULT_PAGINATION_CLASS and PAGE_SIZE, or pagination_class on a view. PageNumberPagination reads ?page=N and returns count, next, previous and results; a page beyond the end is a 404.
solid answer
~40 sBoth `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` default to `None`, so list endpoints return a bare JSON array until you set both, for example `"rest_framework.pagination.PageNumberPagination"` with `PAGE_SIZE = 50`; setting only `PAGE_SIZE` triggers DRF's system check warning `rest_framework.W001`, and a class with no page size simply doesn't paginate. A generic view can override with `pagination_class` (or `None` to switch it off). `PageNumberPagination` reads `?page=N`, accepts `?page=last`, and wraps the page as `{"count", "next", "previous", "results"}` with absolute URLs for the links. A page number past the end, or a non-number, raises `NotFound` (404, "Invalid page."). Pagination is applied automatically only by `ListModelMixin.list()` in generic views and viewsets; a plain `APIView` must call the paginator itself.
code
python · 17 linesfrom rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from .models import Activity
from .serializers import ActivitySerializer
class ActivityPagination(PageNumberPagination):
page_size = 25
page_size_query_param = "page_size"
max_page_size = 100
class ActivityList(generics.ListAPIView):
queryset = Activity.objects.order_by("-created", "-id")
serializer_class = ActivitySerializer
pagination_class = ActivityPaginationgo deeper
Recall the two settings, that both default to None, and the count, next, previous and results keys.
Explain how ListModelMixin.list() calls the paginator, why APIView does not paginate, and how invalid pages become 404.
Make ordering deterministic, bound client page sizes, and decide which endpoints need a different style from the project default.
Set one pagination envelope as part of the API contract and govern when an endpoint may deviate from it.
## Pagination is off until two settings agree Django REST Framework (DRF) splits pagination across two keys of the `REST_FRAMEWORK` settings dictionary, and **both default to `None`**: - `DEFAULT_PAGINATION_CLASS` — which **pagination class** generic list views use; - `PAGE_SIZE` — how many items a page holds, read by the built-in classes as their default page size. ```python # settings.py REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 50, } ``` The two fail differently when only one is set: | Configured | Result | |---|---| | neither | list endpoints return a plain JSON array of every row | | `PAGE_SIZE` only | still unpaginated; `manage.py check` reports warning `rest_framework.W001` | | class only | `PageNumberPagination` has no page size, so `paginate_queryset()` returns `None` and the list is unpaginated | | both | every generic list view is paginated | ## Where pagination actually happens Pagination is wired into **generic views**, not into `APIView`: 1. `GenericAPIView.pagination_class` defaults to `DEFAULT_PAGINATION_CLASS`. 2. `ListModelMixin.list()` filters the queryset and calls `self.paginate_queryset(queryset)`. 3. If that returns a page, it serialises the page with `many=True` and returns `self.get_paginated_response(serializer.data)`. 4. If it returns `None` (no class, or no page size), it serialises the whole queryset. So `ListAPIView`, `ListCreateAPIView` and `ModelViewSet.list` paginate automatically; detail actions never do, and a hand-written `APIView` must instantiate a paginator itself. Setting `pagination_class = None` on one view opts it out. ## What `PageNumberPagination` reads and returns - Query parameter `page` (`page_query_param`), defaulting to page 1. - The word `last` (from `last_page_strings`) jumps to the final page. - `page_size_query_param` is `None` by default, so clients **cannot** change the page size unless you name a parameter such as `page_size` in a subclass — and then you should also set `max_page_size`. The response body for `GET /api/activity/?page=3` looks like: ```json { "count": 1023, "next": "https://api.example.org/api/activity/?page=4", "previous": "https://api.example.org/api/activity/?page=2", "results": ["..."] } ``` `next` and `previous` are absolute URLs built from the current request, or `null` at either end. `count` is the total across all pages, which costs a count query on every request. ## Invalid pages Under the hood `PageNumberPagination` uses Django's `Paginator` (its `django_paginator_class`). When `Paginator.page()` rejects the number — beyond the last page, zero, or not an integer — DRF raises `NotFound`, so the client gets **404** with a detail beginning "Invalid page.". This differs from `LimitOffsetPagination`, which answers an offset beyond the end with 200 and an empty `results` list. ## Ordering matters Page boundaries are only stable if the queryset has a deterministic order. An unordered queryset makes Django's paginator emit an `UnorderedObjectListWarning`, and rows can repeat or vanish between pages. Give the model a `Meta.ordering` or call `order_by()` with a unique tiebreaker such as `"-created", "-id"`. ## Choosing the class per view - Keep one project default so clients see a consistent envelope. - Override `pagination_class` per view when an endpoint needs a different style — for example `CursorPagination` for an infinite-scroll activity feed. - Subclass a built-in to change `page_size`, parameter names or the maximum page size, rather than rewriting pagination logic.
- In DRF, what happens to ?page_size=0 when page_size_query_param is enabled?`get_page_size()` parses the value in strict mode, where zero counts as invalid, so it falls back to the class's `page_size`. A negative or non-numeric value falls back the same way. A value above `max_page_size` is silently clamped to the maximum rather than rejected.
- Why does a DRF APIView returning Activity.objects.all() never paginate, even with DEFAULT_PAGINATION_CLASS set?The setting is read by `GenericAPIView.pagination_class`, and only `ListModelMixin.list()` calls the paginator. A plain `APIView` has neither, so it serialises whatever you return. Either switch to `ListAPIView` or instantiate the pagination class and call `paginate_queryset()` and `get_paginated_response()` yourself.
saying these in an interview costs you the question
- DRF paginates list endpoints by default with PageNumberPagination.
- Setting PAGE_SIZE alone is enough to turn pagination on.
- Clients can pass ?page_size= to any PageNumberPagination endpoint.
- A page number past the end returns an empty results list.
- Every APIView is paginated once DEFAULT_PAGINATION_CLASS is set.