您的位置:首页 > 编程语言 > Go语言

django:分页功能源码与应用实现

2020-08-27 16:24 1176 查看

分页功能作为网页中的必要功能之一,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_list
self._check_object_list_is_ordered()
self.per_page = int(per_page)
self.orphans = int(orphans)
self.allow_empty_first_page = allow_empty_first_page

def validate_number(self, number):
"""Validate the given 1-based page number."""
try:
if isinstance(number, float) and not number.is_integer():
raise ValueError
number = 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:
pass
else:
raise EmptyPage(_('That page contains no results'))
return number

def get_page(self, number):
"""
Return a valid page, even if the page argument isn't a number or isn't
in range.
"""
try:
number = self.validate_number(number)
except PageNotAnInteger:
number = 1
except EmptyPage:
number = self.num_pages
return 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_page
top = bottom + self.per_page
if top + self.orphans >= self.count:
top = self.count
return 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 the
standard :cls:`Page` object.
"""
return Page(*args, **kwargs)

@cached_property
def 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_property
def num_pages(self):
"""Return the total number of pages."""
if self.count == 0 and not self.allow_empty_first_page:
return 0
hits = max(1, self.count - self.orphans)
return ceil(hits / self.per_page)

@property
def page_range(self):
"""
Return a 1-based range of pages for iterating through within
a 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_list
self.number = number
self.paginator = paginator

def __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_pages

def has_previous(self):
return self.number > 1

def 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 0
return (self.paginator.per_page * (self.number - 1)) + 1

def 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.count
return 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.count
4
>>> p.num_pages
4
>>> type(p.page_range)
<class 'range'>
>>> p.page_range
range(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\pag
inator.py", line 70, in page
number = self.validate_number(number)
File "F:\PythonProjects\web\django-yingyongkaifashizhan\MyDjango\venv\lib\site-packages\django\core\pag
inator.py", line 47, in validate_number
raise 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 models

class 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, include
urlpatterns = [
path('', include(('index.urls', 'index'), namespace='index')),
]

app/urls.py:
from django.urls import path
from .views import *
urlpatterns = [
path('<page>/', index, name='index'),
]

3,功能视图

from django.shortcuts import render
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from .models import PersonInfo

def index(request, page):
person = PersonInfo.objects.all().order_by('-age')
# 设置每一页的数据量为2
p = 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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: