Skip to content

DRF Querying and Pagination

Drf provides powerful tools for querying and paginating data in your APIs. These features allow you to filter, search, sort, and paginate your data efficiently, making it easier for clients to consume your API. Here’s an overview of how to use these features effectively:

graph TD Start["API Request"] -->|Query Parameters| Querying["DRF Querying (Filtering, Searching, Sorting)"] Querying -->|Filtered/Sorted Data| Pagination["DRF Pagination (Page Number, Limit-Offset)"] Pagination -->|Paginated Data| End["API Response"]
Reference Code for Models and Serializers
# models.py
from 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.py
from rest_framework import serializers
from .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 allows clients to specify criteria to narrow down the results returned by the API. DRF provides built-in support for filtering using query parameters. You can use the django-filter package to easily add filtering capabilities to your views.

There are several types of filtering you can implement:

  • Query Parameter Filtering: Clients can filter results by passing query parameters in the URL.

  • django-filter: This package provides a powerful and flexible way to filter querysets based on model fields.

You can implement basic filtering by overriding the get_queryset method in your view. For example:

# views.py
from rest_framework import viewsets
from .models import Product
from .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 queryset

This allows clients to filter products by collection ID using a query parameter like ?collection_id=1. By implementing this, you can provide a more efficient and user-friendly API that allows clients to retrieve only the data they need.

Django-filter is a powerful package that simplifies the process of adding filtering capabilities to your DRF views. It allows you to define filters based on your model fields and provides a clean API for clients to use. To use django-filter, you need to follow these steps:

  1. To use django-filter, you need to install it first:

    Terminal window
    uv add django-filter
  2. Add it to your INSTALLED_APPS in settings.py:

    INSTALLED_APPS = [
    # other apps
    'django_filters',
    ]
# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['collection_id']

To enable more complex filtering, you can define a custom filter class using django-filter. This allows you to specify different types of filters for your model fields. For example:

# filters.py
from django_filters.rest_framework import FilterSet
from .models import Product
class ProductFilter(FilterSet):
class Meta:
model = Product
fields = {
'price': ['gt', 'lt'],
'collection_id': ['exact'],
}

This custom filter allows clients to filter products based on price using exact matches, greater than, or less than comparisons. The collection_id field can still be filtered using exact matches. Here’s a breakdown of the available lookup types:

LookupMeaning
exact=
iexactcase-insensitive exact
containsLIKE %x%
icontainscase-insensitive contains
inIN (...)
gt / gte> / >=
lt / lte< / <=
rangeBETWEEN
isnullIS NULL
startswithLIKE x%
istartswithcase-insensitive startswith
endswithLIKE %x
iendswithcase-insensitive endswith

Then, you can use this custom filter in your view:

# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = ProductFilter

By using default query parameter we can implement searching functionality in our API. But it becomes more complex when we want to search across multiple fields or want to implement case-insensitive searching. DRF provides a built-in SearchFilter that allows you to easily add search functionality to your views. To use it, you need to add it to your view’s filter_backends and specify the fields you want to search on using the search_fields attribute. For example:

# views.py
from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from .models import Product
from .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
]

By implementing search functionality, you can allow clients to easily find relevant data in your API by searching across multiple fields. The url for searching would look like this: ?search=keyword, where keyword is the term you want to search for. This makes it easier for clients to discover and access the data they need in your API.

Sorting allows clients to specify the order in which results are returned by the API. DRF provides a built-in OrderingFilter that allows you to easily add sorting capabilities to your views. To use it, you need to add it to your view’s filter_backends and specify the fields you want to allow sorting on using the ordering_fields attribute. For example:

# views.py
from rest_framework import viewsets
from rest_framework.filters import OrderingFilter
from .models import Product
from .serializers import ProductSerializer
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['title', 'price', 'last_update']

By implementing sorting functionality, you can allow clients to specify the order of results based on different fields, making it easier for them to find the data they need in a way that suits their preferences. The url for sorting would look like this: ?ordering=field_name, where field_name is the name of the field you want to sort by. You can also specify descending order by prefixing the field name with a hyphen, like this: ?ordering=-field_name. This gives clients more control over how they access and view the data in your API.

Pagination allows you to control the amount of data returned in a single API response, which can improve performance and reduce bandwidth usage. DRF provides several built-in pagination classes that you can use to paginate your API responses.

We can implement pagination by two ways:

  • Global Pagination: You can set a default pagination class in your settings.py file, which will apply to all views that support pagination.

  • View-Level Pagination: You can specify a pagination class for individual views by setting the pagination_class attribute.

There are several types of pagination you can implement:

  • Page Number Pagination: This is the most common type of pagination, where clients can specify the page number and the number of items per page.

  • Limit-Offset Pagination: This type of pagination allows clients to specify the number of items to return (limit) and the starting point (offset) for the results.

To set a default pagination class for your entire API, you can add the following configuration to your settings.py file:

# settings.py
REST_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,
}

To specify a pagination class for an individual view, you can set the pagination_class attribute in your view. For example:

# pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import LimitOffsetPagination
class ProductPagination(PageNumberPagination):
page_size = 10
class ProductLimitOffsetPagination(LimitOffsetPagination):
default_limit = 10
# views.py
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer
from .pagination import ProductPagination, ProductLimitOffsetPagination
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
pagination_class = ProductPagination # or ProductLimitOffsetPagination

By implementing pagination, you can improve the performance of your API and provide a better experience for clients by allowing them to retrieve data in manageable chunks. The url for page number pagination would look like this: ?page=2, where 2 is the page number you want to retrieve. For limit-offset pagination, the url would look like this: ?limit=10&offset=20, where limit specifies the number of items to return and offset specifies the starting point for the results. This allows clients to easily navigate through large datasets and access the data they need without overwhelming their applications or consuming excessive bandwidth.