前段时间在网上搜例子,对着葫芦画瓢,通过重写一些方法等实现了分页,略复杂。其实有更简单的方式,先看怎么用一句代码实现分页?
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数据库:
然后运行程序看下效果:(注意:offset是起始查询位置)
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方法完成了哪几项工作。
注意:此篇只分析分页相关步骤
当运行到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]) 返回一个装满查询集结果的列表(用切片实现选取所需数据片段)
queryset = self.filter_queryset(self.get_queryset())
总结:list方法做的第一件事就是让queryset指向根据前端输入过滤后的查询集的列表
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对象,序列化
return Response(serializer.data) # 返回序列化后的数据
所以在views.py中就几行代码简简单单的实现了分页,然后根据前端传入limit和offset,就能看到对应的结果了: