django-filter的使用

发布时间 2023-07-17 15:34:54作者: 蕝戀

有时候前端需要各种各样的过滤查询,如果自己写多少有点麻烦和冗余。使用django-filter就可以很好的解决这个问题。

django-filter可以用在django上, 也与配合drf一起使用。

主要区别在于drf要集成的FilterSet和django的不是同一个,别的都差不多。

下面展示配合drf使用。来实现搜索过滤的功能。

安装

pip install django-filter

配置

'django_filters' 添加到Django的 INSTALLED_APPS 中:

INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]

DRF添加默认过滤后端:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

或者将过滤器后端类添加到单个视图或视图集。

rom django_filters.rest_framework import DjangoFilterBackend

class UserListView(generics.ListAPIView):
    ...
    filter_backends = [DjangoFilterBackend]

入门使用

如果你只是需要对模型类的字段进行等值过滤,则可以参考下面的例子,这样能够快速实现等值过滤。

class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    
    # 使用filterset_fields属性,指定需要等值过滤的模型类字段名
    filterset_fields = ['category', 'in_stock']

这将自动为给定字段创建一个 FilterSet 类,并允许您发出如下请求:

http://example.com/api/products?category=clothing&in_stock=True

如果需要实现其他条件、模糊查询等,则需要自定义FilterSet.

自定义FilterSet实现更高级的过滤规则

https://django-filter.readthedocs.io/en/stable/guide/usage.html

FilterSet类的定义的写法和DRF的序列化器几乎差不多了,因为他们都是参照django的ModelForm。

实例:

# 这里导的是django_filters.rest_framework下的filter.FilterSet,不要导错了
# from django_filters import FilterSet 这个是给django用的。
from django_filters import rest_framework as filters

# 自定义FilterSet
class BookInfoFilter(filters.FilterSet):

    **# 这种叫直接声明过滤器字段,灵活性高,但如果字段比较多会相相对冗余一些..**
    # **格式:** 
    #     url中查询字符串key名 = 过滤器类型(field_name=模型类字段名, lookup_expr=过滤表达式关键词)
    #           lookup_expr: django的查询过滤表达式关键词,比如exact、startswith、in、gt等等...
    name_like = filters.CharFilter(field_name="name", lookup_expr="icontains")
    # 然后后台进行orm查询自动生成的表达式为:name__icontains执行过滤(这个就是django的过滤表达式而已)

    class Meta:
        model = BookInfo  # 需要指定django模型类
        
        # 如果fields 给定的是一个元组、列表,则默认自动生成的字段类只是等值过滤(没有其他过滤规则)
        #   那还不如直接在视图中指定filterset_fields属性,这样更快捷。
        # 所以一般我们只有实现高级过滤才会自定义FilterSet,并定义过滤器字段的查找表达式
        fields = ['name', ]
        
        # exclude 从自动过滤器生成中排除的字段。但不会禁用直接在 FilterSet 上声明的过滤器。
        # exclude不能同fields同时使用。
        # exclude=["is_delete"]

**# 进行name字段模糊查询时就可以使用以下url:**
# http://127.0.0.1:8000/book/viewset/?name_like=八

视图代码:

class BookInfoModelViewSet(ModelViewSet):
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]

    # filterset_fields = ['readcount', 'commentcount']
    
    # filter_class不能和filterset_fields同时指定!
    # 因为filterset_fields本身就自动帮你生成一个FilterSet,实现最简单的字段等值过滤查询!
    filter_class = BookInfoFilter

使用Meta.fileds来定义过滤器

原来直接定义过滤器字段,如果需要过滤的字段多则带啊吗相对冗长,如下:

class BookInfoFilter(filters.FilterSet):
    name_like = filters.CharFilter(field_name="name", lookup_expr="icontains")
    pub_data_year = filters.CharFilter(field_name="pub_date", lookup_expr="year__exact")
    pub_data_year_gt = filters.CharFilter(field_name="pub_date", lookup_expr="year__gt")
    ....

    class Meta:
        model = BookInfo  # 需要指定django模型类
        fields = ['name', 'pub_date']

通过Meta.fields属性来定义过滤器

class BookInfoFilter(filters.FilterSet):
    #name_like = filters.CharFilter(field_name="name", lookup_expr="icontains")
    #pub_data_year = filters.CharFilter(field_name="pub_date", lookup_expr="year__exact")
    #pub_data_year_gt = filters.CharFilter(field_name="pub_date", lookup_expr="year__gt")

    class Meta:
        model = BookInfo
        # fields = ["name",]
        fields = {
            # 使用fileds来定义的话,URL的查询字符串会变成这样:
            # ?name=xxx  # 精确匹配,等值匹配extra不需要加上就可以查询。
            # ?name__icontains=xxx # 模糊匹配
            "name": ["exact", "icontains",],

            # ?pub_data__year__gt=xxx
            # ?pub_data__year__lt=xxxx
            # ?pub_data__year=xxx
            # ?pub_data=xxx  # 日期年月日用”-“或者”/“分割,比如:2022-02-02、2023/02/02
            "pub_date": ["year__gt", "year__lt", "year__exact", "exact"],
        }

两种方法总结:

  1. 如果直接手动声明过滤器字段,则灵活较大,但是如果你需要过滤很多规则时,代码就会很多,相对冗余。好处是可以改变get查询字符串中key的名字。
  2. 使用fields属性通过字典的形式定义,代码比较简洁,但是无法改变get查询字符串中key的名字。

使用Filter.method来自定义过滤规则

觉得django的过滤表达式不够用?

您可以通过指定 method 来执行过滤来控制过滤器的行为。请注意,您可以访问过滤器集的属性,例如 request

class F(django_filters.FilterSet):
    username = CharFilter(method='my_custom_filter')

    class Meta:
        model = User
        fields = ['username']

    def my_custom_filter(self, queryset, name, value):
        return queryset.filter(**{
            name: value,
        })

更详细的用法见:https://django-filter.readthedocs.io/en/stable/ref/filters.html#filter-method

更改主查询结果集

默认情况下,FilterSet是直接模型类.objects.all()来获取所有结果集,然后再根据你指定的Filter过滤器来过滤的。

如果想改变主查询结果集,可以重写FilterSet.qs()方法.

例如,您可以将博客文章过滤为仅包含已发布的文章和登录用户拥有的文章(可能是作者的草稿文章)。

class ArticleFilter(django_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)