1. Home
  2. Docs
  3. Django Rest Framework
  4. Filtering

Filtering

Introduction to Filtering in DRF

Filtering in DRF allows users to retrieve subsets of data from API endpoints based on query parameters provided in the request URL. DRF provides built-in filtering capabilities, making it easy to implement various filtering options such as exact match, partial match, range-based filtering, and more.

Basic Concepts

Before diving into implementation, let’s understand some basic concepts related to filtering in DRF:

  1. Filter Backend: A filter backend is responsible for processing the filtering parameters provided in the request and applying them to the queryset before returning the results.
  2. Filtering Fields: These are the fields in your model or serializer that you want to allow filtering on. You can specify which fields are filterable and customize how they are filtered.
  3. Filtering Parameters: These are the query parameters passed in the request URL to specify the filtering criteria. For example, ?name=John would filter results where the name field matches ‘John’.

Step 1: Setup

Ensure you have Django and Django REST Framework installed:

pip install django djangorestframework
Bash

settings.py

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ],
   
}
Bash

Step 2: Define Models

Create a simple Django model to work with. For demonstration purposes, let’s create a Product model with name, price, and category fields.

# models.py

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    description = models.TextField()
    in_stock = models.BooleanField(default=True)

    def __str__(self):
        return self.name
Bash

Step 3: Create Serializer

Create a serializer for the Product model to serialize/deserialize data.

# serializers.py

from rest_framework import serializers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'
Bash

Step 4: Configure Views

Create a DRF view to handle the API endpoint for listing products.

# views.py

from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer
from django_filters.rest_framework import DjangoFilterBackend

class ProductListAPIView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name', 'price', 'category']
Bash

Step 5: URLs Configuration

Define the URL patterns for the API endpoints.

# urls.py

from django.urls import path
from .views import ProductListAPIView

urlpatterns = [
    path('products/', ProductListAPIView.as_view(), name='product-list'),
]
Bash

urls.py

from django.urls import path
from .views import ProductListAPIView

urlpatterns = [
path('products/', ProductListAPIView.as_view(), name='product-list'),
]
Bash

Step 6: Testing

Start the development server and navigate to the API endpoint to test filtering.

python manage.py runserver
Bash

Criteria list

  1. Exact Match: Filters results where the specified field matches exactly the provided value.
    • Example: /api/products/?category=Electronics
  2. Partial Match: Filters results where the specified field contains the provided value as a substring.
    • Example: /api/products/?name__contains=Laptop
  3. Case-insensitive Partial Match: Filters results where the specified field contains the provided value as a substring, ignoring case.
    • Example: /api/products/?name__icontains=laptop
  4. Starts with: Filters results where the specified field starts with the provided value.
    • Example: /api/products/?name__startswith=Samsung
  5. Ends with: Filters results where the specified field ends with the provided value.
    • Example: /api/products/?name__endswith=Pro
  6. Case-insensitive Exact Match: Filters results where the specified field matches exactly the provided value, ignoring case.
    • Example: /api/products/?category__iexact=electronics
  7. In: Filters results where the specified field matches any of the provided values.
    • Example: /api/products/?category__in=Electronics,Books
  8. Not In: Filters results where the specified field does not match any of the provided values.
    • Example: /api/products/?category__notin=Electronics,Books
  9. Is Null: Filters results where the specified field is null.
    • Example: /api/products/?description__isnull=true
  10. Is Not Null: Filters results where the specified field is not null.
    • Example: /api/products/?description__isnull=false
  11. Greater Than: Filters results where the specified field is greater than the provided value.
    • Example: /api/products/?price__gt=1000
  12. Greater Than or Equal To: Filters results where the specified field is greater than or equal to the provided value.
    • Example: /api/products/?price__gte=500
  13. Less Than: Filters results where the specified field is less than the provided value.
    • Example: /api/products/?price__lt=50
  14. Less Than or Equal To: Filters results where the specified field is less than or equal to the provided value.
    • Example: /api/products/?price__lte=100
  15. Range: Filters results where the specified field falls within the provided range of values.
    • Example: /api/products/?price__range=50,100
  16. Date Range: Filters results based on a range of dates.
    • Example: /api/products/?created_at__range=2022-01-01,2022-12-31
  17. Exact Date: Filters results where the specified date field matches exactly the provided date.
    • Example: /api/products/?created_at=2022-10-15
  18. Year-Month Filter: Filters results based on a specific year and month.
    • Example: /api/products/?created_at__year=2022&created_at__month=10
  19. Boolean Field: Filters results based on boolean field values (True/False).
    • Example: /api/products/?in_stock=true
  20. Custom Function: Applies custom filtering logic using a custom function.
    • Example: /api/products/?custom_filter_function=true

Request From Frontend

import React, { useState } from 'react';
import axios from 'axios';

const ProductList = () => {
  const [filters, setFilters] = useState({
    name: '',
    name__contains: '',
    name__startswith: '',
    name__endswith: '',
    name__icontains: '',
    category: '',
    category__in: '',
    category__notin: '',
    price: '',
    price__gt: '',
    price__gte: '',
    price__lt: '',
    price__lte: '',
    price__range: '',
    created_at: '',
    created_at__year: '',
    created_at__month: '',
    description__isnull: '',
    in_stock: '',
    // Add more criteria as needed
  });

  const [filteredProducts, setFilteredProducts] = useState([]);

  const fetchProducts = async () => {
    try {
      const response = await axios.get('/api/products/', {
        params: filters,
      });
      console.log('Filtered products:', response.data);
      setFilteredProducts(response.data); // Update filtered products state
    } catch (error) {
      console.error('Error fetching products:', error);
      // Handle error
    }
  };

  const handleFilterChange = (e) => {
    const { name, value } = e.target;
    setFilters((prevFilters) => ({
      ...prevFilters,
      [name]: value,
    }));
  };

  return (
    <div>
      <h2>Product List</h2>
      <label>
        Name:
        <input
          type="text"
          name="name"
          value={filters.name}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Category:
        <input
          type="text"
          name="category"
          value={filters.category}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Min Price:
        <input
          type="number"
          name="price__gte"
          value={filters.price__gte}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Max Price:
        <input
          type="number"
          name="price__lte"
          value={filters.price__lte}
          onChange={handleFilterChange}
        />
      </label>
      {/* Add more input fields for other criteria */}
      <button onClick={fetchProducts}>Apply Filters</button>

      {/* Display filtered products */}
      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            {product.name} - {product.category} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ProductList;
Bash

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ],
   
}
Bash

models and views

# models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    description = models.TextField()
    in_stock = models.BooleanField(default=True)

    def __str__(self):
        return self.name


# views.py
from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter

class ProductListCreateAPIView(generics.ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = {
        'name': ['exact', 'contains', 'startswith', 'endswith', 'icontains'],
        'category': ['exact', 'iexact', 'in', 'notin'],
        'price': ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
        'created_at': ['exact', 'range', 'year', 'month'],
        'description': ['exact', 'isnull'],
        'in_stock': ['exact']
    }
    search_fields = ['name', 'category', 'description']
    ordering_fields = ['name', 'price', 'created_at']

class ProductRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
Bash

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const ProductList = () => {
  const [filters, setFilters] = useState({
    name: '',
    name__contains: '',
    name__startswith: '',
    name__endswith: '',
    name__icontains: '',
    category: '',
    category__iexact: '',
    category__in: '',
    category__notin: '',
    price: '',
    price__gt: '',
    price__gte: '',
    price__lt: '',
    price__lte: '',
    price__range: '',
    created_at: '',
    created_at__range: '',
    created_at__year: '',
    created_at__month: '',
    description__isnull: '',
    in_stock: '',
    custom_filter_function: '',
  });

  const [filteredProducts, setFilteredProducts] = useState([]);
  const [ordering, setOrdering] = useState('');

  useEffect(() => {
    fetchProducts();
  }, [filters, ordering]);

  const fetchProducts = async () => {
    try {
      const response = await axios.get('/api/products/', {
        params: { ...filters, ordering },
      });
      setFilteredProducts(response.data);
    } catch (error) {
      console.error('Error fetching products:', error);
    }
  };

  const handleFilterChange = (e) => {
    const { name, value } = e.target;
    setFilters((prevFilters) => ({
      ...prevFilters,
      [name]: value,
    }));
  };

  const handleSortChange = (e) => {
    setOrdering(e.target.value);
  };

  return (
    <div>
      <h2>Product List</h2>
      <label>
        Name:
        <input
          type="text"
          name="name"
          value={filters.name}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Category:
        <input
          type="text"
          name="category"
          value={filters.category}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Min Price:
        <input
          type="number"
          name="price__gte"
          value={filters.price__gte}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Max Price:
        <input
          type="number"
          name="price__lte"
          value={filters.price__lte}
          onChange={handleFilterChange}
        />
      </label>
      <label>
        Ordering:
        <select name="ordering" value={ordering} onChange={handleSortChange}>
          <option value="">None</option>
          <option value="name">Name (A-Z)</option>
          <option value="-name">Name (Z-A)</option>
          <option value="price">Price (Low to High)</option>
          <option value="-price">Price (High to Low)</option>
        </select>
      </label>
      <button onClick={fetchProducts}>Apply Filters</button>

      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            {product.name} - {product.category} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ProductList;
Bash

How can we help?