DRF之过滤类源码分析

发布时间 2023-09-18 22:14:41作者: Chimengmeng

【一】过滤类介绍及BaseFilterBackend

  • Django REST framework(DRF)中的过滤类允许你在API视图中对查询进行过滤,以根据特定条件筛选结果集。
  • 过滤类是DRF的一部分,它允许你轻松地添加各种过滤选项,以满足不同用例的需求。
class BaseFilterBackend:
    """
    A base class from which all filter backend classes should inherit.
    """

    def filter_queryset(self, request, queryset, view):
        """
        Return a filtered queryset.
        """
        raise NotImplementedError(".filter_queryset() must be overridden.")

    def get_schema_fields(self, view):
        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
        return []

    def get_schema_operation_parameters(self, view):
        return []

【二】内置过滤类SearchFilter

【1】使用

# 过滤:必须继承 GenericAPIView 及其子类,才能使用(如果继承APIView,则不能这么写)
# DRF 给我们提供了一个排序类
from rest_framework.filters import SearchFilter


class BookView(GenericViewSet, ListModelMixin):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter]  # 排序类
    # SearchFilter:必须配合一个类属性
    # 按照那些字段进行筛选
    # http://127.0.0.1:8000/app01/v1/books/?search=梦
    # 只要name/price中带 梦 都能被搜出来
    search_fields = ['name']

【2】源码分析

