django-filter 23.3 文档翻译(一)

264 阅读5分钟

概述

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, pricerelease_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有两个主要的参数:

  1. field_name: 想要筛选的模型类字段名。你可以使用Django的__语法通过“关系路径”(外键)来过滤相关模型上的字段。例如,manufacturer__name。
  2. lookup_expr: 筛选时要使用的字段查找。Django的__语法可以再次用于支持查找转换。例如,year__gte。

field_namelookup_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']

以上代码提供了pricerelease_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_daterelease_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.fieldsFilterSet

class ProductFilter(FilterSet):
    class Meta:
        model = Product
        fields = ('category', 'in_stock')

注意不支持同时使用filterset_fieldsfilterset_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()
        }