django_rest_framework一句代码搞定分页 LimitOffsetPagination原理详解

Django Rest Framework DRF一句代码实现分页之原理分析 ---LimitOffsetPagination篇

  • 一句代码实现分页?
    • 代码分解
  • list方法完成的第一件事
  • list方法完成的第二件事
  • list方法完成的第三件事

一句代码实现分页?

前段时间在网上搜例子,对着葫芦画瓢,通过重写一些方法等实现了分页,略复杂。其实有更简单的方式,先看怎么用一句代码实现分页?
models.py:

class LoginPrison(models.Model):
    id = models.AutoField(primary_key=True)
    account_id = models.IntegerField()
    occured_time = models.DateTimeField()
    type = models.IntegerField()

    class Meta:
        db_table = 'login_prison'
        ordering = ('-occured_time',)

serializers.py:

class LoginPrisonSerializer(serializers.ModelSerializer):
    class Meta:
        model = LoginPrison
        fields = '__all__'

urls.py:

router = routers.SimpleRouter()
router.register(r'TV', views.TestViewSet)
urlpatterns = router.urls

views.py:

class TestViewSet(mixins.RetrieveModelMixin,
                  mixins.DestroyModelMixin,
                  mixins.ListModelMixin,
                  viewsets.GenericViewSet):
    queryset = LoginPrison.objects.all()
    serializer_class = LoginPrisonSerializer
    pagination_class = LimitOffsetPagination

其实上面代码分页的核心就一句:pagination_class = LimitOffsetPagination
(当然还有其他分页类,此处只介绍一个类即可,因为原理类似)

看看postgresql数据库:
django_rest_framework一句代码搞定分页 LimitOffsetPagination原理详解_第1张图片
然后运行程序看下效果:(注意:offset是起始查询位置)

django_rest_framework一句代码搞定分页 LimitOffsetPagination原理详解_第2张图片

views.py中只是继承了几个类,重写了几个类属性,一个方法都没写就轻轻松松完成了分页,好神奇,下面跟踪下代码,记录下过程:

代码分解

前端输入http://120.79.84.147:3389/show/TV/?limit=4&offset=1,url解析对应到views.TestViewSet。
下面再次看看views.py:

class TestViewSet(mixins.RetrieveModelMixin,
                  mixins.DestroyModelMixin,
                  mixins.ListModelMixin,
                  viewsets.GenericViewSet):
    queryset = LoginPrison.objects.all()
    serializer_class = LoginPrisonSerializer
    pagination_class = LimitOffsetPagination

继承了4个类,并且初始化了3个类属性queryset、serializer_class、pagination_class。由于我们前端用的get方式请求,直接在源码中定位到父类ListModelMixin中:

class ListModelMixin(object):
    """
    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)

可以发现父类ListModelMixin中有get请求返回的list方法,即调用此处的list方法。
下面分析list方法完成了哪几项工作。
注意:此篇只分析分页相关步骤

list方法完成的第一件事

当运行到page = self.paginate_queryset(queryset)时,调用paginate_queryset方法,
由于是多重继承所以此处会去其他父类中找paginate_queryset方法,先去GenericViewSet类中找:

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass

再去ViewSetMixin类、GenericAPIView类中找,最后再GenericAPIView类中找到了:

    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)

该方法第一句 返回self.paginator属性,找到对应源码:

    @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

可知,self.paginator 返回的self.pagination_class(),就是在views.py中的TestViewSet类中初始化的一个类属性:pagination_class = LimitOffsetPagination。而方法paginate_queryset返回:

return self.paginator.paginate_queryset(queryset, self.request, view=self)

解析之后方法paginate_queryset返回的是LimitOffsetPagination.paginate_queryset(queryset, self.request, view=self)
下面看看LimitOffsetPagination类中定义的类属性以及即将被调用的paginate_queryset方法:

class LimitOffsetPagination(BasePagination):
    """
    A limit/offset based style. For example:

    http://api.example.org/accounts/?limit=100
    http://api.example.org/accounts/?offset=400&limit=100
    """
    default_limit = api_settings.PAGE_SIZE
    limit_query_param = 'limit'
    limit_query_description = _('Number of results to return per page.')
    offset_query_param = 'offset'
    offset_query_description = _('The initial index from which to return the results.')
    max_limit = None
    template = 'rest_framework/pagination/numbers.html'
    def paginate_queryset(self, queryset, request, view=None):
        self.count = self.get_count(queryset)
        self.limit = self.get_limit(request)
        if self.limit is None:
            return None

        self.offset = self.get_offset(request)
        self.request = request
        if self.count > self.limit and self.template is not None:
            self.display_page_controls = True

        if self.count == 0 or self.offset > self.count:
            return []
        return list(queryset[self.offset:self.offset + self.limit])

看上图代码中paginate_queryset方法完成的步骤:
步骤1:先通过方法getcount、get_limit以及get_offset获取count、limit和offset

    def get_limit(self, request):
        if self.limit_query_param:
            try:
                return _positive_int(
                    request.query_params[self.limit_query_param],
                    strict=True,
                    cutoff=self.max_limit
                )
            except (KeyError, ValueError):
                pass

        return self.default_limit
    def get_count(self, queryset):
        """
        Determine an object count, supporting either querysets or regular lists.
        """
        try:
            return queryset.count()
        except (AttributeError, TypeError):
            return len(queryset)

步骤2:判断如果查询数据的个数大于每页数据个数模板template不为空(注意:类属性template = ‘rest_framework/pagination/numbers.html’),把父类BasePagination的默认为False的类属性display_page_controls设为True。

        if self.count > self.limit and self.template is not None:
            self.display_page_controls = True

判断如果查询数据个数为0或者查询数据起始位大于数据个数,则返回空列表:

        if self.count == 0 or self.offset > self.count:
            return []

步骤3:return list(queryset[self.offset:self.offset + self.limit]) 返回一个装满查询集结果的列表(用切片实现选取所需数据片段

  • 好了 以上就是类ListModelMixin中list方法第一句返回的内容即queryset指向刚返回的列表:
       queryset = self.filter_queryset(self.get_queryset())

总结:list方法做的第一件事就是让queryset指向根据前端输入过滤后的查询集的列表

list方法完成的第二件事

serializer = self.get_serializer(queryset, many=True)

如上面的代码,调用GenericAPIView类中的get_serializer方法,该方法完成的步骤:
步骤1:获取通过方法get_serializer_class,获取views.py中TestViewSet中定义的类属性serializer_class = LoginPrisonSerializer;
步骤2:通过方法get_serializer_context返回包含了request、format和view参数的字典,并把该字典以键值对的方式存入kwargs[‘context’]中—生成serializer对象时添加额外字段会用到context参数;
步骤3:返回serializer_class(*args, **kwargs)即返回LoginPrisonSerializer类的serializer对象;

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class() #步骤1
        kwargs['context'] = self.get_serializer_context() #步骤2
        return serializer_class(*args, **kwargs)#步骤3
    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

总结:list方法做的第二件事就是返回LoginPrisonSerializer类的serializer对象,序列化

list方法完成的第三件事

	return Response(serializer.data)  # 返回序列化后的数据

所以在views.py中就几行代码简简单单的实现了分页,然后根据前端传入limit和offset,就能看到对应的结果了:
django_rest_framework一句代码搞定分页 LimitOffsetPagination原理详解_第3张图片

你可能感兴趣的:(django,rest,framework)