drf - 基础分页组件 PageNumberPagination
数据准备
model.py文件
定义两个表Car表和Brand表,其中Car中的brand字段外键关联Brand表
from django.db import models
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class Car(BaseModel):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
brand = models.ForeignKey('Brand', db_constraint=False, on_delete=models.DO_NOTHING, related_name='cars')
@property
def brand_name(self):
return self.brand.name
class Meta:
db_table = 'old_boy_car'
verbose_name = '汽车'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Brand(BaseModel):
name = models.CharField(max_length=32)
class Meta:
db_table = 'old_boy_brand'
verbose_name = '品牌'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
新建的serializer.py文件
brand字段只参与反序列化,brand_name只参与序列化
from rest_framework.serializers import ModelSerializer
from . import models
class CarModelSerializer(ModelSerializer):
class Meta:
model = models.Car
fields = ('name','price','brand','brand_name')
extra_kwargs = {
"brand":{
'write_only':True
},
'brand_name':{
'read_only':True
},
}
分页组件部分源码分析一
通常是在获取多条数据list方法中,进行分页显示。所以viewsets.py文件中list方法是分页入口
class ListModelMixin:
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)
1. 先调用paginate_queryset方法,此方法继承至generics中的GenericAPIView类
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None: # self.paginator分页器,如果为None表示不分页
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
2. 调用paginator方法
@property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
# 如果配置中的pagination_class为空就不分页,所以要分页就需要配置pagination_class类,else中实例化分页类
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
3. 自定义pagination_class
4. 调用pagination_class类中的paginate_queryset方法
自定义分页组件
新建的paginations.py文件,CarPageNumberPagination继承了drf分页组件中的PageNumberPagination类
from rest_framework.pagination import PageNumberPagination
class CarPageNumberPagination(PageNumberPagination):
page_size = 3 # 设置每页显示的数量为3
# 优先使用page_size_query_param的设置来显示条数
page_size_query_param = 'page_size' # 自己输入每页显示的条数
max_page_size = 5 # 一页显示的最大条数,与page_size_query_param搭配使用
# url链接:car/?page=2 显示第2页,不写默认显示第一页
# url链接:car/?page_size=4 每页4条数据,若大于max_page_size,则显示max_page_size设置的条数
# url链接:car/?page=2&page_size=4 每页显示4条,显示第2页
# url链接:car/?page=last 显示最后一页
注意:是根据数据显示,若数据总条数只有5条,每页显示3条,这时第2页就值显示2条数据
view.py文件:需要自定义pagination_class
from rest_framework.viewsets import ModelViewSet
from . import models, serializer
from .paginations import CarPageNumberPagination
class CarModelViewSet(ModelViewSet):
queryset = models.Car.objects.filter(is_delete=False)
serializer_class = serializer.CarModelSerializer
# 自定义pagination_class
parser_classes = CarPageNumberPagination
分页组件部分源码分析二
1. 调用CarPageNumberPagination中的paginate_queryset方法
def paginate_queryset(self, queryset, request, view=None):
"""
Paginate a queryset if required, either returning a
page object, or `None` if pagination is not configured for this view.
"""
page_size = self.get_page_size(request) # 获取page_size
if not page_size:
return None
paginator = self.django_paginator_class(queryset, page_size) # 分页器,django.core.paginator中的Paginator分页器类
page_number = request.query_params.get(self.page_query_param, 1) # page_query_param为'page',在url接口中page=2表示第2页,page就是链接的页数表示。默认为page=1
if page_number in self.last_page_strings:
# last_page_strings为('last',),如果url输入page=last,表示最后一页
page_number = paginator.num_pages
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
msg = self.invalid_page_message.format(
page_number=page_number, message=str(exc)
)
raise NotFound(msg)
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
self.display_page_controls = True
self.request = request
return list(self.page)
2. 调用get_page_size方法,获取page_size:每页显示的数量
def get_page_size(self, request):
if self.page_size_query_param: # drf中默认设置为None,可以在CarPageNumberPagination类中自定义,如果没有设置就直接返回page_size的值
try:
return _positive_int(
request.query_params[self.page_size_query_param],
strict=True,
cutoff=self.max_page_size # 一页显示的最大数量
)
except (KeyError, ValueError):
pass
return self.page_size # drf中默认设置为None,可以在类中配置
3. 设置了page_size_query_param时,获取的page_size每页显示的数量。调用_positive_int方法
def _positive_int(integer_string, strict=False, cutoff=None):
"""
Cast a string to a strictly positive integer.
"""
ret = int(integer_string) # url传入的值,如page_size_query_param='page_size',在url中:car/?page_size=4.则ret=4
if ret < 0 or (ret == 0 and strict):
raise ValueError()
if cutoff:
return min(ret, cutoff)
return ret # 此时ret为4就是返回的page_size