class SearchFilter(BaseFilterBackend):
    # The URL query parameter used for the search.
    
    # 搜索参数的名称,默认为 api_settings.SEARCH_PARAM,通常是 search。
    # 拼在路由尾部的关键字  http://127.0.0.1:8000/app01/v1/books/?search=梦
    # 你可以通过在API视图中自定义该参数来更改搜索参数的名称。
    search_param = api_settings.SEARCH_PARAM
    
    # 用于HTML渲染搜索部件的模板路径,默认为 'rest_framework/filters/search.html'。
    template = 'rest_framework/filters/search.html'
    
    # 定义了搜索条件的前缀和它们对应的查询操作。
    # 例如,^ 表示 "istartswith"(不区分大小写的开始于)
    # = 表示 "iexact"(不区分大小写的精确匹配) 使用居多
    # @ 表示 "search"(全文搜索)
    # $ 表示 "iregex"(不区分大小写的正则表达式匹配)。
    lookup_prefixes = {
        '^': 'istartswith',
        '=': 'iexact',
        '@': 'search',
        '$': 'iregex',
    }
    
    # 
    search_title = _('Search')
    
    # 
    search_description = _('A search term.')
	
    # 从视图中获取搜索字段列表
    # 可以覆盖此方法以动态更改搜索字段
    def get_search_fields(self, view, request):
        """
        Search fields are obtained from the view, but the request is always
        passed to this method. Sub-classes can override this method to
        dynamically change the search fields based on request content.
        """
        # 从视图类中中的 search_fields 列表中映射出所有的 过滤参数字段
        return getattr(view, 'search_fields', None)
	
    # 解析查询参数中的搜索条件,将其分割成一个列表。
    def get_search_terms(self, request):
        """
        Search terms are set by a ?search=... query parameter,
        and may be comma and/or whitespace delimited.
        """
        # 获取到路由尾部携带的参数
        params = request.query_params.get(self.search_param, '')
        
        # 空格代替 空
        params = params.replace('\x00', '')  # strip null characters
        # 多字段进行切分
        params = params.replace(',', ' ')
        # 返回切分后的参数
        return params.split()
	
    # 构建搜索条件,根据前缀选择合适的查询操作。
    # field_name 参数是传递给过滤器的搜索字段名,可能包含前缀
    def construct_search(self, field_name):
        
        #  字典定义了搜索条件的前缀和它们对应的查询操作。
        # 检查 field_name 的第一个字符(前缀),并使用 get 方法从 lookup_prefixes 字典中获取对应的查询操作。
        lookup = self.lookup_prefixes.get(field_name[0])
        
        # 如果前缀存在
        if lookup:
            # 则将前缀从 field_name 中去除,并将查询操作存储在 lookup 变量中。
            field_name = field_name[1:]
        else:
            # 如果前缀不存在,则默认使用 'icontains' 查询操作,这表示执行不区分大小写的部分匹配。
            lookup = 'icontains'
        
        # 使用 LOOKUP_SEP 连接字段名和查询操作,返回最终的搜索条件字符串。
        # LOOKUP_SEP 是Django中用于连接查询字段和操作的分隔符,通常是双下划线 __。
		# 例如,如果传递的搜索字段名是 ^name,那么这个方法将返回 "name__istartswith",这表示要在 name 字段上执行不区分大小写的开始于匹配操作。
        return LOOKUP_SEP.join([field_name, lookup])
	
    # 返回一个布尔值,指示是否应该使用 distinct() 方法来查询结果集以避免重复项。
    # queryset 参数是要进行过滤的查询集,通常是数据库查询的结果集。
	# search_fields 参数是用于搜索的字段列表,这些字段可能包含前缀。
    def must_call_distinct(self, queryset, search_fields):
        """
        Return True if 'distinct()' should be used to query the given lookups.
        """
        
        # 迭代 search_fields 中的每个搜索字段
        for search_field in search_fields:
            
            # 对于每个搜索字段,它获取与查询集相关联的模型的元数据(opts = queryset.model._meta)
            opts = queryset.model._meta
            
            # 检查搜索字段的第一个字符是否在 lookup_prefixes 中
            if search_field[0] in self.lookup_prefixes:
                # 如果前缀存在,则将前缀从搜索字段中去除(search_field = search_field[1:])。
                search_field = search_field[1:]
            # Annotated fields do not need to be distinct
            
            # 检查搜索字段是否已经在查询集的注释中。
            if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
                # 如果字段已经在注释中,说明该字段已经被标记为注释字段,通常不需要使用 distinct()。
                continue
                
            # 如果字段不在注释中,则进一步检查字段是否包含嵌套关系(例如,related_field__nested_field)。
            parts = search_field.split(LOOKUP_SEP)
            
            # 如果字段是嵌套关系,它会更新模型的元数据以跟踪关系路径,并检查是否存在多对多关系(m2m 关系)。
            for part in parts:
                
                field = opts.get_field(part)
                if hasattr(field, 'get_path_info'):
                    # This field is a relation, update opts to follow the relation
                    path_info = field.get_path_info()
                    opts = path_info[-1].to_opts
                    
                    # # 如果字段是多对多关系,就需要调用 distinct() 方法,因为多对多关系通常会导致结果集中的重复项。
                    if any(path.m2m for path in path_info):
                        # This field is a m2m relation so we know we need to call distinct
                        # 如果需要调用 distinct(),它将返回 True
                        return True
                else:
                    # This field has a custom __ query transform but is not a relational field.
                    break
                    
        # 最后,方法返回一个布尔值,指示是否应该调用 distinct()。
        # 如果需要调用 distinct(),它将返回 True,否则返回 False。
        return False
	
    # 实际的查询过滤操作,根据查询参数中的搜索条件修改查询集
    def filter_queryset(self, request, queryset, view):
        # 获取了视图中定义的搜索字段列表(search_fields)和查询参数中传递的搜索条件(search_terms)。
        search_fields = self.get_search_fields(view, request)
        search_terms = self.get_search_terms(request)
		
        # 如果没有定义搜索字段或者没有传递搜索条件
        if not search_fields or not search_terms:
            # 就直接返回原始的查询集 queryset,不进行任何过滤。
            return queryset
		
        # 构建了一个包含了所有搜索字段的ORM查询操作列表 orm_lookups
        # 通过调用 construct_search 方法将搜索字段名转换为相应的查询操作。
        orm_lookups = [
            self.construct_search(str(search_field))
            for search_field in search_fields
        ]
		
        # 始化了两个变量,base 和 conditions。
        # base 是原始的查询集
        # conditions 是用来存储过滤条件的列表。
        base = queryset
        conditions = []
        
        # 迭代每个搜索条件(search_term)和每个查询操作(orm_lookup),并创建一个 Q 对象,将查询操作和搜索条件传递给它。
        # 这个 Q 对象表示了一个或多个查询条件的逻辑或关系。
        for search_term in search_terms:
            
            # 创建的 Q 对象列表存储在 queries 中
            queries = [
                models.Q(**{orm_lookup: search_term})
                for orm_lookup in orm_lookups
            ]
            
            # 然后使用 reduce(operator.or_, queries) 将它们组合成一个包含所有查询条件的 Q 对象。
            # 最终,conditions 列表包含了一个或多个 Q 对象,每个 Q 对象代表一个搜索条件的查询操作。
            # 使用 reduce(operator.and_, conditions) 将所有的 Q 对象组合成一个包含所有搜索条件的查询操作,这个查询操作表示了所有搜索条件之间的逻辑与关系。
            conditions.append(reduce(operator.or_, queries))
            
        

		# 最后,使用 queryset.filter() 方法,将这个复合查询操作应用于原始查询集,以便执行过滤操作。
        queryset = queryset.filter(reduce(operator.and_, conditions))
        
		# 如果必须调用 distinct() 方法(通过调用 must_call_distinct 方法来确定)
        if self.must_call_distinct(queryset, search_fields):
            # Filtering against a many-to-many field requires us to
            # call queryset.distinct() in order to avoid duplicate items
            # in the resulting queryset.
            # We try to avoid this if possible, for performance reasons.
            
            # 在过滤后的查询集上调用 distinct() 以确保结果集不包含重复项
            queryset = distinct(queryset, base)
            
        # 返回过滤后的数据集
        return queryset
	
    # 用于生成搜索框的HTML表示形式,以便在API的浏览器浏览界面中显示
    def to_html(self, request, queryset, view):
        '''
        目的是生成一个包含搜索框的HTML表示,以便在API的浏览器浏览界面中让用户输入搜索条件。
        搜索框的样式和布局通常由指定的模板文件定义,这使得可以根据需要自定义搜索框的外观。
        搜索参数的名称和搜索条件的值也被传递到模板中,以便在HTML中动态生成搜索框。
        '''
        
        # 检查视图是否定义了搜索字段列表(search_fields)。
        if not getattr(view, 'search_fields', None):
            # 如果没有定义搜索字段,就直接返回空字符串,表示不需要显示搜索框。
            return ''
		
        # 获取查询参数中传递的搜索条件(search_terms)。
        term = self.get_search_terms(request)
        
        # 如果搜索条件存在,就将第一个搜索条件(通常只支持一个搜索条件)存储在 term 变量中。
        term = term[0] if term else ''
        
        # 创建一个名为 context 的字典,其中包含两个键值对:
        # 'param':用于指定搜索参数的名称,通常是 search。
        # 'term':包含了搜索条件的值,即用户在搜索框中输入的文本。
        context = {
            'param': self.search_param,
            'term': term
        }
        
        # 使用 Django 的 loader.get_template 函数获取指定模板文件(self.template)的模板对象。
        # 这个模板通常包含了搜索框的HTML表示。
        template = loader.get_template(self.template)
        
        # 使用 template.render(context) 渲染模板,将 context 字典中的数据传递给模板,生成包含搜索框的HTML表示
        return template.render(context)

    def get_schema_fields(self, view):
        '''
        目的是生成一个包含搜索参数的字段定义,以便在API文档中显示搜索参数的相关信息。
        这有助于API用户理解如何使用搜索功能,并构建正确的搜索请求。
        '''
        
        # 通过 assert 语句确保 coreapi 和 coreschema 这两个库已经安装。
        # 这些库是用于构建API文档和描述API数据结构的工具。
        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
        
        # 创建一个列表,其中包含一个 CoreAPI 字段定义对象。
        # 这个字段定义对象用于描述搜索参数。
        # 返回一个包含了搜索参数字段定义的列表。这个列表通常会与其他字段定义一起用于生成API文档,并且可以让API客户端了解如何构建搜索请求
        return [
            coreapi.Field(
                # name:搜索参数的名称,通常是 search。
                name=self.search_param,
                
                # required:指定搜索参数是否是必需的,这里设置为 False 表示不是必需的。
                required=False,
                
                # location:指定搜索参数在请求中的位置,这里设置为 query,表示在查询字符串中。
                location='query',
                
                # schema:定义搜索参数的数据模式,这里使用 coreschema.String 来定义搜索参数的数据类型为字符串,并提供了标题(title)和描述(description)。
                schema=coreschema.String(
                    title=force_str(self.search_title),
                    description=force_str(self.search_description)
                )
            )
        ]
	
    # 用于生成API操作的参数定义,这些参数用于API文档的生成
    # 返回包含了参数定义的列表。这个列表通常会与API操作一起用于生成API文档,以帮助API用户了解如何构建请求和使用参数。
    def get_schema_operation_parameters(self, view):
        '''
        API文档中显示操作的参数信息。这有助于API用户理解如何构建API请求,并知道哪些参数是可选的、哪些是必需的
        '''
        
        # 创建了一个包含参数定义的列表。每个参数定义都是一个字典,描述了一个API操作的参数。
        return [
            {
                # 'name':指定参数的名称,通常是 search。
                'name': self.search_param,
                
                # 'required':指定参数是否是必需的,这里设置为 False 表示不是必需的。
                'required': False,
                
                # 'in':指定参数在请求中的位置,这里设置为 query,表示在查询字符串中。
                'in': 'query',
                
                # 'description':提供参数的描述,使用了 force_str 函数来确保描述是字符串类型。
                'description': force_str(self.search_description),
                
                # 'schema':定义参数的数据模式,这里指定参数的数据类型为字符串('type': 'string')。
                'schema': {
                    'type': 'string',
                },
            },
        ]

