使用Django框架内置模块django.core.paginator
中封装的Paginator类Page类进行分页功能实现。其中Paginator是分页器,从分页器中可以得到Page,即分页对象。源码如下:
import collections.abc
import inspect
import warnings
from math import ceilfrom django.utils.functional import cached_property
from django.utils.inspect import method_has_no_args
from django.utils.translation import gettext_lazy as _class UnorderedObjectListWarning(RuntimeWarning):passclass InvalidPage(Exception):passclass PageNotAnInteger(InvalidPage):passclass EmptyPage(InvalidPage):passclass Paginator:# Translators: String used to replace omitted page numbers in elided page# range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10].ELLIPSIS = _("…")def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):self.object_list = object_listself._check_object_list_is_ordered()self.per_page = int(per_page)self.orphans = int(orphans)self.allow_empty_first_page = allow_empty_first_pagedef __iter__(self):for page_number in self.page_range:yield self.page(page_number)def validate_number(self, number):"""Validate the given 1-based page number."""try:if isinstance(number, float) and not number.is_integer():raise ValueErrornumber = int(number)except (TypeError, ValueError):raise PageNotAnInteger(_("That page number is not an integer"))if number < 1:raise EmptyPage(_("That page number is less than 1"))if number > self.num_pages:if number == 1 and self.allow_empty_first_page:passelse:raise EmptyPage(_("That page contains no results"))return numberdef get_page(self, number):"""Return a valid page, even if the page argument isn't a number or isn'tin range."""try:number = self.validate_number(number)except PageNotAnInteger:number = 1except EmptyPage:number = self.num_pagesreturn self.page(number)def page(self, number):"""Return a Page object for the given 1-based page number."""number = self.validate_number(number)bottom = (number - 1) * self.per_pagetop = bottom + self.per_pageif top + self.orphans >= self.count:top = self.countreturn self._get_page(self.object_list[bottom:top], number, self)def _get_page(self, *args, **kwargs):"""Return an instance of a single page.This hook can be used by subclasses to use an alternative to thestandard :cls:`Page` object."""return Page(*args, **kwargs)@cached_propertydef count(self):"""Return the total number of objects, across all pages."""c = getattr(self.object_list, "count", None)if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c):return c()return len(self.object_list)@cached_propertydef num_pages(self):"""Return the total number of pages."""if self.count == 0 and not self.allow_empty_first_page:return 0hits = max(1, self.count - self.orphans)return ceil(hits / self.per_page)@propertydef page_range(self):"""Return a 1-based range of pages for iterating through withina template for loop."""return range(1, self.num_pages + 1)def _check_object_list_is_ordered(self):"""Warn if self.object_list is unordered (typically a QuerySet)."""ordered = getattr(self.object_list, "ordered", None)if ordered is not None and not ordered:obj_list_repr = ("{} {}".format(self.object_list.model, self.object_list.__class__.__name__)if hasattr(self.object_list, "model")else "{!r}".format(self.object_list))warnings.warn("Pagination may yield inconsistent results with an unordered ""object_list: {}.".format(obj_list_repr),UnorderedObjectListWarning,stacklevel=3,)def get_elided_page_range(self, number=1, *, on_each_side=3, on_ends=2):"""Return a 1-based range of pages with some values elided.If the page range is larger than a given size, the whole range is notprovided and a compact form is returned instead, e.g. for a paginatorwith 50 pages, if page 43 were the current page, the output, with thedefault arguments, would be:1, 2, …, 40, 41, 42, 43, 44, 45, 46, …, 49, 50."""number = self.validate_number(number)if self.num_pages <= (on_each_side + on_ends) * 2:yield from self.page_rangereturnif number > (1 + on_each_side + on_ends) + 1:yield from range(1, on_ends + 1)yield self.ELLIPSISyield from range(number - on_each_side, number + 1)else:yield from range(1, number + 1)if number < (self.num_pages - on_each_side - on_ends) - 1:yield from range(number + 1, number + on_each_side + 1)yield self.ELLIPSISyield from range(self.num_pages - on_ends + 1, self.num_pages + 1)else:yield from range(number + 1, self.num_pages + 1)class Page(collections.abc.Sequence):def __init__(self, object_list, number, paginator):self.object_list = object_listself.number = numberself.paginator = paginatordef __repr__(self):return "<Page %s of %s>" % (self.number, self.paginator.num_pages)def __len__(self):return len(self.object_list)def __getitem__(self, index):if not isinstance(index, (int, slice)):raise TypeError("Page indices must be integers or slices, not %s."% type(index).__name__)# The object_list is converted to a list so that if it was a QuerySet# it won't be a database hit per __getitem__.if not isinstance(self.object_list, list):self.object_list = list(self.object_list)return self.object_list[index]def has_next(self):return self.number < self.paginator.num_pagesdef has_previous(self):return self.number > 1def has_other_pages(self):return self.has_previous() or self.has_next()def next_page_number(self):return self.paginator.validate_number(self.number + 1)def previous_page_number(self):return self.paginator.validate_number(self.number - 1)def start_index(self):"""Return the 1-based index of the first object on this page,relative to total objects in the paginator."""# Special case, return zero if no items.if self.paginator.count == 0:return 0return (self.paginator.per_page * (self.number - 1)) + 1def end_index(self):"""Return the 1-based index of the last object on this page,relative to total objects found (hits)."""# Special case for the last page because there can be orphans.if self.number == self.paginator.num_pages:return self.paginator.countreturn self.number * self.paginator.per_page
从源码也可以看出,Paginator对象的主要属性和方法:
- object_list:当前分页对象所有的数据列表。
- per_page:分页对象的每页的展示的对象的数量。
- orphans:如果分页的页数的数据总记录数不匹配,会导致最后一页显示少量数据。该属性控制是否将最后一页的数据添加到上一页展示。比如,总记录数为23,每页展示10条数据,正常最后一页会只展示3条数据。但是如果设置orphans=5,因为最后一页数量3小于5,则会把最后一页数据展示到上一页。
- allow_empty_first_page:如果分页查询数据为空,如果该属性为False,则第一页是否以空白页展示,并抛出EmptyPage异常。默认为True,不抛异常。
- get_page(number):获取第number页的分页对象,即返回Page对象。
- count():获取数据的总记录数。
- num_pages():获取分页的总页数。
- page_range():获取分页对象的range(1, n)生成器。
- EmptyPage:如果在分页操作中,出现页码对应的查询数据不存在,则抛出EmptyPage异常。
Page对象的主要属性和方法:
- has_next():当前页后面是否有下一页
- has_previous():当前页前面是否有前一页
- has_other_pages:当前页是否还有其他页,has_next和has_previous满足其一返回True
- next_page_number():返回下一页的页码
- previous_page_number():返回上一页的页码
- start_index():获取当前页面中起始数据的索引编号。比如每页10条数据,第2页就返回10 * (2 - 1) + 1 = 11。
- end_index():获取当前页面中结束数据的索引编号。
示例:
def list(request):# 通过ORM查询对象的所有数据user_list = User.objects().all()# 构建Paginator对象,每页20条数据paginator = Paginator(user_list, 20)# 获取当前页码page = request.GET.get('page')if page is None:page = 1# 从分页对象中获取页码对象users = paginator.get_page(page)return render(request, 'list.html', {'users': users})