Django-Filter 使用笔记
前言:Django-Filter
是一个 Django
应用,用于过滤 QuerySets
。它的目的是简化过滤 QuerySets
的过程,提供一个简单的方法来定义过滤器。
它的功能类似于 Django 的 ORM 查询,但是更简单。
官网地址: https://django-filter.readthedocs.io/en/stable/
要求: 支持的 Python 版本以及最新版本的 Django REST Framework(DRF)进行了测试。
1. 安装
shell
pip install django-filter
然后将django-filter
添加到 INSTALLED_APPS
python
INSTALLED_APPS = [
...
'django_filters',
]
2. 入门使用
2.1 模型
python
"""
产品库
"""
from django.db import models
class ProductModel(models.Model):
name = models.CharField(max_length=100, blank=True, verbose_name="产品名称")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格")
description = models.TextField(blank=True, verbose_name="描述")
sn = models.CharField(max_length=100, blank=True, verbose_name="产品编号")
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.PROTECT, verbose_name="生产厂家")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
class Meta:
verbose_name = "产品"
verbose_name_plural = "产品"
db_table = "product"
2.2 过滤器
在产品表中,需要根据产品名称、价格、产品编号、生产厂家等字段进行过滤。
python
import django_filters
class ProductFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
price = django_filters.NumberFilter()
sn = django_filters.CharFilter(lookup_expr='icontains')
manufacturer = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = ProductModel
fields = ['name', 'price', 'sn', 'manufacturer']
lookup_expr
是过滤器的查询表达式,icontains
表示忽略大小写的包含查询。
2.3 声明式过滤器
声明式语法在创建过滤器时为您提供了最大的灵活性,但它相当冗长。如果你只想简单地过滤字段,
那么您可以使用 FilterSet
类的 Meta.fields
属性来简化过滤器的创建。
python
class ProductFilter(FilterSet):
# 价格范围过滤
price_gt = NumberFilter(field_name='price', lookup_expr='gt', label='价格大于')
price_lt = NumberFilter(field_name='price', lookup_expr='lt', label='价格小于')
# 制造商名称模糊搜索
manufacturer_name = CharFilter(field_name='manufacturer__name', lookup_expr='icontains', label='制造商名称包含')
class Meta:
model = Product
fields = ['price_gt', 'price_lt', 'manufacturer_name', 'sn'] # 显式列出所有你想在表单中显示的字段
/api/product/?manufacturer_name=xxxx
将会查询所有制造商名称中包含 "xxxx"(不区分大小写,因为你使用了 icontains 查找表达式)的
Product 实例。/api/product/?price_gt=10
将会查询所有价格大于 10 的 Product 实例。/api/product/?manufacturer_name=xxxx&price_gt=10
将会查询所有制造商名称中包含 "xxxx" 并且价格大于 10 的 Product 实例。
2.4 Meta.fields 生成过滤器
FilterSet Meta 类提供了一个fields
属性,可用于轻松指定多个过滤器,而无需大量代码重复。基本语法支持多个字段名称的列表:
python
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['price', 'sn']
根据上面的代码,django-filter
将自动创建两个过滤器,一个用于价格,一个用于发布日期。这两个过滤器将使用默认的查询表达式(exact
)。
exact
是精确查找,即只有字段值与查询值完全相等时才返回结果。
为每个字段指定多个查询表达式
python
class ProductFilter(django_filters.FilterSet):
# 你可以在这里添加自定义的过滤方法或字段
# 但对于基本的查找,我们只需要在 Meta 类中配置
class Meta:
model = Product
fields = {
'price': ['lt', 'gt'], # 允许小于和大于的价格过滤
'created_at': ['gt'], # 允许大于特定创建时间的过滤
# 如果你还想要基于 sn 字段进行精确匹配,可以这样
'sn': ['exact'],
}
# 如果你想要为过滤器设置更友好的标签或帮助文本,可以这样做
# labels = {
# 'price': _('价格'),
# 'created_at': _('创建时间'),
# 'sn': _('编号'),
# }
/api/product/?price_gt=10&created_at_gt=2023-01-01
将会查询价格大于 10 并且创建时间大于 2023-01-01 的 Product 实例。
3.与DRF集成
3.1 配置Django全局过滤器
python
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
# ...
),
}
3.2 在视图中使用自定义过滤器类 filterset_class
python
from rest_framework import generics
from django_filters import rest_framework as filters
from myapp 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')
class Meta:
model = Product
fields = ['category', 'in_stock']
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
# filter_backends = (filters.DjangoFilterBackend,) # 如果配置全局了,此处不需要再配置,如果没有配置3.1中的全局,需要手动在视图接口中写入此行代码
filterset_class = ProductFilter # 指定此视图的过滤类为 ProductFilter
上述 ProductList
接口中就可以使用 min_price
和 max_price
进行价格区间过滤。
- /api/product/?min_price=10&max_price=100 将会查询价格在 10 到 100 之间的 Product 实例。
3.3 在视图中使用自定义过滤器字段 filterset_fields
python
from rest_framework import generics
from django_filters import rest_framework as filters
from myapp import Product
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('category', 'in_stock')
# 也可以写为
"""
filterset_fields = {
'category': ['exact'],
'in_stock': ['exact'],
}
或者
filterset_fields = "__all__" # 全部字段精确查找
"""
4. 注意事项
在 Django REST framework 结合 django-filter
的使用中,filterset_class
和 filterset_fields
通常不会同时在一个视图类中直接使用,因为它们提供了不同的方式来定义过滤逻辑。然而,理解它们之间的关系和用法是很重要的。
filterset_class
filterset_class
属性允许你指定一个 FilterSet
类,这个类定义了过滤字段、方法以及可能的自定义过滤逻辑。当你需要更复杂的过滤逻辑,比如跨字段的过滤、排除特定值的过滤等,你应该使用 filterset_class
。
filterset_fields
filterset_fields
是一个简单的快捷方式,用于快速指定应该被过滤的模型字段。当你只需要对模型的几个字段进行基本的过滤(如精确匹配、模糊匹配等),并且不需要复杂的过滤逻辑时,filterset_fields
是一个很方便的选择。
不能同时使用?
从技术上讲,filterset_class
和 filterset_fields
可以在同一个视图类中设置,但通常不推荐这样做,因为这样做可能会导致意料之外的行为或冲突。filterset_class
提供了更高的灵活性和控制力,而 filterset_fields
则是为了简化常见的用例。
如果你同时设置了 filterset_class
和 filterset_fields
,django-filter
可能会优先使用 filterset_class
(这取决于 django-filter
的具体实现和版本),因为 filterset_class
提供了更具体的过滤配置。然而,由于这种不明确的行为,最好避免同时使用它们。
最佳实践
- 如果你的过滤需求很简单,只需要对几个字段进行基本的过滤,那么使用
filterset_fields
就足够了。 - 如果你的过滤需求更复杂,比如需要跨字段的过滤、自定义过滤方法或逻辑,那么你应该定义一个
FilterSet
类,并在视图中使用filterset_class
来指定它。
示例
使用 filterset_class
的示例:
python
from rest_framework import generics
from django_filters import FilterSet, CharFilter
from myapp.models import Product
from myapp.serializers import ProductSerializer
class ProductFilter(FilterSet):
category_name = CharFilter(field_name='category__name', lookup_expr='icontains')
class Meta:
model = Product
fields = ['category', 'in_stock', 'category_name']
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [filters.DjangoFilterBackend]
filterset_class = ProductFilter
在这个例子中,我们定义了一个 ProductFilter
类,它继承自 FilterSet
并包含了一个自定义的过滤字段 category_name
。然后,我们在 ProductList
视图中使用了 filterset_class
来指定这个过滤集。