• django-rest-framework-源码解析005-分页器/过滤器/版本控制


    分页器/过滤器只有在视图类继承GenericAPIView或者viewsets的类的时候才会自动执行分页, 如果继承的是常规APIView,则需要自己调用分页API,以确保响应中包含分页数据。

    我们可以看到在GenericAPIView类中定义了过滤类属性 filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 和分页类属性 pagination_class = api_settings.DEFAULT_PAGINATION_CLASS 

    对应的方法是 filter_queryset(self, queryset) 和 paginator(self) , 可以看到settings默认的过滤器类是空列表 'DEFAULT_FILTER_BACKENDS': [] , 分页器类是None 'DEFAULT_PAGINATION_CLASS': None 

    过滤器(Filter)

    过滤器的作用就是将查询的数据进行过滤和排序, 且过滤和排序规则可以用户在url中指定. 如只查询名称中带有'记'的内容, 并将查询结果按创建降序排序

    在rest_framework.filters中定义了两个过滤器类,  OrderingFilter 和 SearchFilter 

    SearchFilter(查询过滤器)

    支持基于简单的单个查询参数的搜索, 在SearchFilter类中我们可以看到两个关键类属性(search_param / lookup_prefixes)和一个实例方法(get_search_fields)

    class SearchFilter(BaseFilterBackend):
        # URL中表示查询的关键字, 默认为search
        search_param = api_settings.SEARCH_PARAM
        # 查询内容的前缀符号
        '''
        ^ 以指定内容开始
        = 完全匹配
        @ 全文搜索(目前只支持Django的MySQL后端)
        $ 正则搜索
        '''
        lookup_prefixes = {
            '^': 'istartswith',
            '=': 'iexact',
            '@': 'search',
            '$': 'iregex',
        }
        # 在类属性search_fields中获取查询针对的字段, 可以是一个可迭代对象
        def get_search_fields(self, view, request):
            return getattr(view, 'search_fields', None)

    在使用SearchFilter类时必须在类视图中定义search_fields, 但是search_fields 只支持文本类型字段,例如 CharField 或 TextField 。

    并且可以使用双下划线对ForeignKey或ManyToManyField执行关系查询, 如查询出版社的名称字段, 则为publish__name

    class BookAuthViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
        ......代码省略
        # 过滤器
        filter_backends = [SearchFilter]
        search_fields = ['name', 'publish__name']

    默认情况下,搜索不区分大小写,并使用部分匹配的模式。实际上,可以同时有多个搜索参数,用空格和/或逗号分隔。 如果使用多个搜索参数,则仅当所有提供的模式都匹配时才在列 表中返回对象。

    如查询书籍名称, 通过 search=h 能查出 python 和 php, 而通过 search=h o 只能查出python

     OrderingFilter(排序过滤器)

     该过滤器主要实现排序功能, 在OrderingFilter类中我们可以看到两个关键类属性(ordering_param / ordering_fields)和一个实例方法(get_default_ordering)

    class OrderingFilter(BaseFilterBackend):
        # URL中表示排序的关键字, 默认为ordering
        ordering_param = api_settings.ORDERING_PARAM
        # 在类属性中指定只能通过这些字段进行排序
        # 若未指定或指定为'__all__'则表示可以通过所有表字段进行排序
        ordering_fields = None
        # 在类属性ordering中获取默认的排序字段
        def get_default_ordering(self, view):
            ordering = getattr(view, 'ordering', None)
            if isinstance(ordering, str):
                return (ordering,)
            return ordering

    我们可以继承该类创建自己的排序类, 主要是想要限制可排序的字段, 这有助于防止意外的数据泄漏,例如不小心允许用户针对密码字段或其他敏感数据进行排序。

    class MyOrderingFilter(OrderingFilter):
        # 指定可以在哪些字段上进行排序过滤, 不指定或者指定为 __all__表示可以对所有字段排序
        ordering_fields = ['name', 'create_time', 'price']

    然后在视图类中指定排序器类, 并设置默认的排序字段(负号表示降序)

    class BookAuthViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
        # 过滤器
        filter_backends = [SearchFilter, MyOrderingFilter]
        # 查询的字段
        search_fields = ['name', 'publish__name']
        # 默认排序字段, 负号表示降序
        ordering = ['-price', 'name']

    分页器(Pagination)

    分页器的作用就是将查询的数据分页返回给前端, 这样可以减少前后台数据传输的大小, 让用户只查询指定部分的数据, 减少了无用数据的展示. 如只查询第三页的内容, 每页展示10条数据.

    在rest_framework.pagination中定义了三个分页器类,  PageNumberPagination 和 LimitOffsetPagination 和 CursorPagination 

    PageNumberPagination

    在PageNumberPagination类中定义了几个关键的类属性

    class PageNumberPagination(BasePagination):
        # 每页的数据量大小, 默认为None
        page_size = api_settings.PAGE_SIZE
        # url中页码的参数名
        page_query_param = 'page'
        # 也可以通过url参数控制每页的数据量大小
        page_size_query_param = None
        # url中最大能输入的页码数
        max_page_size = None

    我们可以直接使用该类作为分页器类, 也可以创建继承该类的自定义分页器类, 只需要重写这些类属性就可以了, 如:

    class MyPageNumberPagination(PageNumberPagination):
        # 每页的数据量大小, 默认为None
        page_size = 2
        # url中页码的参数名
        page_query_param = 'page_num'
        # 也可以通过url参数控制每页的数据量大小
        page_size_query_param = 'page_size'
        # url中最大能输入的页码数
        max_page_size = 10

    实际效果为:

      1. url中不输入任何参数, 那么自动展示第一页, 且每页展示2条数据

      2. url中加入参数 page_num=n , 则展示第n页的数据, 且每页展示2条数据

      3. url中再加入参数 page_size=m, 则该页展示的数据为m条, 而不只是2条

      4. url中参数 page_num=n, 若n大于10 , 则报错页码错误

    LimitOffsetPagination

    该分页器类似于mysql的limit和offset, 即从第一条数据开始往下偏移offset条数据, 从第offset数据开始拿limit条数据展示出来

    同样在LimitOffsetPagination类中定义了几个关键的类属性, 我们可以自定义分页器类继承该类:

    class MyLimitOffsetPagination(LimitOffsetPagination):
        # 每次展示的数据量大小, 默认为None
        default_limit = 4
        # url中每次展示数据量大小的参数名
        limit_query_param = 'limit'
        # url中偏移量的参数名
        offset_query_param = 'offset'
        # 每次展示的数量的最大值
        max_limit = 10

    实际效果为:

      1. url中不输入任何参数, 那么偏移量offset=0, 展示第一到第四条数据

      2. url中加入参数 limit=n , 那么偏移量offset=0, 展示第一到第n条数据

      3. url中再参数 offset=m, 那么偏移量offset=m, 展示第m到第m+n条数据

      4. url中参数 limit=n, 若n大于10 , 则报错页码错误

    CursorPagination

    该分页器与 PageNumberPagination 分页器功能类似, 只是url中不再是page=n, 而是 cursor=cD0yMDIwLTA3LT, 也就是说这个分页器将url的页码数进行了加密, 这样导致不能在url中随意输入想要跳转的页码, 只能通过放回的json字符串得知上一页的url和下一页的url, 这样能够起到保护数据的作用.

    同样在CursorPagination类中定义了几个关键的类属性, 我们可以自定义分页器类继承该类:

    class MyCursorPagination(CursorPagination):
        # url中游标变量名
        cursor_query_param = 'cursor'
        # 每页的数据量大小, 默认为None
        page_size = 4
        # 排序方式, 负号-create_time表示倒序
        ordering = 'create_time'
        # 也可以通过url参数控制每页的数据量大小
        page_size_query_param = 'page_size'
        # url中最大能输入的页码数
        max_page_size = None

    实际效果为:

      1. url中不输入任何参数, 展示第一页的4条数据, 并在返回的json数据中可以拿到下一页的地址

      2. url中加入参数 page_size=m, 则该页展示的数据为m条, 而不只是2条

    版本(Versioning)

    RESTful规范指出api最好都有版本控制, 而版本控制由传入的请求决定,可以放在URL上,也可以放在请求头中

    DRF的版本模块对于版本信息所处的位置都有对应的类控制, 最终版本信息会放在request.version 属性中。 默认情况下,版本控制没有启用, request.version 将总是返回 None 。

    源码流程

    在dispatch中的 self.initial(request, *args, **kwargs) 中, 执行三大认证之前, 调用了 determine_version() 将版本的信息赋给了request对象, 

        def initial(self, request, *args, **kwargs):
             ......代码省略
    
            # Determine the API version, if versioning is in use.
            version, scheme = self.determine_version(request, *args, **kwargs)
            request.version, request.versioning_scheme = version, scheme
    
            # Ensure that the incoming request is permitted
            # 认证
            self.perform_authentication(request)
            # 权限
            self.check_permissions(request)
            # 限流
            self.check_throttles(request)

    在 determine_version() 方法中拿到了配置的版本控制器类, 并调用了版本类的 determine_version() 方法, 说明版本类必须有 determine_version() 这个方法

        def determine_version(self, request, *args, **kwargs):
            """
            If versioning is being used, then determine any API version for the
            incoming request. Returns a two-tuple of (version, versioning_scheme)
            """
            if self.versioning_class is None:
                return (None, None)
            scheme = self.versioning_class()
            return (scheme.determine_version(request, *args, **kwargs), scheme)

    结合上一步操作, 我们可以看到赋给request对象的版本信息为: request.version 表示的是版本的信息, request.versioning_scheme 表示的是版本类的实例对象

    版本类的设置 versioning_class 可以在类视图中设置, 也可以在项目的settings中设置, 一般我们会在settings中全局设置

    在rest_framework.versioning中也定义了几个版本控制类:

      BaseVersioning:基类,用于继承

      AcceptHeaderVersioning(BaseVersioning):版本信息放在请求头中时使用

      URLPathVersioning(BaseVersioning):版本信息放在URL中时使用

      NamespaceVersioning(BaseVersioning):版本信息放在URL配置中时使用

      HostNameVersioning(BaseVersioning):版本信息放在域名中时使用

      QueryParameterVersioning:版本信息放在URL的参数中时使用

     他们都重写了两个方法:

      determine_version(): 用于获取版本号

      reverse(): 用于反向解析, 其功能就是继承的Django的反向解析 django.urls.reverse, 即通过urls.py配置文件中的name参数和request即可反向解析到具体的url地址

    版本类的用法

    这里以常见的 URLPathVersioning 为例, 其他的类修改响应的配置即可:

    1. 在视图类中定义类属性 versioning_class (这是局部配置, 也可在项目的settings中进行全局配置), 为了查看获取到的版本信息, 我们把request.version和反向解析到的地址也打印一下

    class BookAuthViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
        .....代码省略
        # 版本
        versioning_class = URLPathVersioning
    
        def list(self, request, *args, **kwargs):print(request.version)
            print(request.versioning_scheme.reverse(viewname='v7', request=request))

    2. 在urls.py中设置path路径, 设置了一个version变量, 并设置了name属性, 用于反向解析

    urlpatterns = [
        ...
        path('<version>/', views.BookAuthViewSet.as_view({'get': 'list'}), name='v7'),
    ]

    我们访问url查看结果, 接口数据返回正常, 控制台也打印出了版本信息和反向解析的地址

  • 相关阅读:
    SET/JFace ProgressIndicator的使用以及来回滚动进度条实际使用示例
    Java/SWT/JFace项目打包总结注意事项
    StyledText实现的简单Java类文件编辑器
    命令行界面的简单银行系统
    TC二次开发遇到的问题总结
    用Java模仿简单的Ping命令
    Oracle SQL Developer定制每天执行一次存储过程的计划任务
    关于SpringMVC中text/plain的编码导致的乱码问题解决方法
    Spring框架下的定时任务quartz框架的使用
    Delphi 函数参数修饰中的var 、out和const
  • 原文地址:https://www.cnblogs.com/gcxblogs/p/13369554.html
Copyright © 2020-2023  润新知