DRF Querying and Pagination
DRF gives you simple tools to filter, search, sort, and paginate the data in your API. These tools help clients get exactly the data they want, in the amount they want, without slowing things down. Here’s a simple look at how all of this works:
Reference Code for Models and Serializers
# models.pyfrom django.db import models
class Collection(models.Model): title = models.CharField(max_length=255)
class Product(models.Model): title = models.CharField(max_length=255) price = models.DecimalField(max_digits=10, decimal_places=2) collection = models.ForeignKey('Collection', on_delete=models.CASCADE)# serializers.pyfrom rest_framework import serializersfrom .models import Product, Collection
class CollectionSerializer(serializers.ModelSerializer): class Meta: model = Collection fields = ['id', 'title']
class ProductSerializer(serializers.ModelSerializer): collection = CollectionSerializer()
class Meta: model = Product fields = ['id', 'title', 'price', 'collection']Filtering
Section titled “Filtering”Filtering means letting clients pick what data they want to see, based on some rule. For example, “only show me products from collection number 1.” DRF makes this easy with query parameters (the part after ? in a URL). You can also use a package called django-filter to add filtering with much less code.
There are two main ways to add filtering:
-
Query Parameter Filtering: The client adds a filter directly in the URL.
-
django-filter: A package that gives you an easy and flexible way to filter data based on model fields.
Query Parameter Filtering
Section titled “Query Parameter Filtering”You can add simple filtering by changing the get_queryset method in your view. For example:
# views.pyfrom rest_framework import viewsetsfrom .models import Productfrom .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer
def get_queryset(self): queryset = super().get_queryset() collection_id = self.request.query_params.get('collection_id', None) if collection_id is not None: queryset = queryset.filter(collection_id=collection_id) return querysetThis lets clients filter products by collection ID using a URL like ?collection_id=1. With this in place, clients can get only the data they actually need, instead of pulling everything.
Django-filter
Section titled “Django-filter”Django-filter is a handy package that makes adding filters to your DRF views much simpler. It lets you set up filters based on your model fields and gives clients a clean way to use them. Here’s how to set it up:
-
First, install
django-filter:Terminal window uv add django-filter -
Then add it to your
INSTALLED_APPSinsettings.py:INSTALLED_APPS = [# other apps'django_filters',]
# views.pyfrom django_filters.rest_framework import DjangoFilterBackendfrom rest_framework import viewsetsfrom .models import Productfrom .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['collection_id']Custom Filters
Section titled “Custom Filters”If you need more advanced filtering, you can build your own filter class using django-filter. This lets you choose different filter types for each field. For example:
# filters.pyfrom django_filters.rest_framework import FilterSetfrom .models import Product
class ProductFilter(FilterSet):
class Meta: model = Product fields = { 'price': ['gt', 'lt'], 'collection_id': ['exact'], }This custom filter lets clients filter products by price using “greater than” or “less than,” and still filter by collection_id using an exact match. Here’s a simple table showing what each lookup type means:
| Lookup | Meaning |
|---|---|
exact | = |
iexact | case-insensitive exact |
contains | LIKE %x% |
icontains | case-insensitive contains |
in | IN (...) |
gt / gte | > / >= |
lt / lte | < / <= |
range | BETWEEN |
isnull | IS NULL |
startswith | LIKE x% |
istartswith | case-insensitive startswith |
endswith | LIKE %x |
iendswith | case-insensitive endswith |
Now you can use this custom filter in your view:
# views.pyfrom django_filters.rest_framework import DjangoFilterBackendfrom rest_framework import viewsetsfrom .models import Productfrom .serializers import ProductSerializerfrom .filters import ProductFilter
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [DjangoFilterBackend] filterset_class = ProductFilterSearching
Section titled “Searching”You can build basic search using query parameters, but it gets tricky if you want to search across many fields at once, or want the search to ignore uppercase and lowercase letters. DRF solves this with a built-in tool called SearchFilter. To use it, add it to your view’s filter_backends, then list the fields you want to search using search_fields. For example:
# views.pyfrom rest_framework import viewsetsfrom rest_framework.filters import SearchFilterfrom .models import Productfrom .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [SearchFilter] search_fields = [ 'title', 'description', 'collection__title', # you can also search on related # fields using double underscores ]With search set up, clients can easily find what they’re looking for across several fields at once. The URL for searching looks like this: ?search=keyword, where keyword is whatever you’re searching for. This makes your API much easier to use.
Sorting
Section titled “Sorting”Sorting lets clients choose the order in which they get results back. DRF has a built-in tool for this called OrderingFilter. To use it, add it to your view’s filter_backends, then list which fields can be sorted using ordering_fields. For example:
# views.pyfrom rest_framework import viewsetsfrom rest_framework.filters import OrderingFilterfrom .models import Productfrom .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [OrderingFilter] ordering_fields = ['title', 'price', 'last_update']With sorting set up, clients can choose how they want their results arranged, based on whichever field works best for them. The URL for sorting looks like this: ?ordering=field_name, where field_name is the field you want to sort by. To sort in reverse (descending) order, just add a hyphen before the field name, like this: ?ordering=-field_name. This gives clients full control over how they view your data.
Pagination
Section titled “Pagination”Pagination means breaking up a large set of results into smaller chunks, instead of sending everything at once. This makes your API faster and saves bandwidth. DRF comes with built-in pagination classes that you can use right away.
There are two ways to set up pagination:
-
Global Pagination: Set a default pagination class in
settings.py. This applies to every view that supports pagination. -
View-Level Pagination: Set a pagination class on a specific view using the
pagination_classattribute.
There are two common types of pagination:
-
Page Number Pagination: The client asks for a specific page number and how many items they want per page.
-
Limit-Offset Pagination: The client says how many items they want (limit) and where to start from (offset).
Global Pagination
Section titled “Global Pagination”To set a default pagination class for your whole API, add this to your settings.py file:
# settings.pyREST_FRAMEWORK = { # other settings 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # you can also use 'rest_framework.pagination.LimitOffsetPagination' for limit-offset pagination 'PAGE_SIZE': 10,}View-Level Pagination
Section titled “View-Level Pagination”To set pagination for just one view, set the pagination_class attribute on that view. For example:
# pagination.pyfrom rest_framework.pagination import PageNumberPaginationfrom rest_framework.pagination import LimitOffsetPagination
class ProductPagination(PageNumberPagination): page_size = 10
class ProductLimitOffsetPagination(LimitOffsetPagination): default_limit = 10# views.pyfrom rest_framework import viewsetsfrom .models import Productfrom .serializers import ProductSerializerfrom .pagination import ProductPagination, ProductLimitOffsetPagination
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer pagination_class = ProductPagination # or ProductLimitOffsetPaginationWith pagination set up, your API runs faster and gives clients data in smaller, easy-to-handle pieces. The URL for page number pagination looks like this: ?page=2, where 2 is the page you want. For limit-offset pagination, the URL looks like this: ?limit=10&offset=20, where limit is how many items you want and offset is where to start counting from. This way, clients can move through large amounts of data without overloading their apps or using too much bandwidth.