Django-Filter 终极指南:构建强大的数据过滤系统
还在为 Django 项目中的复杂数据过滤需求而烦恼吗?每次都需要手动编写繁琐的查询逻辑?django-filter 为你提供了优雅的解决方案!本文将带你从零开始,全面掌握这个强大的 Django 过滤库。
🎯 读完本文你将获得
- django-filter 核心概念与架构深度解析
- 基础到高级的 FilterSet 配置实战指南
- Django REST Framework 无缝集成技巧
- 自定义过滤逻辑与高级用例实现
- 性能优化与最佳实践总结
📦 快速安装与环境配置
首先安装 django-filter:
pip install django-filter
在 Django 项目的 settings.py 中添加配置:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_filters', # 添加 django-filter
]
# 可选:配置默认查找表达式
FILTERS_DEFAULT_LOOKUP_EXPR = 'icontains'
🔧 核心概念:FilterSet 架构解析
django-filter 的核心是 FilterSet 类,它类似于 Django 的 ModelForm,但专门用于数据过滤。让我们通过一个完整的示例来理解其架构:
🚀 基础实战:构建第一个过滤系统
数据模型定义
假设我们有一个电商产品模型:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=50)
class Product(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
stock_quantity = models.IntegerField(default=0)
is_available = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
def __str__(self):
return self.name
基础 FilterSet 实现
import django_filters
from .models import Product
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'is_available', 'category', 'manufacturer']
视图层集成
from django.shortcuts import render
from django.views.generic import ListView
from .models import Product
from .filters import ProductFilter
# 函数视图方式
def product_list(request):
product_filter = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'products/list.html', {'filter': product_filter})
# 类视图方式
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
def get_queryset(self):
queryset = super().get_queryset()
self.filterset = ProductFilter(self.request.GET, queryset=queryset)
return self.filterset.qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = self.filterset
return context
模板渲染
<!-- products/list.html -->
{% extends "base.html" %}
{% block content %}
<h2>产品筛选</h2>
<!-- 筛选表单 -->
<form method="get" class="row g-3">
<div class="col-md-3">
{{ filter.form.name.label_tag }}
{{ filter.form.name }}
</div>
<div class="col-md-2">
{{ filter.form.price.label_tag }}
{{ filter.form.price }}
</div>
<div class="col-md-2">
{{ filter.form.is_available.label_tag }}
{{ filter.form.is_available }}
</div>
<div class="col-md-3">
{{ filter.form.category.label_tag }}
{{ filter.form.category }}
</div>
<div class="col-md-2 align-self-end">
<button type="submit" class="btn btn-primary">筛选</button>
<a href="?" class="btn btn-secondary">重置</a>
</div>
</form>
<hr>
<!-- 筛选结果 -->
<div class="row mt-4">
{% for product in filter.qs %}
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<p class="card-text">{{ product.description|truncatewords:20 }}</p>
<p class="text-primary">¥{{ product.price }}</p>
<p class="text-muted">
库存: {{ product.stock_quantity }} |
分类: {{ product.category.name }}
</p>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info">没有找到匹配的产品</div>
</div>
{% endfor %}
</div>
{% endblock %}
🎛️ 高级配置:灵活控制过滤行为
1. 精确控制字段查找表达式
class AdvancedProductFilter(django_filters.FilterSet):
# 自定义字段名称和查找表达式
product_name = django_filters.CharFilter(
field_name='name',
lookup_expr='icontains',
label='产品名称包含'
)
min_price = django_filters.NumberFilter(
field_name='price',
lookup_expr='gte',
label='最低价格'
)
max_price = django_filters.NumberFilter(
field_name='price',
lookup_expr='lte',
label='最高价格'
)
# 日期范围过滤
created_after = django_filters.DateFilter(
field_name='created_at',
lookup_expr='gte',
label='创建时间之后'
)
created_before = django_filters.DateFilter(
field_name='created_at',
lookup_expr='lte',
label='创建时间之前'
)
# 关联模型过滤
manufacturer_country = django_filters.CharFilter(
field_name='manufacturer__country',
lookup_expr='iexact',
label='制造商国家'
)
class Meta:
model = Product
fields = {
'stock_quantity': ['exact', 'gt', 'lt'],
'category': ['exact'],
}
2. Meta.fields 的多种配置方式
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
# 方式1:简单字段列表(默认使用exact查找)
fields = ['name', 'price', 'category']
# 方式2:字典配置,支持多个查找表达式
fields = {
'name': ['exact', 'icontains', 'istartswith'],
'price': ['exact', 'gt', 'lt', 'gte', 'lte'],
'created_at': ['exact', 'year', 'year__gt', 'year__lt'],
'stock_quantity': ['exact', 'gt', 'lt'],
}
# 方式3:混合使用
fields = ['name', 'is_available',
{'price': ['gt', 'lt'],
'created_at': ['year__gt', 'year__lt']}]
3. 自定义过滤方法
class CustomProductFilter(django_filters.FilterSet):
search = django_filters.CharFilter(method='custom_search', label='综合搜索')
class Meta:
model = Product
fields = ['category', 'manufacturer']
def custom_search(self, queryset, name, value):
"""
自定义搜索方法:同时搜索名称、描述和制造商名称
"""
return queryset.filter(
models.Q(name__icontains=value) |
models.Q(description__icontains=value) |
models.Q(manufacturer__name__icontains=value)
)
@property
def qs(self):
"""
重写qs属性,添加额外的过滤逻辑
"""
queryset = super().qs
# 示例:只返回有库存的产品
if not self.request.user.is_staff:
queryset = queryset.filter(stock_quantity__gt=0)
return queryset
🔗 Django REST Framework 集成
1. 基础 DRF 集成
# filters.py
from django_filters import rest_framework as filters
from .models import Product
class ProductFilter(filters.FilterSet):
min_price = filters.NumberFilter(field_name="price", lookup_expr='gte')
max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')
in_stock = filters.BooleanFilter(field_name='stock_quantity',
method='filter_in_stock')
class Meta:
model = Product
fields = ['category', 'manufacturer', 'is_available']
def filter_in_stock(self, queryset, name, value):
if value:
return queryset.filter(stock_quantity__gt=0)
return queryset.filter(stock_quantity=0)
# views.py
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
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
# 或者使用简写方式
# filterset_fields = ['category', 'manufacturer', 'is_available']
2. 配置默认过滤后端
# settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.OrderingFilter',
'rest_framework.filters.SearchFilter',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20
}
3. 自定义过滤后端
from django_filters.rest_framework import DjangoFilterBackend
class CustomFilterBackend(DjangoFilterBackend):
def get_filterset_kwargs(self, request, queryset, view):
kwargs = super().get_filterset_kwargs(request, queryset, view)
# 添加额外的上下文信息
kwargs['request'] = request
kwargs['user'] = request.user
return kwargs
# 在视图中使用
class ProductViewSet(viewsets.ModelViewSet):
filter_backends = [CustomFilterBackend]
filterset_class = ProductFilter
🎨 自定义过滤器与部件
1. 创建自定义过滤器
from django_filters import Filter
from django.db.models import Q
class MultiFieldSearchFilter(Filter):
def __init__(self, fields=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields = fields or []
def filter(self, qs, value):
if not value:
return qs
query = Q()
for field in self.fields:
query |= Q(**{f"{field}__icontains": value})
return qs.filter(query)
# 使用自定义过滤器
class ProductFilter(django_filters.FilterSet):
global_search = MultiFieldSearchFilter(
fields=['name', 'description', 'manufacturer__name'],
label='全局搜索'
)
class Meta:
model = Product
fields = []
2. 自定义部件(Widget)
from django import forms
from django_filters.widgets import RangeWidget
class DateRangeWidget(forms.MultiWidget):
def __init__(self, attrs=None):
widgets = [
forms.DateInput(attrs={'placeholder': '开始日期', 'type': 'date'}),
forms.DateInput(attrs={'placeholder': '结束日期', 'type': 'date'})
]
super().__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.start, value.stop]
return [None, None]
class DateRangeFilter(django_filters.DateFromToRangeFilter):
widget = DateRangeWidget
📊 性能优化与最佳实践
1. 查询优化策略
class OptimizedProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['category', 'manufacturer', 'is_available']
@property
def qs(self):
queryset = super().qs
# 使用select_related减少查询次数
queryset = queryset.select_related('category', 'manufacturer')
# 使用prefetch_related处理多对多关系
# queryset = queryset.prefetch_related('tags')
# 只选择需要的字段
queryset = queryset.only(
'name', 'price', 'stock_quantity',
'category__name', 'manufacturer__name'
)
return queryset
2. 分页与缓存策略
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class ProductListView(ListView):
model = Product
paginate_by = 20
filterset_class = ProductFilter
@method_decorator(cache_page(60 * 15)) # 缓存15分钟
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_queryset(self):
# 生成缓存键
cache_key = f"products_{hash(frozenset(self.request.GET.items()))}"
# 尝试从缓存获取
cached_qs = cache.get(cache_key)
if cached_qs is not None:
return cached_qs
# 执行过滤
queryset = super().get_queryset()
self.filterset = self.filterset_class(self.request.GET, queryset=queryset)
filtered_qs = self.filterset.qs
# 缓存结果
cache.set(cache_key, filtered_qs, 60 * 15)
return filtered_qs
3. 安全性考虑
class SecureProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = {
'price': ['exact', 'lt', 'gt'],
'created_at': ['exact', 'lt', 'gt'],
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 限制查询范围,防止数据泄露
user = getattr(self.request, 'user', None)
if user and not user.is_staff:
self.queryset = self.queryset.filter(is_available=True)
🧪 测试策略
from django.test import TestCase
from django.urls import reverse
from .models import Product, Category, Manufacturer
from .filters import ProductFilter
class ProductFilterTests(TestCase):
def setUp(self):
self.category = Category.objects.create(name="电子产品")
self.manufacturer = Manufacturer.objects.create(
name="测试厂商", country="中国"
)
self.product1 = Product.objects.create(
name="智能手机",
price=2999.00,
stock_quantity=10,
category=self.category,
manufacturer=self.manufacturer
)
self.product2 = Product.objects.create(
name="笔记本电脑",
price=5999.00,
stock_quantity=0,
category=self.category,
manufacturer=self.manufacturer
)
def test_name_filter(self):
# 测试名称过滤
filterset = ProductFilter(
{'name': '手机'},
queryset=Product.objects.all()
)
self.assertEqual(filterset.qs.count(), 1)
self.assertEqual(filterset.qs.first().name, "智能手机")
def test_price_range_filter(self):
# 测试价格范围过滤
filterset = ProductFilter(
{'price__gt': 3000, 'price__lt': 7000},
queryset=Product.objects.all()
)
self.assertEqual(filterset.qs.count(), 1)
self.assertEqual(filterset.qs.first().name, "笔记本电脑")
def test_in_stock_filter(self):
# 测试库存过滤
filterset = ProductFilter(
{'stock_quantity__gt': 0},
queryset=Product.objects.all()
)
self.assertEqual(filterset.qs.count(), 1)
self.assertEqual(filterset.qs.first().name, "智能手机")
📈 实战案例:电商平台商品筛选系统
需求分析
完整实现代码
# filters.py
import django_filters
from django.db import models
from .models import Product, Category, Manufacturer
class EcommerceProductFilter(django_filters.FilterSet):
# 基础字段过滤
name = django_filters.CharFilter(lookup_expr='icontains', label='商品名称')
min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte', label='最低价')
max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte', label='最高价')
# 多选过滤
categories = django_filters.ModelMultipleChoiceFilter(
field_name='category',
queryset=Category.objects.all(),
label='商品分类'
)
manufacturers = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer',
queryset=Manufacturer.objects.all(),
label='品牌'
)
# 库存状态过滤
in_stock = django_filters.BooleanFilter(
field_name='stock_quantity',
method='filter_in_stock',
label='仅显示有货'
)
# 排序选项
ORDERING_CHOICES = [
('price', '价格从低到高'),
('-price', '价格从高到低'),
('name', '名称A-Z'),
('-name', '名称Z-A'),
('-created_at', '最新上架'),
]
ordering = django_filters.OrderingFilter(
choices=ORDERING_CHOICES,
label='排序方式'
)
class Meta:
model = Product
fields = []
def filter_in_stock(self, queryset, name, value):
if value:
return queryset.filter(stock_quantity__gt=0)
return queryset
@property
def qs(self):
queryset = super().qs
# 优化查询性能
queryset = queryset.select_related('category', 'manufacturer')
queryset = queryset.only(
'name', 'price', 'stock_quantity', 'image',
'category__name', 'manufacturer__name'
)
return queryset
# views.py
from django.views.generic import ListView
from django.core.paginator import Paginator
from .models import Product
from .filters import EcommerceProductFilter
class ProductListView(ListView):
model = Product
template_name = 'ecommerce/product_list.html'
context_object_name = 'products'
paginate_by = 24
def get_queryset(self):
queryset = super().get_queryset()
self.filterset = EcommerceProductFilter(self.request.GET, queryset=queryset)
return self.filterset.qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = self.filterset
# 添加分页信息
paginator = Paginator(self.filterset.qs, self.paginate_by)
page_number = self.request.GET.get('page')
page_obj = paginator.get_page(page_number)
context['page_obj'] = page_obj
return context
🎯 总结与展望
django-filter 是一个功能强大且灵活的 Django 过滤库,通过本文的学习,你应该已经掌握了:
核心优势
- 声明式配置:类似 ModelForm 的简洁 API
- 高度可扩展:支持自定义过滤器和部件
- DRF 无缝集成:完美的 REST API 支持
- 性能优化:内置查询优化机制
最佳实践
- 合理使用 Meta.fields:根据需求选择列表或字典配置
- 优化查询性能:使用 select_related 和 prefetch_related
- 安全性考虑:限制敏感数据的访问
- 用户体验:提供清晰的标签和占位符文本
未来发展方向
- 支持更复杂的数据类型过滤
- 增强前端 JavaScript 集成
- 提供更多的内置过滤器类型
- 改进性能监控和调试工具
通过合理运用 django-filter,你可以轻松构建出功能强大、用户体验优秀的筛选系统,大幅提升开发效率和项目质量。
提示:如果本文对你有帮助,请点赞收藏支持!如有任何问题,欢迎在评论区讨论交流。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



