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:
- 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.
- 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.
- 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 thename
field matches ‘John’.
Step 1: Setup
Ensure you have Django and Django REST Framework installed:
pip install django djangorestframework
Bashsettings.py
# settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
],
}
BashStep 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
BashStep 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__'
BashStep 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']
BashStep 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'),
]
Bashurls.py
from django.urls import path
from .views import ProductListAPIView
urlpatterns = [
path('products/', ProductListAPIView.as_view(), name='product-list'),
]
BashStep 6: Testing
Start the development server and navigate to the API endpoint to test filtering.
python manage.py runserver
BashCriteria list
- Exact Match: Filters results where the specified field matches exactly the provided value.
- Example:
/api/products/?category=Electronics
- Example:
- Partial Match: Filters results where the specified field contains the provided value as a substring.
- Example:
/api/products/?name__contains=Laptop
- Example:
- 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
- Example:
- Starts with: Filters results where the specified field starts with the provided value.
- Example:
/api/products/?name__startswith=Samsung
- Example:
- Ends with: Filters results where the specified field ends with the provided value.
- Example:
/api/products/?name__endswith=Pro
- Example:
- Case-insensitive Exact Match: Filters results where the specified field matches exactly the provided value, ignoring case.
- Example:
/api/products/?category__iexact=electronics
- Example:
- In: Filters results where the specified field matches any of the provided values.
- Example:
/api/products/?category__in=Electronics,Books
- Example:
- Not In: Filters results where the specified field does not match any of the provided values.
- Example:
/api/products/?category__notin=Electronics,Books
- Example:
- Is Null: Filters results where the specified field is null.
- Example:
/api/products/?description__isnull=true
- Example:
- Is Not Null: Filters results where the specified field is not null.
- Example:
/api/products/?description__isnull=false
- Example:
- Greater Than: Filters results where the specified field is greater than the provided value.
- Example:
/api/products/?price__gt=1000
- Example:
- 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
- Example:
- Less Than: Filters results where the specified field is less than the provided value.
- Example:
/api/products/?price__lt=50
- Example:
- 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
- Example:
- Range: Filters results where the specified field falls within the provided range of values.
- Example:
/api/products/?price__range=50,100
- Example:
- Date Range: Filters results based on a range of dates.
- Example:
/api/products/?created_at__range=2022-01-01,2022-12-31
- Example:
- Exact Date: Filters results where the specified date field matches exactly the provided date.
- Example:
/api/products/?created_at=2022-10-15
- Example:
- Year-Month Filter: Filters results based on a specific year and month.
- Example:
/api/products/?created_at__year=2022&created_at__month=10
- Example:
- Boolean Field: Filters results based on boolean field values (True/False).
- Example:
/api/products/?in_stock=true
- Example:
- Custom Function: Applies custom filtering logic using a custom function.
- Example:
/api/products/?custom_filter_function=true
- Example:
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',
],
}
Bashmodels 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
Bashimport 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