概述
Django-filter是一个通用的、可重复利用的应用程序,可以减少编写一些更常见的视图代码。具体来说,它允许用户根据模型的字段筛选queryset,并显示表单以允许他们这样做。
安装
Django-filter可以通过pip来安装“
pip install django-filter
之后将django_filters添加进INSTALLED_APPS
INSTALLED_APPS = [
...
'django_filters',
]
入门
Django-filter提供了一种基于用户提供的参数来筛选queryset的简单方法。我们有一个Product模型,并且我们希望让用户筛选哪些是他们想在产品列表页面看到的产品。
模型类
开始创建Product模型类:
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=32)
country = models.CharField(max_length=32)
class Product(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
description = models.TextField()
release_date = models.DateField()
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
过滤器
Product模型类有一些字段,我们希望让用户可以通过name, price和release_date三个字段来筛选。为此我们创建一个FilterSet:
import django_filters
class ProductFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='iexact')
class Meta:
model = Product
fields = ['price', 'release_date']
如你所见,过滤器使用了跟Django的ModelForm非常相似的API。正如ModelForm一样,我们也可以覆盖filter,或者使用声明性语法添加新的filter。
声明过滤器
声明性语法在创建过滤器时为您提供了最大的灵活性,但它相当冗长。我们将用下面的例子来简述FilterSet中filter的核心参数:
from .models import Product
import django_filters
class ProductFilter(django_filters.FilterSet):
price = django_filters.NumberFilter()
price__gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt')
price__lt = django_filters.NumberFilter(field_name='price', lookup_expr='lt')
release_year = django_filters.NumberFilter(field_name='release_data', lookup_expr='year')
release_year__gt = django_filters.NumberFilter(field_name='release_data', lookup_expr='year__gt')
release_year__lt = django_filters.NumberFilter(field_name='release_data', lookup_expr='year__lt')
manufacturer__name = django_filters.ChoiceFilter(lookup_expr='icontains')
class Meta:
model = Product
fields = ['price', 'release_date', 'manufacturer']
filter有两个主要的参数:
field_name: 想要筛选的模型类字段名。你可以使用Django的__语法通过“关系路径”(外键)来过滤相关模型上的字段。例如,manufacturer__name。lookup_expr: 筛选时要使用的字段查找。Django的__语法可以再次用于支持查找转换。例如,year__gte。
field_name和lookup_field共同表示了一个完整的Django查找表达式。Django的Lookup API reference 中提供了查找表达式的详细解释。django-filter支持包含转换和最终查找的表达式。
通过Meta.fields生成filters
FilterSet Meta元类提供了一个可以方便指定多个filter,而且不会出现重复代码的fields属性。基本语法支持多个字段名的列表:
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fileds = ['price', 'release_date']
以上代码提供了price和release_date两个字段的'exact'(精确)查找。
此外,可以使用字段为每个字段设置多个查找表达式:
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = {
'price' = ['lt', 'gt'],
'release_date' = ['exact', 'year__gt'],
}
以上代码会生成price__lt, price__gt, release_date和release_date__year__gt
注:exact是一个隐式的,不会加在字段名的后面。例如,release date的exact过滤器是release_date, 不是release_date__exact。
对于外部关系(外键),还可以使用Django的__语法来过滤关联模型类的字段。
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['manufacturer__country']
覆盖默认的filters
就像django.contrib.admin.ModelAdmin, 可以在Meta元类使用filter_overrides覆盖所有的模型类字段默认的过滤器。
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = {
'name' = ['exact'],
'release_date' = ['isnull'],
}
filter_overrides = {
models.CharField: {
'filter_class': django_filter.CharFilter,
'extra': lambda f: {
'lookup_expr': 'icontains',
},
},
models.BooleanField: {
'filter_class': django_filters.BooleanFilter,
'extra': lambda f: {
'widget': forms.CheckboxInput,
},
},
}
基于请求筛选
可以使用可选的request参数初始化FilterSet。如果传递了请求对象,则可以在筛选期间访问该请求。这允许您根据请求的属性进行筛选,例如当前登录的用户或Accepts Languages标头。
注:request并不一定会提供给FilterSet实例,所有基于请求的代码必须有处理无request对象的能力。
筛选主键 .qs
要按request对象筛选主queryset,只需重写FilterSet.qs属性即可。例如,你可以筛选博客里发布的文章 和是当前登录用户所写的文章(大概是作者的文章草稿)
class ArticleFilter(django_filters.FilterSet):
class Meta:
model = Article
fields = [...]
@property
def qs(self):
parent = super().qs
author = getarrt(self.request, 'user', None)
return parent.filter(is_published=True) | parent.filter(author=author)
集成DRF
与Django Rest Framework的集成是通过DRF特定的FilterSet和filter后端提供的。这些可以在rest_framework子库中找到。
快速入门
使用新的FilterSet只需要更换引用路径。从rest_framework的子库中引用,而不是从dajngo_filters引用。
from django_filters.rest_framework import FilterSet
class ProductFilter(FilterSet):
...
你的视图类也需要在filter_backends中添加DjangoFilterBackend。
from .models import Product
from django_filters.rest_framework import DjangoFilterBackend
class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (DjangoFilterBackend, )
filterset_fields = ('category', 'in_stock')
如果你想把django-filter后端当作默认设置,把它添加到DEFAULT_FILTER_BACKENDS中:
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
...
),
}
使用filterset_class添加一个FilterSet
为了能通过FilterSet过滤,给你的视图类添加filterset_class参数。
from rest_framework import generics
from .models import Product
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.rest_framework import FilterSet
class ProductFilter(FilterSet):
min_price = filter.Number(field_name='price', lookup_expr='gte')
max_price = filter.Number(field_name='price', lookup_expr='lte')
class Meta:
model = Product
fields = ['category', 'in_stock']
class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (DjangoFilterBackend, )
filterset_class = ProductFilter
使用filterset_fields快捷方式
你可以通过在视图类中添加filterset_fields来避免创建一个FilterSet
class ProuctListView(ListAPIView):
queryset = Product.object.all()
filter_backends = (DjangoFilterBackend, )
filterset_fields = ('category', 'in_stock')
以上filterset_fields参数等于下面只有Meta.fields的FilterSet:
class ProductFilter(FilterSet):
class Meta:
model = Product
fields = ('category', 'in_stock')
注意不支持同时使用filterset_fields和filterset_class参数。
重写FilterSet创建
可以通过重写后端类上的以下方法来自定义“FilterSet”创建:
.get_filterset(self, request, queryset, view).get_filterset_class(self, view, queryset=None).get_filterset_kwargs(self, request, queryset, view)
您可以根据具体情况为每个视图重写这些方法,创建独特的后端,也可以使用这些方法为视图类编写自己的钩子方法。
class MyFilterbackend(DjangoFilterBackend):
def get_filterset_kwargs(self, request, queryset, view):
kwargs = super().get_filterset_kwargs(request, queryset, view)
# 合并视图类提供的筛选器集kwargs
if hasattr(view, 'get_filterset_kwargs'):
kwargs.update(view.get_filterset_kwargs()):
return kwargs
class BookFilter(Filter):
def __init__(self, *args, author=None, **kwargs):
super().__init(*args, **kwargs)
class BookViewSet(ModelViewSet):
filter_backends = [MyFilterBackend]
filterset_class = BookFilter
def get_filterset_kwargs(self):
return {
'author': self.get_author()
}