分页功能作为网页中的必要功能之一,django提供的内置Paginator实现了如下功能:
- 当前页面是否存在前后页。
- 每页的数据量。
- 访问的页数是否超范围。
一,django分页源码
1,分页由
Paginator
类实现,源码如下:
class Paginator: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 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)
其中的初始化参数如下:
-
object_list
,需分页的数据对象,为必需的。使用count()或len()方法的列表、元组、查询集或其他可分割对象。为了实现一致的分页,应该对查询集进行排序,例如使用order by()子句,或者使用模型上的默认排序。这里要注意\”对大型查询集进行分页的性能问题\”.
-
per_page
,必需的。页面上要包括的最大项数,不包括“遗留项”。
-
orphans
,可选的。当你不想最后一页条目很少的时候使用这个。如果最后一页通常有许多小于或等于遗留的项,那么这些项将被添加到前一页(即最后一页),而不是将这些项单独留在页面上。
-
allow_empty_first_pag
,可选的。第一页是否允许为空。如果False且对象列表为空,则会引发一个EmptyPage错误。
其中的方法如下:
- validate_number,验证当前分页数是否大于等于1。
-
get_page
,返回给定的基于1索引的Page对象,同时还处理超出范围和无效的页码。如果页面不是一个数字,它返回第一个页面。如果页号为负数或大于页数,则返回最后一页。
-
page
,据当前页数对object_list进行切片,返回给定的基于1索引的Page对象。如果给定的页码不存在,则引发InvalidPage。
-
_get_page
,将当前页数及页面对应的数据传给
Page
类,创建当前页数据对象。
-
count
,返回object_list长度。
-
num_pages
,获取分页后页面总数。
-
page_range
,将页面总数变为可循环对象。
-
_check_object_list_is_ordered
,对未排序的ORM object_list发出警告。
2,关于_get_page返回的数据:Page`类:
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# 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
其中的初始化参数如下:
-
object_list
,必选,此页上已被切片的对象列表。
-
number
,必选,用户传递的页数。
-
paginator
,必选,Paginator的实例化对象。
其中的方法如下:
- has_next,Returns True if there’s a next page.
- has_previous,Returns True if there’s a previous page.
- has_other_pages,Returns True if there’s a next or previous page.
- next_page_number,Returns the next page number. Raises InvalidPage if next page doesn’t exist.
- previous_page_number,Returns the previous page number. Raises InvalidPage if previous page doesn’t exist.
- start_index,输出当前页面的第一行数据在整个数据中的位置。
- end_index,输出当前页面的最后一行数据在整个数据中的位置。
3,关于InvalidPage exceptions:
-
InvalidPage
,如果所请求的页面无效(即不是整数)或不包含任何对象,则Paginator.page()方法会引发该异常。当然也可以使用下面的异常实现更细的粒度。
-
PageNotAnInteger
,当page()被赋予非整数值时引发。
-
EmptyPage
,当给page()一个有效值但该页上不存在对象时引发。
二,基本操作
通过以上部分源码分析,django以提供众多的属性与方法实现分页相关操作,所以分页操作比较固定。
>>> from django.core.paginator import Paginator>>> objects = [\'john\', \'paul\', \'george\', \'ringo\']>>> p = Paginator(objects, 1)>>> p.object_list[\'john\', \'paul\', \'george\', \'ringo\']>>> p.count4>>> p.num_pages4>>> type(p.page_range)<class \'range\'>>>> p.page_rangerange(1, 5)>>> page1 = p.page(1)>>> page1.object_list[\'john\']>>> page2 = p.page(2)>>> page2.object_list[\'paul\']>>> page2.has_next()True>>> page2.has_previous()True>>> page2.has_other_pages()True>>> page2.next_page_number()3>>> page1.has_previous()False>>> page2.start_index()2>>> p.page(0)Traceback (most recent call last):File \"<console>\", line 1, in <module>File \"F:\\PythonProjects\\web\\django-yingyongkaifashizhan\\MyDjango\\venv\\lib\\site-packages\\django\\core\\paginator.py\", line 70, in pagenumber = self.validate_number(number)File \"F:\\PythonProjects\\web\\django-yingyongkaifashizhan\\MyDjango\\venv\\lib\\site-packages\\django\\core\\paginator.py\", line 47, in validate_numberraise EmptyPage(_(\'That page number is less than 1\'))django.core.paginator.EmptyPage: That page number is less than 1>>> p.page(1)<Page 1 of 4>
三,案例实现
1,创建模型并添加数据
from django.db import modelsclass PersonInfo(models.Model):id = models.AutoField(primary_key=True)name = models.CharField(max_length=20)age = models.IntegerField()
2,添加路由
proj/urls.py:from django.urls import path, includeurlpatterns = [path(\'\', include((\'index.urls\', \'index\'), namespace=\'index\')),]app/urls.py:from django.urls import pathfrom .views import *urlpatterns = [path(\'<page>/\', index, name=\'index\'),]
3,功能视图
from django.shortcuts import renderfrom django.core.paginator import Paginatorfrom django.core.paginator import EmptyPagefrom django.core.paginator import PageNotAnIntegerfrom .models import PersonInfodef index(request, page):person = PersonInfo.objects.all().order_by(\'-age\')# 设置每一页的数据量为2p = Paginator(person, 2)try:pages = p.get_page(page)except PageNotAnInteger:# 如果参数page的数据类型不是整型,就返回第一页数据pages = p.get_page(1)except EmptyPage:# 若用户访问的页数大于实际页数,则返回最后一页的数据pages = p.get_page(p.num_pages)return render(request, \'index.html\', locals())
4,模板文件
<!DOCTYPE html><html lang=\"zh-hans\"><head>{% load static %}<title>分页功能</title><link rel=\"stylesheet\" href=\"{% static \"css/base.css\" %}\"/><link rel=\"stylesheet\" href=\"{% static \"css/lists.css\" %}\"></head><body class=\"app-route model-hkrouteinfo change-list\"><div id=\"container\"><div id=\"content\" class=\"flex\"><h1>分页功能</h1><div id=\"content-main\"><div class=\"module filtered\" id=\"changelist\"><form id=\"changelist-form\" method=\"post\"><div class=\"results\"><table id=\"result_list\"><thead><tr><th class=\"action-checkbox-column\"><div class=\"text\"><span><input type=\"checkbox\"/></span></div></th><th><div class=\"text\">姓名</div></th><th><div class=\"text\">年龄</div></th></tr></thead><tbody>{% for p in pages %}<tr><td class=\"action-checkbox\"><input type=\"checkbox\" class=\"action-select\"></td><td>{{ p.name }}</td><td>{{ p.age }}</td></tr>{% endfor %}</tbody></table></div><p class=\"paginator\">{# 上一页的路由地址 #}{% if pages.has_previous %}<a href=\"{% url \'index:index\' pages.previous_page_number %}\">上一页</a>{% endif %}{# 列出所有的路由地址 #}{% for n in pages.paginator.page_range %}{% if n == pages.number %}<span class=\"this-page\">{{ pages.number }}</span>{% else %}<a href=\"{% url \'index:index\' n %}\">{{ n }}</a>{% endif %}{% endfor %}{# 下一页的路由地址 #}{% if pages.has_next %}<a href=\"{% url \'index:index\' pages.next_page_number %}\">下一页</a>{% endif %}</p></form></div></div></div></div></body></html>