这篇博客非常好的详述了django分页的自定义修改方法
发表于 2018-01-23 | 分类于 Django |
可以通过 Django 提供的 Paginator 类来实现分页功能。也可以自定义实现分页功能。
自定义实现分页
一、原理解析
1、初始数据:
total_data: 数据集合,这是一个list,存放所有的数据。
page_size: 每页显示多少条数据。
num_page: 分页处显示页码数量。
2、分页需要使用的数据
current_page: 当前页,当前的页码。
通过 request.GET 获得:
current_page = int(request.GET.get('p'))
total_num: 数据条目总数
total_num = len(total_data)
max_page: 最大页码数
计算方法: 数据总条数除以每页显示条数,若余数等于0,最大页码数为商,否则最大页码数为商+1
getmax_page = lambda x,y: divmod(x,y)[0] if divmod(x,y)[1] == 0 else divmod(x,y)[0]+1
max_page = getmax_page(total_num,page_size)
peer_data: 每页显示数据
计算方法: 当我们定义page_size=10的时候,每页显示10条数据
第一页:0:10
第二页:10:20
以此类推…….
第n页:(n-1)x10:(nx10)
就可以推出每页显示数据的计算方法:
start = (current_page-1)*page_size
end = current_page*page_size
peer_data = total_data[start:end]
prev_page: 上一页页码
当前页小于等于1的时候,上一页为1
if current_page <= 1:
prev_page =1
else:
prev_page = current_page - 1
next_page: 下一页页码
当前页大于等于最大页时,下一页为最大页
if current_page >= max_page:
next_page = max_page
else:
next_page = current_page + 1
num_page_range: 分页区域显示页码范围
此处如果全部显示页码的话不合适,页码数太多页码就超级难看了,因此可以显示一个固定数量的页码值。
前面定义的 num_page (分页区域显示的页码数量)就在这里用到了。
如果num_page=7的话就这样显示:
1 2 3 4 5 6 7
前半区 后半区
实现逻辑如下:
1、总页数 max_page 小于最多显示页数 num_page ,显示1到总页数 max_page,此时显示的页码数量小于最多显示页数 num_page。
2、当前页 current_page 小于等于最多显示页数的一半 num_page/2,显示1到最多显示页数 num_page,当前页在前半区。
3、当前页 current_page 加上最多显示页数的一半 num_page/2 大于总页数 max_page,显示(总页数 max_page 减去最大显示页数 num_page)到总页数 max_page,当前页在后半区。
4、前后各显示最多显示页数一半,当前页在中间位置。
part = num_page/2
if max_page < num_page:
num_page_range = [i for i in range(1,max_page + 1)]
elif current_page <= part:
num_page_range = [i for i in range(1,num_page + 1)]
elif current_page + part > max_page:
num_page_range = [i for i in range(max_page - max_page_num,max_page + 1)]
else:
num_page_range = [i for i in range(current_page-part,current_page + part + 1)]
3、视图函数传给模板的变量汇总
current_page: 当前页码
peer_data: 当前页数据
prev_page: 上一页页码
next_page: 下一页页码
max_page: 最大页码数
num_page_range: 分页区域显示页码范围
4、前端模板逻辑分析
前端页面调用 Bootstrap 分页组件来显示:
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
<ul class="pagination">
{% if current_page == 1 %}
<li class="disabled">
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li>
<a href="?p={{ prev_page }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
下一页按钮
当前为最后一页时,禁用右边的下一页按钮
{% if current_page == max_page %}
<li class="disabled">
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li>
<a href="?p={{ next_page }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
显示页码范围
根据视图传过来的分页区域显示页码范围 num_page_range 来循环显示,并对当前页进行样式处理:
{% for p in num_page_range %}
{% if p == current_page %}
<li class="active"><a href="#">{{ p }}</a></li>
{% else %}
<li><a href="?p={{ p }}">{{ p }}</a></li>
{% endif %}
{% endfor %}
二、将分页实现为Web框架公共组件
分页的使用其实非常广泛,前面分析了分页实现的原理,这里我们将代码实现为公共组件,以后使用时就可以直接调用。
1、在app下建立py文件并自定义构建类
pager.py
#coding:utf-8
class Pagination(object):
def __init__(self, totalCount, currentPage, perPageItemNum=10, maxPageNum=7):
# 数据条目总数
self.total_count = totalCount
# 当前页页码值
self.current_page = currentPage
# 每页显示数据条目数
self.per_page_item_num = perPageItemNum
# 页码区域最多显示页码数
self.max_page_num = maxPageNum
def start(self):
return (self.current_page - 1)*self.per_page_item_num
def end(self):
return self.current_page*self.per_page_item_num
@property
def num_pages(self):
'''
装饰器@property将函数的方法的调用方式转换为属性调用方式
求出总页数
'''
a,b = divmod(self.total_count, self.per_page_item_num)
if a == 0:
return a
else:
return a+1
def pager_num_page(self):
'''
分页区域显示页码范围
'''
part = self.max_page_num/2
if self.num_pages < self.max_page_num:
return range(1,self.num_pages+1)
elif self.current_page <= part:
return range(1,self.max_page_num+1)
elif self.current_page + part > self.num_pages:
return range(self.num_pages-self.max_page_num,self.num_pages+1)
else:
return range(self.current_page-part,self.current_page+part+1)
def page_str(self):
'''
html返回到templates,templates中需引入bootStrap的css样式
'''
page_list = []
first = """
<li><a href='?p=1'>首页</a></li>
"""
page_list.append(first)
if self.current_page == 1:
prev_page = """
<li class="disabled">
<span>
<span aria-hidden="true">«</span>
</span>
</li>
"""
else:
prev_page = """
<li>
<a href="?p=%s" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
""" %(self.current_page - 1)
page_list.append(prev_page)
for i in self.pager_num_page():
if i == self.current_page:
temp = """
<li class="active">
<li class="active"><a href="#">%s</a></li>
</li>
""" %i
else:
temp = """
<li>
<a href="?p=%s">%s</a>
</li>
""" %(i,i)
page_list.append(temp)
if self.current_page == self.num_pages:
next_page = """
<li class="disabled">
<span>
<span aria-hidden="true">»</span>
</span>
</li>
"""
else:
next_page = """
<li>
<a href="?p=%s" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
""" %(self.current_page + 1)
page_list.append(next_page)
last = """
<li><a href='?p=%s'>尾页</a></li>
""" %self.num_pages
page_list.append(last)
return ''.join(page_list)
2、调用 Pagination 实现分页
视图方法逻辑: views.py
#coding:utf-8
from django.shortcuts import render
from app01.pager import Pagination
# 定义后台数据
mylist=[]
for i in range(1,1000):
temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
mylist.append(temp)
def mypager(request):
try:
current_page = int(request.GET.get('p'))
except:
current_page = 1
page_obj = Pagination(len(mylist),current_page,15,5)
data = mylist[page_obj.start():page_obj.end()]
return render(request,'mypager.html',locals())
模板使用: mypager.html
<!DOCTYPE html>
<html lang="en">
{% load staticfiles %}
<head>
<meta charset="UTF-8">
<title>mypager</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
</head>
<body>
<div class="container">
<div class="panel">
<table class="table table-striped">
<tr>
<th>ID</th>
<th>name</th>
<th>age</th>
<th>addr</th>
<th>score</th>
</tr>
{% for item in data %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.addr }}</td>
<td>{{ item.score }}</td>
</tr>
{% endfor %}
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination">
{{ page_obj.page_str | safe }}
</ul>
</nav>
</div>
</body>
</html>
使用Django内置分页
Django提供了两个新的类(Paginator和Page)来帮助你管理分页数据,这两个类存放在 django/core/paginator.py .它可以接收列表、元组或其它可迭代的对象。
1、语法解析
Paginator类
基于分页对象
count 数据总个数
num_pages 总共可分页数
page_range 总页数索引范围
Page类
基于分页对象中的某一页
object_list 分页对象的元素列表
number 分页对象的当前页值
has_next 是否有下一页
next_page_number 下一页代码
has_previous 是否有上一页
previous_page_number 上一页代码
has_other_pages 是否有其他页
start_index 分页对象元素的开始索引
end_index 分页对象元素的结束索引
2、基本语法实例
import os
from django.core.paginator import Paginator
objects = ['john','paul','george','ringo','lucy','meiry','checy','wind','flow','rain']<br>
p = Paginator(objects,3) # 3条数据为一页,实例化分页对象
print p.count # 10 对象总共10个元素
print p.num_pages # 4 对象可分4页
print p.page_range # xrange(1, 5) 对象页的可迭代范围
page1 = p.page(1) # 取对象的第一分页对象
print page1.object_list # 第一分页对象的元素列表['john', 'paul', 'george']
print page1.number # 第一分页对象的当前页值 1
page2 = p.page(2) # 取对象的第二分页对象
print page2.object_list # 第二分页对象的元素列表 ['ringo', 'lucy', 'meiry']
print page2.number # 第二分页对象的当前页码值 2
print page1.has_previous() # 第一分页对象是否有前一页 False
print page1.has_other_pages() # 第一分页对象是否有其它页 True
print page2.has_previous() # 第二分页对象是否有前一页 True
print page2.has_next() # 第二分页对象是否有下一页 True
print page2.next_page_number() # 第二分页对象下一页码的值 3
print page2.previous_page_number() # 第二分页对象的上一页码值 1
print page2.start_index() # 第二分页对象的元素开始索引 4
print page2.end_index() # 第2分页对象的元素结束索引 6
3、内置分页使用实例
视图方法逻辑: views.py
#coding:utf-8
from django.shortcuts import render
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
# Create your views here.
mylist=[]
for i in range(1,1000):
temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
mylist.append(temp)
def myin(request):
try:
current_page = int(request.GET.get('p'))
except:
current_page = 1
# 实例化分页对象,每页10条数据
paginator = Paginator(mylist,10)
try:
# 取对象的当前页分页对象
posts = paginator.page(current_page)
# current_page非数字时取第一页
except PageNotAnInteger:
posts = paginator.page(1)
# current_page空时取最后一页
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request,'inpage.html',{'posts':posts})
模板使用: inpage.html
<!DOCTYPE html>
{% load staticfiles %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>内置</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
</head>
<body>
<div class="container">
<div class="panel">
<table class="table table-striped">
<tr>
<th>ID</th>
<th>name</th>
<th>age</th>
<th>addr</th>
<th>score</th>
</tr>
{% for item in posts.object_list %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.addr }}</td>
<td>{{ item.score }}</td>
</tr>
{% endfor %}
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination">
<!--当前页分页对象如果有上一页-->
{% if posts.has_previous %}
<li>
<a href="?p={{ posts.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
<!--循环显示可迭代页码范围-->
{% for p in posts.paginator.page_range %}
<!--如果是当前页,高亮显示-->
{% if p == posts.number %}
<li class="active"><a href="#">{{ p }}</a></li>
{% else %}
<li><a href="?p={{ p }}">{{ p }}</a></li>
{% endif %}
{% endfor %}
<!--当前页分页对象如果有下一页-->
{% if posts.has_next %}
<li>
<a href="?p={{ posts.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</body>
</html>
使用Django内置Paginator可以实现分页,但分页区域显示的页码范围 page_range 默认是全部,如果页数很多,页码效果将会很差,下面介绍修改扩展Django内置分页,实现自定义分页区域显示的页码范围功能。
4、扩展Django内置分页
在视图函数中自定义一个新类,继承 django.core.paginator.Paginator 来进行扩展。
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
class CustomPaginator(Paginator):
def __init__(self ,current_page, per_pager_num, *args, **kwargs):
# 当前页
self.current_page = int(current_page)
# 页码最大显示范围
self.per_pager_num = per_pager_num
# 继承父类Paginator的其他属性方法
Paginator.__init__(self, *args, **kwargs)
def pager_num_range(self):
if self.num_pages < self.per_pager_num:
return range(1, self.num_pages + 1)
part = int(self.per_pager_num / 2)
if self.current_page <= part:
return range(1, self.per_pager_num + 1)
if (self.current_page + part) > self.num_pages:
return range(self.num_pages - self.per_pager_num + 1, self.num_pages + 1)
else:
return range(self.current_page - part, self.current_page + part + 1)
## 调用
mylist=[]
for i in range(1,1000):
temp = {'id':i,'name':'zhang'+str(i),'age':i,'addr':'gaoxin'+str(i),'score':2*i +1}
mylist.append(temp)
def myin(request):
try:
current_page = int(request.GET.get('p'))
except:
current_page = 1
# 实例化分页对象,每页10条数据
paginator = CustomPaginator(current_page, 5, mylist, 10)
try:
# 取对象的当前页分页对象
posts = paginator.page(current_page)
# current_page非数字时取第一页
except PageNotAnInteger:
posts = paginator.page(1)
# current_page空时取最后一页
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request,'inpage.html',{'posts':posts})
完美解决问题。