Django restframwork中使用分页及实现自定义分页

关于为何要分页 以及如何在Django+Template架构中如何使用分页,可以参考之前的文章

django 自定义分页类和使用总结


Django RestFramework中分页限制

今天开篇我们先不讲如何使用,我们先说 Django + restframework 实现前后端分离项目开发时, 分页功能使用的限制?

缘由是之前在开发运维平台的时候,没有正确使用分页功能,导致 自定义的分页在不同情况下,有的不能用,有的能用。最终原因是看实际使用的时候是 继承的那个类

这里根据实例使用类视图 时的继承关系梳理了如下图

Django RestFramework 列表相关类视图继承关系

这里需要重点关注的是:

1、generics.ListAPIViewgenerics.ListCreateAPIView 继承自对应的 XXXModelMixin 和 GenericAPIView

2、ModelViewSet 继承自 对应的 XXXModelMixin 和 GenericViewSet, 而 GenericViewSet又继承自 ViewSetMixin 和 GenericAPIView

我们查阅 RestFramework 的源码知道,关于分页的逻辑实现,是在 GenericAPIView中实现的,

class GenericAPIView(views.APIView):
    ... ...

    # The style to use for queryset pagination.
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    ... ...

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator

    def paginate_queryset(self, queryset):
        """
        Return a single page of results, or `None` if pagination is disabled.
        """
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)

    def get_paginated_response(self, data):
        """
        Return a paginated style `Response` object for the given output data.
        """
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

而使用分页的情况下,都会实现list()方法,而在 ListModelMixin中 就调用了 self.paginate_querysetself.get_paginated_response

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

所以综上所述:

Django Restframework 中只有继承以下类的情况才可以使用分页

  • viewsets.ModelViewSet

  • generics.ListAPIView

  • generics.ListCreateAPIView


继承 ModelViewSet 实现分页

1、先定义 serializer

为了方便,我们直接使用 ModelSerializer

# appdemo.serializers.py

from rest_framework.serializers import ModelSerializer
from appdemo.models import Post


class PostModelSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

2、定义viewset

from rest_framework.viewsets import ModelViewSet
# 这里导入
from rest_framework.pagination import PageNumberPagination

from appdemo.models import Post
from appdemo.serializers import PostModelSerializer

class PostModelViewSet(ModelViewSet):
    serializer_class = PostModelSerializer
    queryset = Post.objects.all()
    # 这是视图级别配置分页
    pagination_class = PageNumberPagination

使用 RestFramework 分页时,也可以全局配置

# settings.py
REST_FRAMEWORK ={
    # 全局配置
    # 'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',

    # 但是至少要配置 PAGE_SIZE, 因为系统默认 PAGE_SIZE = NONE 相当于没有分页
    'PAGE_SIZE': 3 
}

3、配置viewset对应的路由

和 一般类视图不一样的是, ModelViewSet 定义路由使用如下配置

# appdemo.urls.py
from appdemo.views import PostModelViewSet

router = DefaultRouter()
router.register('post', PostModelViewSet)

urlpatterns = [... ...]
urlpatterns += router.urls

然后就可以通过 http://127.0.0.1:8092/demo/post/ 来访问了

继续 ListAPIView 实现分页

# 这里导入
from rest_framework.pagination import PageNumberPagination
from rest_framework.generics import ListAPIView
from appdemo.models import Post
from appdemo.serializers import PostModelSerializer


class PostListAPIView(ListAPIView):
    serializer_class = PostModelSerializer
    queryset = Post.objects.all()
    pagination_class = PageNumberPagination

和上面 ModelViewSet 不同的是, ListAPIView 只是实现了list()方法,而 ModelViewSet 实现 create()list()retrieve()update()destory() 方法

自定义分页

其实RestFramework 默认提供了三种分页方式

  • PageNumberPagination 普通分页器

支持用户按?page=3&size=10这种更灵活的方式进行查询,这样用户不仅可以选择页码,还可以选择每页展示数据的数量。

但是一般强烈建议要配置 max_page_size, 防止超大数据量的”恶意“查询

  • LimitOffsetPagination 偏移分页器

支持用户按?limit=20&offset=100这种方式进行查询。offset是查询数据的起始点,limit是每页展示数据的最大条数,类似于page_size。

偏移分页器建议设置 max_limit 限制单页查询的数据量

  • CursorPagination 游标分页器

这是DRF提供的加密分页查询, 不返回具体的页码、大小或者是起始位置,仅支持用户按响应提供的上一页和下一页链接进行分页查询,每页的页码都是加密的。

使用这种方式进行分页需要你的模型有”created”这个字段,否则你要手动指定ordering排序才能进行使用。

了解这三种分页器之后,下面我们开始自定义

# appdemo.pagination.py

from rest_framework.pagination import PageNumberPagination


class CwsPageNumberPagination(PageNumberPagination):
    # 覆盖默认的PAGE_SIZE = None
    page_size = 5
    # 自定义页面大小参数
    page_size_query_param = 'size'
    # 设置每页最大数据量, 默认为None,就是不限制
    max_page_size = 100
    # 自定义页码参数
    page_query_param = 'page'

一般如上代码基本就满足我们定制化的需求,但是在前后端分离架构中,有时候会 自定义响应

如果存在分页的话,从 mixins.ListModelMixin 类中我们知道它是直接使用 rest_framework.response.Response 进行返回的。 (参考文章上面提到的源码)

那么在ModelViewSet中就存在 list() 是 按照 rest_framework.response.Response 返回,而其他方法按照 自定义响应 返回,就存在返回格式不统一的问题

所以这个时候,一般需要在自定义分页器中实现get_paginated_response()方法,该方法中使用自定义响应类返回就行。

具体如何自定义响应,我们后续文章在介绍

使用自定义分页很简单,在全局配置文件settings.py 中配置 REST_FRAMEWORK 的 DEFAULT_PAGINATION_CLASS 值为 自定义分页器 或者 在视图类中配置 pagination_class

具体实现之后的效果, 建议大家自己动手尝试下, 效果如下

[图片上传失败...(image-500c1b-1688631738236)]

关于 LimitOffsetPagination 偏移分页器 和 CursorPagination 游标分页器

这里直接给出相关代码,感兴趣的可以自己挨个敲代码实现看看效果,更容易加深理解

# appdemo.pagination.py

class CwsLimitOffsetPagination(LimitOffsetPagination):
    # 默认页面大小
    default_limit = 5  
    # 最大页面限制
    max_limit = 10
    # 页面 大小参数和 起始参数
    limit_query_param = 'limit'  
    offset_query_param = 'offset'  


class CwsCursorPagination(CursorPagination):
    # page_size 和 page_size_query_param 含义和 PageNumberPagination 一样
    page_size = 3
    page_size_query_param = 'page_size' 
    # 默认的游标参数
    cursor_query_param = 'cursor'
    # 如果模型中没有 created 字段,那么就需要 明确配置 oridering 
    ordering = '-create_date'

今天的知识就介绍到这里。 如果觉得文章对你有用,请不吝点赞 和 关注个人公众号(搜索 全栈运维 或者 DailyJobOps)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 141,714评论 1 298
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 60,817评论 1 254
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 93,456评论 0 211
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,811评论 0 174
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,501评论 1 252
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,648评论 1 173
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,305评论 2 267
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,107评论 0 165
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,870评论 6 228
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,441评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,211评论 2 213
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,531评论 1 225
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,189评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,074评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,433评论 3 204
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,596评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 25,953评论 0 164
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,423评论 2 230
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,515评论 2 229

推荐阅读更多精彩内容