分页组件的设计
在web应用系统中,很多时候需要在页面列出数据库表中的记录。如果表中记录很多。一方面会导致检索时间长,另一方面在一个页面列出全部记录会使页面变得很长。因此要进行分页。
样例8:普通分页编写
在test_orm项目上建立一个test_page应用。
URL配置
首先在终端输入python manage.py startapp test_page建立应用,在settings注册。
在/test_orm/test_orm/urls.py中加入配置:
path(‘test_page/‘,include(‘test_page.urls‘)),
在/test_orm/test_page/下新建urls,输入:
from django.urls import path,include
from . import views
urlpatterns = [
# 普通分页页面
path(‘person_page/‘,views.person_page),
# 用分页组件的页面
path(‘person_pagenew/‘,views.person_pagenew)
]
数据模型
在models中编写,生成一张分页要用的数据库表。
from django.db import models
# 员工数据模型
class person(models.Model):
# 员工姓名
name = models.CharField(max_length=32,verbose_name=‘姓名‘)
# 员工的邮箱
email = models.EmailField(verbose_name=‘邮箱‘)
# 员工的部门
dep = models.ForeignKey(to=‘department‘,to_field=‘id‘,on_delete=models.CASCADE)
# 薪水
salary = models.DecimalField(max_digits=8,decimal_places=2)
def __str__(self):
return self.name
class department(models.Model):
# 部门名称
dep_name = models.CharField(max_length=32,verbose_name=‘部门名称‘,unique=True,blank=False)
# 部门备注说明
dep_script = models.CharField(max_length=60,verbose_name=‘备注‘,null=True)
def __str__(self):
return self.dep_name
通过命令生成数据库表然后输入一定数量的记录供分页测试使用。
视图函数
from django.shortcuts import render,HttpResponse
from . import models
def person_page(request):
# 从URL中取出参数page,这个参数是"?page=1"形式
cur_page_num = request.GET.get(‘page‘)
# 取得person中的记录总数
total_count = models.person.objects.all().count()
# 设定每一页显示多少条记录
one_page_lines = 10
# 页面上总共展示多少页码标签
page_maxtag = 9
"""
根据总记录数,计算出总的页数
通过divmod函数取得商和余数,有余数时,总页数是商加1
同时判断当前页的页码是否大于总页数,如果大于总页数,设置当前页码等于最后一页的页码
"""
total_page,remainder=divmod(total_count,one_page_lines)
if remainder:
total_page += 1
try:
# 参数page传递过来的是字符类型数值,因此需要转化为整数类型
cur_page_num = int(cur_page_num)
# 如果输入的页码超过了超过了最大的页码,设置当前页码是最后一页的页码
if cur_page_num > total_page:
cur_page_num = total_page
except Exception as e:
# 当输入的页码不是正整数或者不是数字时,设置当前页码是第一页的页码
cur_page_num = 1
# 定义两个变量,指定表中当前页的记录开始数,以及当前页的记录结束数
rows_start = (cur_page_num-1)*one_page_lines
rows_end = cur_page_num*one_page_lines
# 如果页数小于每页设置的页码标签数,设置每页页码标签数为总页数
if total_page<page_maxtag:
page_maxtag=total_page
# 把当前页码标签放在中间,前面放一半页码标签,后面放一半页码标签
# 因此先把设置的页码标签数除以2
half_page_maxtag = page_maxtag//2
# 页面上页码标签的开始数
page_start = cur_page_num - half_page_maxtag
# 页面上页码标签的结束数
page_end = cur_page_num + half_page_maxtag
"""
如果计算出的页码标签开始数小于1,页面中页码标签设置从1开始
设置页面中页码标签结束数等于前面设置的页码标签总数page_maxtag
"""
if page_start <=1:
page_start =1
page_end=page_maxtag
"""
如果计算出的页码标签数比总页码数大,设置最后的页码标签数为总页数
设置页面中页码开始数等于总页数减去page_maxtag加1
"""
if page_end >= total_page:
page_end = total_page
page_start = total_page-page_maxtag + 1
if page_start <= 1:
page_start = 1
# 对person表中的记录进行切片,取出属于本页的记录
per_list = models.person.objects.all()[rows_start:rows_end]
# 初始化一个列表变量,用来保存拼接分页的HTML代码
html_page = []
# 首页代码
html_page.append(‘<li><a href="/test_page/person_page/?page=1">首页</a></li>‘)
# 上一页页码标签的HTML代码,如果当前是第一页,设置上一页页码标签为非可用状态
if cur_page_num <=1:
html_page.append(‘<li class="disabled"><a href="#"><span aria-hidden="true">«</span></a></li>‘.format(
cur_page_num-1))
else:
# 上一页页码标签的HTML代码
html_page.append(‘<li><a href="/test_page/person_page/?page={}"><span aria-hidden="true">«</span></a></li>‘.format(
cur_page_num - 1))
# 依次取页码标签
for i in range(page_start,page_end+1):
# 如果等于当前页就加一个active类
if i == cur_page_num:
html_temp = ‘<li class="active"><a href="/test_page/person_page/?page={0}">{0}</a></li>‘.format(i)
else:
html_temp = ‘<li><a href="/test_page/person_page/?page={0}">{0}</a></li>‘.format(i)
html_page.append(html_temp)
# 下一页页码标签的HTML页码
# 判断如果是最后一页,下一页设为disabled
if cur_page_num >= total_page:
html_page.append(‘<li class="disabled"><a href="#"><span aria-hidden="true">»</span></a></li>‘)
else:
html_page.append(
‘<li><a href="/test_page/person_page/?page={}"><span aria-hidden="true">»</span></a></li>‘.format(
cur_page_num + 1))
# 最后一页页码标签的html代码
html_page.append(
‘<li><a href="/test_page/person_page/?page={}">尾页</a></li>‘.format(total_page))
# 把HTML连接起来
page_nav = ‘‘.join(html_page)
return render(request,‘test_page/list_person.html‘,{‘person_list‘:per_list,‘page_nav‘:page_nav})
说明:
以上代码主要生成一个分页的HTML片段,存在变量中发送给模板文件,主要有五步:
- 从URL取得当前页码,对应代码cur_page_num = request.GET.get(‘page‘)。
- 取得总记录数,指定每页要显示的记录数,每页要显示的页码标签数。
- 计算出总页数,计算出当前页中记录从第几条开始到第几条结束并取出这些记录。
- 计算出页码标签的起始值和结束值。
- 生成分页的HTML代码,存在变量中,通过render函数发给模板文件。
list_person.html:
{% load static %}
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<title>模板样例</title>
<!-- Bootstrap core CSS -->
<link href="{% static ‘bootstrap/css/bootstrap.min.css‘%}" rel="stylesheet">
</head>
<body>
<div class="col-sm-4 col-sm-offset-4 col-md-6 col-md-offset-3 main">
<br>
<br>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">员工列表</h3> <!--这里加标题 //-->
</div>
<div class="panel-body"> <!--将表格放在这个<div class="panel-body">的标签中 //-->
<table class="table table-bordered table-condensed table-striped table-hover"> <!--给表格增加bootstrap样式 //-->
<thead>
<tr>
<th>姓名</th>
<th>邮件</th>
<th>薪水</th>
<th>部门</th>
</tr>
</thead>
<tbody>
{% for per in person_list %}
<tr>
<td>{{ per.name }}</td>
<td>{{ per.email }}</td>
<td>{{ per.salary }}</td>
<td>{{ per.dep.dep_name }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7">无相关记录!</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav aria-label="Page navigation">
<ul class="pagination">
{{ page_nav|safe }}
</ul>
</nav>
</div>
</div>
</div>
<script src="{% static ‘jquery-3.4.1.min.js‘ %}"></script>
<script src="{% static ‘bootstrap/js/bootstrap.min.js‘%}"></script>
</body>
</html>
页面把视图函数传过来的有关分页的变量以模板标签{{ page_nav|safe }}的形式放在页面上,这样在相应位置显示页码标签。
分页组件
如果每个视图函数遇到分页的情况都要写一遍分页代码,重复太多效率不高,因此有必要写一个分页组件,这样网页上的记录需要使用分页功能时,只需调用这个组件即可。
分页组件
在/test_orm/文件夹下新建一个文件夹utils,在其下新建一个paginater.py:
class Paginater():
def __init__(self, url_address,cur_page_num, total_rows, one_page_lines=10, page_maxtag=9):
"""
url_address: 页码标签href的地址,也就是分页功能的网页URL
cur_page_num: 当前页码数
total_rows: 数据模型的记录总数
one_page_lines: 每页显示多少条记录
page_maxtag: 页面上显示页码标签的个数
"""
self.url_address = url_address
self.page_maxtag=page_maxtag
"""
根据总记录数,计算出总的页数。
通过divmod()函数取得商和余数,有余数时,总页数是商加上1
同时判断当前页的数值是否大于总页数,如果大于总页数,设置当前页数等于最后一页的页数
"""
total_page, remainder = divmod(total_rows, one_page_lines)
if remainder:
total_page += 1
self.total_page = total_page
try:
# 参数page传递进来是字符型数值,因此需要转化为整数类型
cur_page_num = int(cur_page_num)
# 如果输入的页码数超过了最大的页码数,设置当前页数是最后一页的页数
if cur_page_num > total_page:
cur_page_num = total_page
#避免出现当前页数为0
if cur_page_num == 0:
cur_page_num=1
except Exception as e:
# 当输入的页码不是正整数时, 设置当前页数是第一页的页数
cur_page_num = 1
self.cur_page_num = cur_page_num
# 定义两个变量,指定表中取出的记录开始数,以及当前页记录结束的位置
self.rows_start = (cur_page_num - 1) * one_page_lines
self.rows_end = cur_page_num * one_page_lines
# 如果页数小于每页设置的页码标签数,设置每页页码标签数为总页数
if total_page < page_maxtag:
page_maxtag = total_page
# 把当前页码标签放在中间,前面放一半页码标签,后面放一半页码标签
# 因此现把页面上放置的页码标签数除以2
half_page_maxtag = page_maxtag // 2
# 页面上页码开始
page_start = cur_page_num - half_page_maxtag
# 页面上页码结束
page_end = cur_page_num + half_page_maxtag
"""
如果当前页减一半,小于1,页面中页码标签设置从1开始,
页面中页码标签等页面中页码标签数
"""
if page_start <= 1:
page_start = 1
page_end = page_maxtag
"""
如果当前页加 一半 比总页码数大,设置最后的页码标签值为总页数
页面中页码标签起始等于总页数减掉页码标签数加1
"""
if page_end >= total_page:
page_end = total_page
page_start = total_page - page_maxtag + 1
if page_start <= 1:
page_start = 1
self.page_start=page_start
self.page_end=page_end
def html_page(self):
# 初始一个列表变量,用来保存拼接分页的HTML代码
html_page = []
# 首页代码
html_page.append(‘<li><a href="{}?page=1">首页</a></li>‘.format(self.url_address))
# 上一页页码标签的HTML代码,如果当前是第一页,设置上一页标签为非可用状态
if self.cur_page_num <= 1:
html_page.append(‘<li class="disabled"><a href="#"><span aria-hidden="true">«</span></a></li>‘.format(
self.cur_page_num - 1))
else:
# 上一页页码标签的HTML代码
html_page.append(‘<li><a href="{}?page={}"><span aria-hidden="true">«</span></a></li>‘.format(self.url_address, self.cur_page_num-1))
# 依次取页码标签,注意切片函数用法
for i in range(self.page_start, self.page_end + 1):
# 如果是当前页就加一个active样式类
if i == self.cur_page_num:
tmp = ‘<li class="active"><a href="{0}?page={1}">{1}</a></li>‘.format(self.url_address, i)
else:
tmp = ‘<li><a href="{0}?page={1}">{1}</a></li>‘.format(self.url_address, i)
html_page.append(tmp)
# 下一页的页码标签的HTML代码
# 判断,如果是最后一页,就没有下一页
if self.cur_page_num >= self.total_page:
html_page.append(‘<li class="disabled"><a href="#"><span aria-hidden="true">»</span></a></li>‘)
else:
html_page.append(‘<li><a href="{}?page={}"><span aria-hidden="true">»</span></a></li>‘.format(self.url_address, self.cur_page_num+1))
# 最后一页的页码标签的HTML代码
html_page.append(‘<li><a href="{}?page={}">尾页</a></li>‘.format(self.url_address, self.total_page))
# 把HTML联结起来
page_nav = "".join(html_page)
return page_nav
@property
def data_start(self):
return self.rows_start
@property
def data_end(self):
return self.rows_end
说明:以上代码通过Paginater类对分页逻辑代码进行了封装,主要包括初始化、生成HTML代码片段、生成两个属性。
类中__init__函数接收URL、当前代码、记录总数、每页显示的记录数、每页页码标签数等参数,计算出总页数、当前页中记录从第几条开始并且到第几条结束,当前页面上的页码标签的开始数和结束数。
类中html_page函数通过__init__接收参数以及计算出的值生成分页相关的HTML代码。
类中data_start、data_end两个函数分别返回当前页面从哪条记录开始、哪条结束,通过@property装饰器变成类属性。
调用分页组件
视图函数person_pagenew调用了分页组件,实现了person表中记录的分页显示功能:
# 引入分页组件类
from utils.paginater import Paginater
def person_pagenew(request):
# 从URL中取参数page,这个参数与pageinanter.py生成的代码片段有关
cur_page_num = request.GET.get(‘page‘)
if not cur_page_num:
cur_page_num = ‘1‘
# 取得person中的记录总数
total_count = models.person.objects.all().count()
# 设定每一页显示多少条记录
one_page_lines = 6
# 页面上共展示多少页码标签
page_maxtag = 9
# 生成Paginater类的实例化对象
page_obj = Paginater(url_address=‘/test_page/person_pagenew/‘,
cur_page_num=cur_page_num,total_rows=total_count,one_page_lines=one_page_lines,
page_maxtag=page_maxtag)
# 对person表中的记录进行切片,取出本页的记录。
page_list=models.person.objects.all()[page_obj.data_start:page_obj.page_end]
return render(request,‘test_page/list_person.html‘,{‘person_list‘:page_list,‘page_nav‘:page_obj.html_page()})