【三】第三方过滤类DjangoFilterBackend

【1】使用

# (2) 过滤:必须继承 GenericAPIView 及其子类,才能使用(如果继承APIView,则不能这么写)
# 第三方过滤类: pip3.9 install django-filter
from django_filters.rest_framework import DjangoFilterBackend


class BookView(GenericViewSet, ListModelMixin):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend]  # 排序类
    # SearchFilter:必须配合一个类属性
    # 按照那些字段进行筛选
    # http://127.0.0.1:8000/app01/v1/books/?search=梦
    # 根据指定字段进行筛选,按名字和价格精准匹配
    filterset_fields = ['name', 'price']

【2】源码分析

class DjangoFilterBackend:
    # 用于指定用于过滤的过滤器集的基类,默认为filterset.FilterSet
    filterset_base = filterset.FilterSet
    
    # 指示在过滤器验证失败时是否引发异常,默认为True,表示引发异常。
    raise_exception = True

    @property
    # 用于确定渲染过滤器表单时要使用的模板
    def template(self):
        
        # 如果使用Crispy Forms
        if compat.is_crispy():
            
            # 则为"django_filters/rest_framework/crispy_form.html"
            return "django_filters/rest_framework/crispy_form.html"
        
        # 否则为"django_filters/rest_framework/form.html"。
        return "django_filters/rest_framework/form.html"
	
    # 返回过滤器集的实例
    def get_filterset(self, request, queryset, view):
        
        # 使用get_filterset_class方法来获取用于过滤查询集的FilterSet类。
        # 这个类可以在视图中通过filterset_class属性指定,如果未指定,则会自动创建一个基于查询集的FilterSet类。
        filterset_class = self.get_filterset_class(view, queryset)
        
        # 检查过滤器集类:如果获取到了过滤器集类(filterset_class不为None),则继续执行下面的步骤。
        # 否则,返回None,表示没有可用的过滤器集。
        if filterset_class is None:
            return None
		
        # 获取过滤器集的关键字参数
        # 调用get_filterset_kwargs方法,以获取传递给过滤器集构造函数的关键字参数
        # 。这些参数通常包括请求数据、查询集和请求对象等。
        kwargs = self.get_filterset_kwargs(request, queryset, view)
        
        # 创建过滤器集对象
        # 最后,它使用获取到的过滤器集类和关键字参数来创建过滤器集对象,并将其返回。
        return filterset_class(**kwargs)
	
    # 返回用于过滤查询集的FilterSet类
    def get_filterset_class(self, view, queryset=None):
        """
        # 这个方法会根据视图和查询集的情况来确定应该使用哪个 FilterSet 类
        Return the `FilterSet` class used to filter the queryset.
        """
        
        # 尝试从视图中获取 filterset_class 属性。这个属性是视图中定义的,可以指定要用于过滤的 FilterSet 类。
        # 如果视图中有指定,则直接返回这个类。
        filterset_class = getattr(view, "filterset_class", None)
        
        # 如果视图中没有指定 filterset_class,它会检查是否有 filterset_fields 属性。
        # filterset_fields 属性是一组用于过滤的字段名称,通常与查询集的模型相关。
        filterset_fields = getattr(view, "filterset_fields", None)
		
        if filterset_class:
            filterset_model = filterset_class._meta.model
            # FilterSets do not need to specify a Meta class
            if filterset_model and queryset is not None:
                assert issubclass(
                    queryset.model, filterset_model
                ), "FilterSet model %s does not match queryset model %s" % (
                    filterset_model,
                    queryset.model,
                )

            return filterset_class
		
        # 如果视图中定义了这个属性
        if filterset_fields and queryset is not None:
            # 它会自动创建一个临时的 FilterSet 类,该类的 Meta 类中包含了模型和字段信息。
            MetaBase = getattr(self.filterset_base, "Meta", object)

            class AutoFilterSet(self.filterset_base):
                class Meta(MetaBase):
                    model = queryset.model
                    fields = filterset_fields

            return AutoFilterSet
        
		 # 如果既没有指定 filterset_class 也没有 filterset_fields,则返回 None,表示没有可用的 FilterSet 类。
        return None
	
    # 此方法返回传递给过滤器集构造函数的关键字参数,包括请求数据、查询集和请求对象。
    def get_filterset_kwargs(self, request, queryset, view):
        return {
            "data": request.query_params,
            "queryset": queryset,
            "request": request,
        }
	
    # 此方法用于过滤查询集。,,。
    def filter_queryset(self, request, queryset, view):
        # 通过调用 get_filterset 方法,获取与视图关联的过滤器集实例 filterset。
        filterset = self.get_filterset(request, queryset, view)
        
        # 检查 filterset 是否为 None。
        if filterset is None:
            # 如果 filterset 为 None,说明没有可用的过滤器集与视图关联,此时直接返回原始的查询集 queryset,不进行任何过滤。
            return queryset
		
        # 如果 filterset 存在,方法会进一步验证过滤器集的有效性,即调用 is_valid 方法检查过滤器集是否通过了验证。
        if not filterset.is_valid() and self.raise_exception:
             # 如果过滤器集无效(is_valid 返回 False)并且 self.raise_exception 属性为 True,则会抛出验证异常,该异常包含了过滤器集的错误信息。
            raise utils.translate_validation(filterset.errors)
            
        # 然后将其应用于查询集
        return filterset.qs
	
    # 此方法返回过滤器表单的HTML表示。
    def to_html(self, request, queryset, view):
        # 调用 get_filterset 方法获取过滤器集实例 filterset。
        filterset = self.get_filterset(request, queryset, view)
        
        # 如果 filterset 为 None,则返回 None,表示没有可用的过滤器集,因此无法生成过滤器表单的 HTML 表示。
        if filterset is None:
            return None
		
        # 如果 filterset 存在,方法会根据指定的模板(self.template)渲染过滤器表单。
        # 通常,模板会包含 HTML 表单元素,以显示过滤器字段和相应的输入框、复选框等表单组件。
        template = loader.get_template(self.template)
        context = {"filter": filterset}
        
        # 最终,方法返回渲染后的 HTML 表示,以便在前端页面中显示过滤器表单
        return template.render(context, request)
	
    # 此方法返回与过滤器字段相关的CoreAPI字段定义
    def get_coreschema_field(self, field):
        # 首先,它根据过滤器字段的类型判断,
        if isinstance(field, filters.NumberFilter):
            
            # 如果字段类型是 filters.NumberFilter,则生成一个 compat.coreschema.Number 字段
            field_cls = compat.coreschema.Number
        else:
            
            # 否则生成一个 compat.coreschema.String 字段。
            field_cls = compat.coreschema.String
            
        # 字段的 description 属性通常设置为过滤器字段的帮助文本(help_text),以提供关于字段用途的描述信息。
        # 最终,方法返回生成的 CoreAPI 字段定义,用于 API 文档的生成和展示
        return field_cls(description=str(field.extra.get("help_text", "")))
	
    # 此方法返回API操作的参数定义列表,用于生成API文档。它检查过滤器集的基本过滤器,并为每个字段创建一个参数定义。
    def get_schema_fields(self, view):
        # This is not compatible with widgets where the query param differs from the
        # filter's attribute name. Notably, this includes `MultiWidget`, where query
        # params will be of the format `<name>_0`, `<name>_1`, etc...
        
        # 检查 Django 的 RemovedInDjangoFilter25Warning
        from django_filters import RemovedInDjangoFilter25Warning
        
        # 并发出警告,指出内置的模式生成已被弃用,建议使用 drf-spectacular。
        warnings.warn(
            "Built-in schema generation is deprecated. Use drf-spectacular.",
            category=RemovedInDjangoFilter25Warning,
        )
        
        # 断言 coreapi 和 coreschema 库已安装,因为这些库用于生成 API 文档的参数字段。
        assert (
            compat.coreapi is not None
        ), "coreapi must be installed to use `get_schema_fields()`"
        assert (
            compat.coreschema is not None
        ), "coreschema must be installed to use `get_schema_fields()`"
		
        # 方法尝试从视图中获取查询集(queryset)对象。
        try:
            # 这是通过调用视图的 get_queryset() 方法来完成的,以便获取与视图关联的查询集。
            queryset = view.get_queryset()
        except Exception:
            
            # 如果无法获取查询集(通常是因为视图没有实现 get_queryset() 方法),则将 queryset 设置为 None
            queryset = None
            # 并发出警告,指出该视图不兼容模式生成。
            warnings.warn(
                "{} is not compatible with schema generation".format(view.__class__)
            )
		
        # 使用 get_filterset_class 方法来获取与视图关联的过滤器集类(filterset_class)
        filterset_class = self.get_filterset_class(view, queryset)

        return (
            []
            # 如果过滤器集类已经在视图中定义(通过 filterset_class 属性),则使用该类
            if not filterset_class
            
            # 如果没有可用的过滤器集类,方法返回一个空列表 [],表示没有需要生成参数字段的过滤器。
            else [
                # 否则,如果视图定义了 filterset_fields 属性且查询集不为 None,则会动态创建一个自动生成的过滤器集类 AutoFilterSet,该类继承自 filterset_base,并使用查询集的模型和 filterset_fields 属性来定义过滤器集。
                compat.coreapi.Field(
                    
                    # name 属性设置为字段的名称。
                    name=field_name,
                    
                    # required 属性根据字段的 required 属性设置。
                    required=field.extra["required"],
                    
                    # location 属性设置为 "query",表示这些参数位于查询字符串中
                    location="query",
                    
                    # schema 属性通过调用 get_coreschema_field 方法生成,该方法生成与字段相关的 CoreAPI 字段定义
                    schema=self.get_coreschema_field(field),
                )
                # 
                for field_name, field in filterset_class.base_filters.items()
            ]
        )
	
    # 此方法返回API操作的参数列表,用于生成API文档。
    # 它与get_schema_fields类似,但提供了更详细的参数信息,包括类型、是否必需等。
    def get_schema_operation_parameters(self, view):
        
        # 检查 Django 的 RemovedInDjangoFilter25Warning
        from django_filters import RemovedInDjangoFilter25Warning
        
        # 并发出警告,指出内置的模式生成已被弃用,建议使用 drf-spectacular
        warnings.warn(
            "Built-in schema generation is deprecated. Use drf-spectacular.",
            category=RemovedInDjangoFilter25Warning,
        )
        try:
            # 尝试从视图中获取查询集(queryset)对象,以便确定与视图关联的模型。
            queryset = view.get_queryset()
        except Exception:
            
            # 如果无法获取查询集(通常是因为视图没有实现 get_queryset() 方法),则将 queryset 设置为 None
            queryset = None
            
            # 并发出警告,指出该视图不兼容模式生成。
            warnings.warn(
                "{} is not compatible with schema generation".format(view.__class__)
            )
		
        # 使用 get_filterset_class 方法来获取与视图关联的过滤器集类(filterset_class)
        # 如果过滤器集类已经在视图中定义(通过 filterset_class 属性),则使用该类
        # 否则,如果视图定义了 filterset_fields 属性且查询集不为 None,则会动态创建一个自动生成的过滤器集类 AutoFilterSet,该类继承自 filterset_base,并使用查询集的模型和 filterset_fields 属性来定义过滤器集。
        filterset_class = self.get_filterset_class(view, queryset)
        
        # 如果没有可用的过滤器集类,方法返回一个空列表 [],表示没有需要生成参数字段的过滤器。
        if not filterset_class:
            return []
		
        parameters = []
        # # 遍历过滤器集类的基本过滤器字段(base_filters)并为每个字段创建一个参数定义
        for field_name, field in filterset_class.base_filters.items():
            
            # 
            parameter = {
                
                # name 属性设置为字段的名称。
                "name": field_name,
                
                # required 属性根据字段的 required 属性设置。
                "required": field.extra["required"],
                
                # in 属性设置为 "query",表示这些参数位于查询字符串中。
                "in": "query",
                
                # description 属性设置为字段的标签(label),如果字段没有标签,则设置为字段的名称。
                "description": field.label if field.label is not None else field_name,
                
                # schema 属性是一个包含参数类型信息的字典。在这里,所有参数都被表示为字符串("type": "string")。
                "schema": {
                    "type": "string",
                },
            }
            
            # 如果字段的 extra 属性包含了 "choices" 键(即字段具有选项)
            if field.extra and "choices" in field.extra:
                
                # 将 schema 字典的 "enum" 键设置为字段选项的列表。
                parameter["schema"]["enum"] = [c[0] for c in field.extra["choices"]]
                
            parameters.append(parameter)
        return parameters

【四】自定义过滤类

【1】使用

from rest_framework import filters
from django.db.models import Q


class BookFilter(filters.BaseFilterBackend):
    '''
        def filter_queryset(self, request, queryset, view):
        """
        Return a filtered queryset.
        """
        raise NotImplementedError(".filter_queryset() must be overridden.")
    '''

    def filter_queryset(self, request, queryset, view):
        # 返回的数据,都是过滤后的数据
        # http://127.0.0.1:8000/app01/v1/books/?price=99&name=追梦赤子心
        # 需要将筛选条件变成price=99或name=追梦赤子心,而第三方写法中访问地址只能是上面的格式
        price = request.query_params.get('price')
        name = request.query_params.get('name')
        queryset = queryset.filter(Q(name=name) | Q(price=price))

        return queryset

【2】分析

  • 根据地址栏中传入的数据进行过滤
  • 将过滤完的 queryset 数据集返回