Filtering, Ordering and Pagination
DRF provides comprehensive filtering, ordering, and pagination support for list endpoints. Enabling these features is as simple as configuring filter_backends and pagination_class on a view. This article also covers the exception-handling mechanism and how to handle business exceptions that DRF does not process by default.
Filtering
django-filter Integration
For exact-match or range-based field filtering, the recommended approach is the django-filter extension:
pip install django-filterRegister the app and configure the global filter backend:
# settings.py
INSTALLED_APPS = [
...
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
]
}Specify filterable fields on a view:
from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['age', 'sex', 'class_null']
# Client request: GET /students/?age=20&sex=trueFine-Grained Filter Conditions
Use a custom FilterSet class to support range, membership, and fuzzy-match filtering:
import django_filters
from .models import Student
class StudentFilter(django_filters.FilterSet):
age_min = django_filters.NumberFilter(field_name="age", lookup_expr="gte")
age_max = django_filters.NumberFilter(field_name="age", lookup_expr="lte")
name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta:
model = Student
fields = ["age_min", "age_max", "name", "sex"]
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = StudentFilter
# Client request: GET /students/?age_min=18&age_max=30&name=zhangSearch Filter
DRF’s built-in SearchFilter supports fuzzy cross-field search:
from rest_framework.filters import SearchFilter
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_backends = [SearchFilter]
search_fields = ['name', 'description']
# Client request: GET /students/?search=zhangsearch_fields prefix meanings:
| Prefix | Behaviour | Example |
|---|---|---|
| (none) | Fuzzy (icontains) | 'name' |
^ | Starts with | '^name' |
= | Exact match | '=name' |
@ | Full-text search (requires DB support) | '@description' |
Ordering
DRF’s built-in OrderingFilter lets clients control sort order via URL parameters:
from rest_framework.filters import OrderingFilter
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['id', 'age', 'name'] # Fields that may be sorted
ordering = ['-id'] # Default ordering (optional)
# Ascending: GET /students/?ordering=age
# Descending: GET /students/?ordering=-age
# Multi-field: GET /students/?ordering=class_null,-ageCombining Filters and Ordering
When using multiple filter backends simultaneously, list all of them (a per-view list overrides the global configuration):
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['sex', 'class_null']
search_fields = ['name']
ordering_fields = ['id', 'age']
ordering = ['-id']Pagination
PageNumberPagination (Page-Number Pagination)
The most commonly used pagination style; the client requests data by page number and page size:
from rest_framework.pagination import PageNumberPagination
class StandardPagination(PageNumberPagination):
page_size = 10 # Default items per page
page_size_query_param = 'size' # Parameter name for client-specified page size
max_page_size = 100 # Maximum items per page
page_query_param = 'page' # Page-number parameter name (already default)
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
pagination_class = StandardPagination
# Example request: GET /students/?page=2&size=20Paginated response format:
{
"count": 100,
"next": "http://127.0.0.1:8000/students/?page=3&size=20",
"previous": "http://127.0.0.1:8000/students/?page=1&size=20",
"results": [...]
}LimitOffsetPagination (Offset Pagination)
Retrieve data by offset; ideal for infinite-scroll scenarios:
from rest_framework.pagination import LimitOffsetPagination
class StandardLimitOffsetPagination(LimitOffsetPagination):
default_limit = 10 # Default number of items to return
max_limit = 100 # Maximum number of items to return
limit_query_param = 'limit' # Parameter name for item count
offset_query_param = 'offset' # Parameter name for starting offset
class StudentListView(ListAPIView):
pagination_class = StandardLimitOffsetPagination
# Example request: GET /students/?limit=20&offset=40
# Meaning: starting at record 40, return 20 recordsGlobal Pagination Configuration
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'myapp.pagination.StandardPagination',
'PAGE_SIZE': 10,
}list() method. For endpoints that do not need pagination (e.g., dropdown option lists), set pagination_class = None on the view to disable it.Exception Handling
DRF Built-in Exceptions
DRF automatically handles the following exceptions and returns appropriate HTTP responses:
| Exception | Trigger Scenario | HTTP Status |
|---|---|---|
ParseError | Request body parsing failed | 400 |
AuthenticationFailed | Authentication failure | 401 |
NotAuthenticated | User not authenticated | 401 |
PermissionDenied | Permission denied | 403 |
NotFound | Object does not exist | 404 |
MethodNotAllowed | HTTP method not permitted | 405 |
Throttled | Rate limit exceeded | 429 |
ValidationError | Serializer validation failed | 400 |
Custom Exception Handler
For exceptions DRF does not handle (e.g., database errors or business logic exceptions), register a custom handler:
# myapp/utils/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.db import DatabaseError
import logging
logger = logging.getLogger(__name__)
def custom_exception_handler(exc, context):
# Let DRF's default handler process the exception first
response = exception_handler(exc, context)
if response is not None:
# DRF handled the exception; add a status_code field for consistency
response.data['status_code'] = response.status_code
return response
# Handle exceptions that DRF did not process
view = context.get('view')
if isinstance(exc, DatabaseError):
logger.error("[%s] Database error: %s", view.__class__.__name__, exc)
return Response(
{'detail': 'Database error, please try again later'},
status=status.HTTP_503_SERVICE_UNAVAILABLE,
)
# Log and return 500 for all other unexpected exceptions
logger.exception("[%s] Unexpected exception", view.__class__.__name__)
return Response(
{'detail': 'Internal server error'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)Register the handler in settings:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'myapp.utils.exceptions.custom_exception_handler',
}Raising DRF Exceptions Explicitly in Views
from rest_framework.exceptions import NotFound, ValidationError, PermissionDenied
class StudentAPIView(APIView):
def get(self, request, pk):
try:
student = Student.objects.get(pk=pk)
except Student.DoesNotExist:
raise NotFound(f"Student with id={pk} not found")
return Response(StudentSerializer(student).data)
def post(self, request):
if not request.data.get("name"):
raise ValidationError({"name": "Name cannot be empty"})
...API Documentation
DRF supports automatic API documentation generation via coreapi or drf-spectacular.
Recommended: drf-spectacular (OpenAPI 3.0):
pip install drf-spectacular# settings.py
INSTALLED_APPS = [..., 'drf_spectacular']
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]Visit http://127.0.0.1:8000/api/docs/ to view the Swagger UI documentation, which includes live testing support.