啊胡

V1

2022/09/27阅读:18主题:绿意

Django中的列表接口如何实现参数查询和分页功能?

接着上节《Django如何快速开发RESTful风格的接口?》,本节来讲解列表接口中如何快速实现列表接口的参数查询,文章有点长,含部分源码解析,可以收藏后再看哦~

参数查询

从上节的ListModelMixin的源码可以知道,在self.get_queryset()获取到数据后,是由self.filter_queryset()方法对数据进行过滤的。 而具体的self.filter_queryset()逻辑是在GenericAPIView中实现的。

self.filter_backends主要获取配置的filterBackend类,调用类的filter_queryset()方法来实现参数查询的逻辑,self.filter_backends默认是api_settings.DEFAULT_FILTER_BACKENDS,这个就是在项目下的settings.py文件中进行配置的,因此filter_backends有两种配置方案,第一种是直接在settings.py文件指定DEFAULT_FILTER_BACKENDS,这个会对所有的View都生效,第二种方案是在单独的View类中进行配置,仅对当前这个View生效。 下面介绍几种常用列表参数查询的方案。

SearchFilter

为了演示,这里都是采用第二种方案,在View类中进行指定。拿「接口测试平台」专栏中的项目列表来举例,具体代码如下:

class ProjectsViewSet(mixins.ListModelMixin,
                      GenericViewSet)
:

    queryset = Projects.objects.filter()
    serializer_class = ProjectsSerializer
    filter_backends = (SearchFilter,)
    search_fields = ("name",)

重启项目,打开swagger界面可以看到列表接口多了一个search字段,使用这个search字段就可以在配置的search_fields中的字段进行搜索了。

接下来通过源码来看看它是如何实现的。上面已经知道,是通过配置的filterBackend类的filter_queryset方法实现的。所以可以直接看SearchFilter的filter_queryset方法。

SearchFilter还提供了4种匹配方案,没有前缀的,比如上面示例代码就是icontains包含。

  • '^name' 名称以「查询字段」开头
  • '=name' 名称完全等于「查询字段」
  • '@name' 全文搜索
  • '$name' 正则表达式搜索

DjangoFilterBackend

这是插件Django-filter提供的功能,使用之前需要先安装Django-filter依赖。它提供了两种查询方案,filterset_fieldsfilterset_class

接下来通过源码来看看它是如何实现的。同样是查看DjangoFilterBackend的filter_queryset方法。

可以看到首先要获取到filterset,这个是调用了self.get_filterset方法,然后获取到qs属性,这个在后面进行详细介绍。

然后通过self.get_filterset_class获取过滤类然后从请求中获取请求参数,调用具体的过滤类,将请求参数作为传参。根据配置的方式不同,self.get_filterset_class有不同的逻辑,下面分开进行介绍。

filterset_fields

class ProjectsViewSet(mixins.ListModelMixin,
                      GenericViewSet)
:

    queryset = Projects.objects.filter()
    serializer_class = ProjectsSerializer
    filter_backends = (DjangoFilterBackend,)
    filterset_fields = ("name",)

重启项目,打开swagger界面可以看到列表接口多了一个name字段,然后就可以使用name进行搜索了。

从源码可以看出,当filterset_fields不为空时,是使用AutoFilterSet类来进行过滤的。但是它没办法完成模糊搜索等高级功能,因此需要用到下面的方式来自定以Filter类。

filterset_class

class ProjectsFilter(FilterSet):
    # 指定需要模糊查询的字段
    name = CharFilter(field_name='name', lookup_expr='icontains')  # icontains,包含且忽略大小写

    class Meta:
        # 指定模型
        models = Projects
        fields = ['name']
        
class ProjectsViewSet(mixins.ListModelMixin,
                      GenericViewSet)
:

    queryset = Projects.objects.filter()
    serializer_class = ProjectsSerializer
    filter_backends = (DjangoFilterBackend,)
    filterset_class = ProjectsFilter

从源码可以看出,当filterset_class不为空时,返回是是第定义的filterset_class,即自定义的ProjectFilter类。

然后就是上诉两个方案相同的逻辑,即self.get_filterset方法最后返回的filter_class(**kwargs)。不同的是一个使用的是默认的AutoFilterSet,一个是自定义的ProjectsFilter。但是相同的逻辑是它们都继承于FilterSetMetaclass,从类名可以看出来,这是一个元类,即生成类的类,它的主要功能就是根据字段名设置过滤字段,例如默认的AutoFilterSet将会根据字段设置base_filter,并且匹配规则为'exact',即完全匹配,这也就是为什么filterset_fields无法完成模糊搜索等高级功能的原因。

而自定义的Filter类,则会根据配置生成对应字段的匹配规则,如下:

然后就回到self.filter_queryset返回的filter_query.qs这里。因为新生成的具有过滤规则的类都是继承BaseFilterSet类,所以这里其实是获取的BaseFilterSet类的qs属性,主要的逻辑就是根据这是的filters定义的字段匹配规则进行QuerySet的逻辑处理。

最终的效果就是QuerySet的query字段中查询语句后面拼接了参数过滤,如下图

篇幅有限,本章到这里就结束啦,是不是还有很多疑问,下章继续给大家分析QuerySet是如果将结果进行返回的。

分类:

后端

标签:

软件测试

作者介绍

啊胡
V1