django-filter
是一个通用的、可重用的应用程序,它可以减轻视图代码的编写工作量。具体来说,它允许用户根据模型的字段筛选查询集,并显示表单让他们这样做。
pip install django-filter
在settings.py
中添加如下配置:
INSTALLED_APPS = [
...
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
...
),
}
现有如下model:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
description = models.TextField()
release_date = models.DateField()
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
现在,我们在ListAPIView
中添加filterset_fields
属性:
class ProductAPIView(ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_fields = ('category', 'in_stock')
或者这样:
from django_filters import rest_framework as filters
class ProductFilterSet(filters.FilterSet):
class Meta:
model = Product
fields = ['category', 'in_stock']
class ProductAPIView(ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_class = ProductFilterSet
此时,我们可以通过category或in_stock参数来精确查找product的列表数据。值得注意的是,如果同时存在filterset_class
与filterset_fields
属性时,将会优先使用filterset_class
属性
在上面的例子我们可以看到,我们可以自定义字段来进行精确匹配…但是,这远远满足不了用户的需求,如果用户需要模糊匹配,范围查询等等操作时,那该怎么办呢?此时,我们可以对FilterSet
改造,如下:
import django_filters
from django_filters import rest_framework as filters
class ProductFilterSet(filters.FilterSet):
price = django_filters.NumberFilter()
price__gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt') # price 大于 指定值
price__lt = django_filters.NumberFilter(field_name='price', lookup_expr='lt') # price 小于 指定值
price__isnull = django_filters.NumberFilter(field_name='price', lookup_expr='isnull') # price 为空
price__notnull = django_filters.NumberFilter(field_name='price', lookup_expr='isnull', exclude=True) # price 不为空
release_year = django_filters.NumberFilter(field_name='release_date', lookup_expr='year') # release_date的年份 等于 指定值
release_year__gt = django_filters.NumberFilter(field_name='release_date', lookup_expr='year__gt') # release_date的年份 大于 指定值
release_year__lt = django_filters.NumberFilter(field_name='release_date', lookup_expr='year__lt') # release_date的年份 小于 指定值
class Meta:
model = Product
fields = ['price', 'release_year', 'category', 'in_stock']
如果使用该FilterSet,那么传入"price", “price__gt”, “price__lt”, “release_year”, “release_year__gt”, “release_year__lt”, “category”, "in_stock"参数都可以进行过滤了。
关于过滤器有两个主要的参数:
field_name
: 要筛选的模型字段的名称。你可以适用Django中的__
语法遍历"关系路径"来过滤相关模型上的字段。例如:manufacturer__name
lookup_expr
: 筛选时要适用的字段查找。Django的__
语法也可以用来支持查找转换。例如:year__gte
。exclude
: 反转逻辑此外,字典可用于为每个字段指定多个查找表达式:
from django_filters import rest_framework as filters
class ProductFilterSet(filters.FilterSet):
class Meta:
model = Product
fields = {
"price": ["exact", "lt", "gt"],
"release_date": ["exact", "year__gt", "year__lt"]
}
上述语句将生成"price", “price__lt”, “price__gt”, “release_date”, “release_date__year__gt”, "release_date__year__lt"过滤器,传入这些参数可以进行过滤操作。
filter_overrides
我们可以在Meta
类上使用filter_overrides
属性覆盖所有同类models字段的默认过滤器:
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'description']
filter_overrides = {
models.CharField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'icontains',
},
}
}
那么,“name”, "description"字段过滤将由原来的精确匹配,变成了模糊匹配了!
qs
如果你想要根据request
对象来筛选queryset
,你可以覆盖qs
只读属性。例如,可以将博客文章过滤为已发布的文章和已登录用户拥有的文章(假设是作者的文章草稿):
class ArticleFilterSet(filters.FilterSet):
class Meta:
model = Article
fields = [...]
@property
def qs(self):
parent = super().qs
author = getattr(self.request, 'user', None)
return parent.filter(is_published=True) | parent.filter(author=author)
自定义过滤方法
可以通过指定执行筛选的方法来控制筛选器的行为。在方法参考中查看更多信息。请注意,你可以访问筛选器集的属性,例如request
:
class ProductFilterSet(filters.FilterSet):
username = django_filters.CharFilter(method='my_custom_filter')
class Meta:
model = Product
fields = ['username']
def my_custom_filter(self, queryset, name, value):
return queryset.filter(**{name: value})
你可以重写过滤器类的filter()
方法,来做一些特殊的事情。如下:
import django_filters
from django.core.validators import EMPTY_VALUES
class MyCharFilter(django_filters.CharFilter):
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
if self.distinct:
qs = qs.distinct()
lookup = '%s__%s' % (self.field_name, self.lookup_expr)
qs = self.get_method(qs)(**{lookup: value})
return qs
FilterSet
的创建可以通过覆盖后端类上的以下方法进行自定义:
.get_filterset(self, request, queryset, view)
.get_filterset_class(self, view, queryset=None)
.get_filterset_kwargs(self, request, queryset, view)
你可以为每个视图逐个重写这些方法,创建唯一的后端,或者可以使用这些方法编写自己的视图类钩子。
几个与视图相关的属性被重命名,以提高与库其他部分的一致性。重命名属性如下:
ViewSet.filter_class
=> filterset_class
ViewSet.filter_fields
=> filterset_fields
DjangoFilterBackend.default_filter_set
=> filterset_base
DjangoFilterBackend.get_filter_class
=> get_filterset_class()
FilterMixin.filter_fields
=> filterset_fields
https://django-filter.readthedocs.io/en/main/index.html