DRF: Pagination & Filtering
GET /tasks/ against TaskViewSet right now returns every single
Task row in the database in one response. That's fine while TaskFlow has a handful of
tasks — it stops being fine the moment a real project accumulates thousands of them: the response
gets slow to generate, wasteful of bandwidth to transfer, and genuinely hard for a client app to do
anything useful with all at once. This is exactly the same underlying concern
Pagination already covered for ordinary HTML pages — an API
response needs the same kind of "one page at a time" treatment.
PageNumberPagination: Applied Globally, Once
DRF's built-in PageNumberPagination class handles this, and rather than configuring it
on every single ViewSet individually, it's usually set once, project-wide, in settings.py:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
With that in place, every ModelViewSet and generic view in the project — including
TaskViewSet, with no changes to it at all — automatically paginates its list results at
10 per page, and automatically understands a ?page= query parameter for moving between
pages.
What a Paginated Response Actually Looks Like
Turning pagination on changes the shape of a list response. Instead of a bare JSON array, DRF wraps the results in an object carrying a few extra, genuinely useful keys:
GET /api/tasks/?page=2
{
"count": 47,
"next": "http://localhost:8000/api/tasks/?page=3",
"previous": "http://localhost:8000/api/tasks/?page=1",
"results": [
{ "id": 11, "title": "Write updated copy", "priority": "medium", "completed": false },
{ "id": 12, "title": "Fix navbar spacing", "priority": "low", "completed": true }
]
}
count is the total number of matching tasks across every page, not just this one;
next and previous are ready-to-use URLs for moving forward or backward
(either one is null at the start or end of the list); results holds just
this page's slice of tasks. A client never has to calculate page URLs itself — it just follows
next until that field comes back null.
Query-String Filtering With django-filter
Pagination controls how much comes back at once; filtering controls which rows
match in the first place. The standard package for this is django-filter, installed
separately and wired into a ViewSet through filter_backends and
filterset_fields:
pip install django-filter
# taskflow/views.py
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from .models import Task
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['priority', 'completed']
With filterset_fields in place, the client can now narrow the list purely through
query parameters, no extra view code required:
GET /api/tasks/?priority=high&completed=false
That request returns only high-priority, not-yet-completed tasks. Under the hood, this is the exact
same ORM filtering already covered in
QuerySets & Filtering — django-filter
essentially translates recognized query-string parameters into the equivalent
Task.objects.filter(priority='high', completed=False) call automatically.
SearchFilter: Simple Text Search Across Fields
filterset_fields only matches exact values. For a looser, free-text search across one
or more fields, DRF's built-in SearchFilter fits better:
# taskflow/views.py
from rest_framework import filters
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['title', 'description']
GET /api/tasks/?search=report
That request matches any task whose title or description
contains "report" — a case-insensitive partial match across both fields at once, without writing any
filtering logic in the view.
OrderingFilter: Letting the Client Choose the Sort Order
OrderingFilter hands the client control over sort order via a query parameter, rather
than the API always returning results in one fixed order:
# taskflow/views.py
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['due_date', 'priority']
GET /api/tasks/?ordering=-due_date
The leading - reverses the sort, so this request returns tasks with the furthest-out
due date first; dropping the - (?ordering=due_date) sorts soonest-first
instead. All three filter backends above can be combined in the same filter_backends
list, letting a single endpoint support filtering, searching, and ordering together.