如果有多条数据要传递给客户端的时候,例如查询某用户的所有博客,一次性全部发送过去不仅传递耗时,在客户端加载出来也并不美观。这个时候就需要用到分页的帮助了,将全部数据分解为多块,根据用户的请求只发送指定页码的小块数据。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
环境准备
先总结下我的操作环境:
- Centos 7
- Python 3.7
- Pycharm 2019.3
- Django 2.2
因为Django长期支持版本2.2LTS和前一个长期支持版本1.11LTS有许多地方不一样,需要小心区分。
分页逻辑
创建应用Page
,注册在配置中,创建并在项目中注册urls.py
。然后创建如下model
class People(models.Model):
p_name = models.CharField(max_length=16)
p_age = models.IntegerField(default=18)
迁移到数据库中待用。
新建如下路由和view函数用来往数据库中添加99条记录
path('addpeople/', views.add_people, name='add_people'),
def add_people(request):
for i in range(1,100):
people = People()
people.p_name = 'xiaofu{}'.format(str(i))
people.p_age = i
people.save()
return HttpResponse('Add 100 people done')
访问http://127.0.0.1:8000/page/addpeople/
即可成功添加数据。
有了数据,下面开始今天的真题。
未分页时
下面模拟用户请求所有的数据,创建如下路由和view函数
path('getpeople/', views.get_people, name='get_people'),
def get_people(request):
people_info = People.objects.all()
context = {
'people_info': people_info
}
return render(request, 'people_info.html', context=context)
其中h5文件people_info.html
如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>People Info</title>
</head>
<body>
<ul>
{% for people in people_info %}
<li>{{ people.p_name }}: {{ people.p_age }}</li>
{% endfor %}
</ul>
</body>
</html>
之后访问的效果如下
可以说非常不美观。
数据切片
理想的效果,是每次显示少量信息,然后分多页显示。
前面在学MTV中的models时候,我们了解到查询出来的数据是一个QuerySet,其可以做类似于list的切片操作,如下
In [1]: a = [1,2,3,4,5]
In [2]: b = a[0:2]
In [3]: b
Out[3]: [1, 2]
这样我们将99条数据切为10页,切片时从整体的[0,10]
一直到[90,100]
即可。具体显示第几页由用户的url中的查询参数传递进来。为了更灵活,也可以让用户再传递一个每页显示的记录数量代替这个默认的10。
分页效果
创建如下路由和view函数
path('get_people_with_page/', views.get_people_with_page, name='get_people_with_page'),
def get_people_with_page(request):
page_num = int(request.GET.get('page_num', 1))
page_record = int(request.GET.get('page_record', 10))
people_info = People.objects.all()[page_record * (page_num - 1):page_record * page_num]
context = {
'people_info': people_info
}
return render(request, 'people_info.html', context=context)
这里还是用的跟上面同样的h5文件,不过传递进去的是已经切片的数据了。默认每页显示10条数据,显示第一页,如下
可以传递单个参数,另一个参数用默认值,如下显示第4页
也可以传递两个参数,同时指定每页显示数目和显示的页码,如下
这就是分页显示最基本的逻辑。
但是很明显光有最基本的功能是不够的,例如不能通过点击来切换页面,也不知道一共有多少页。为了使得分页变得更简便功能更丰富,Django也直接集成了自己的一个分页模块,就是下面要说的Paginator类。
Paginator类
Paginator类的作用就是将一个QuerySet变为多个Page实例,可以参考其官方文档。
初始化及常用方法和属性
初始化的时候Paginator一共接受两个参数,第一个参数可以传递一个list,tuple或者QuerySet等任何可以切片的对象。第二个参数是每一页显示的条目个数。
这里以list为例进行演示,通过python manage.py shell
进入交互环境。
>>> from django.core.paginator import Paginator
>>> P = Paginator(['xiaofu1','xiaofu2','xiaofu3','xiaofu4'],2)
显示所有条目
>>> P.object_list
['xiaofu1', 'xiaofu2', 'xiaofu3', 'xiaofu4']
显示总条目个数,总页数和每页条目数
>>> P.count
4
>>> P.num_pages
2
>>> P.per_page
2
其中每一页是个Page对象,下面再单独写Page对象
>>> P.page_range
range(1, 3)
>>> P.page(1)
<Page 1 of 2>
Page对象
获取其中一个Page对象,注意这里是从1开始的
>>> page1=P.page(1)
查看本页面的内容和页码
>>> page1.number
1
>>> page1.object_list
['xiaofu1', 'xiaofu2']
本页面内容在所有内容中的index
>>> page1.start_index()
1
>>> page1.end_index()
2
查看本页面在所有页面中的位置关系,因为已经是最前面一页,所以再查看前面一页会报错
>>> page1.has_previous()
False
>>> page1.has_next()
True
>>> page1.has_other_pages()
True
>>> page1.previous_page_number()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/fuhx/python_projects/django/lib/python3.7/site-packages/django/core/paginator.py", line 174, in previous_page_number
return self.paginator.validate_number(self.number - 1)
File "/home/fuhx/python_projects/django/lib/python3.7/site-packages/django/core/paginator.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
>>> page1.next_page_number()
2
还有一些就不一一展示了,下面通过Paginator类结合Bootstrap来对刚才的页面展示进行一下优化
结合Bootstrap效果
少废话,先来看看效果
写过前端的朋友应该都对Bootstrap很熟悉,其自带很多组件及对应的css,通过js展示出一些比较炫酷美观的效果。例如上面的导航条的css就是自带的,我们要完成三个目的:
- 点击导航条数字调到对应页码
- 点击previous和next进行前后页码跳转
- 当不能前后跳转是previous或者next要不能被点击
下面正式开始。
新建一个模板文件,在head里面加上Bootstrap所需的css和js。
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
然后直接去Bootstrap的Pagination组件页面复制框架代码进来
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
之后如果请求该页面会出现一个如下所示的分页主键,css效果出来了,但是没有实现功能
先修改下view函数,将people_info
变为包含所有结果的QuerySet对象,并做为参数给Pagination构造函数。传递给h5页面两个对象,分别是Paginator实例和Page对象
def get_people_with_page(request):
page_num = int(request.GET.get('page_num', 1))
page_record = int(request.GET.get('page_record', 10))
# people_info = People.objects.all()[page_record * (page_num - 1):page_record * page_num]
people_info = People.objects.all()
P = Paginator(people_info,page_record)
page = P.page(page_num)
context = {
'paginator':P,
'page': page
}
return render(request, 'people_info_with_page.html', context=context)
之后重点看下h5页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>People Info</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
</head>
<body>
<ul>
{% for item in page.object_list %}
<li>{{ item.p_name }}</li>
{% endfor %}
</ul>
<nav aria-label="Page navigation example">
<ul class="pagination">
{% if page.has_previous == False %}
<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
{% else %}
<li class="page-item"><a class="page-link"
href="{% url 'page:get_people_with_page' %}?page_num={{ page.previous_page_number }}&page_record={{ paginator.per_page }}">Previous</a>
</li>
{% endif %}
{% for num in paginator.page_range %}
<li class="page-item"><a class="page-link"
href="{% url 'page:get_people_with_page' %}?page_num={{ num }}&page_record={{ paginator.per_page }}">{{ num }}</a>
</li>
{% endfor %}
{% if page.has_next == False %}
<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
{% else %}
<li class="page-item"><a class="page-link"
href="{% url 'page:get_people_with_page' %}?page_num={{ page.next_page_number }}&page_record={{ paginator.per_page }}">Next</a>
</li>
{% endif %}
</ul>
</nav>
</body>
</html>
这里的body分两大块来看:
- 显示条目
- 导航
其中导航又分为三块:
- previous
- 数字
- next
先看显示条目部分
<ul>
{% for item in page.object_list %}
<li>{{ item.p_name }}</li>
{% endfor %}
</ul>
应该比较好理解,page.object_list
是当前页面所有条目的一个list,循环获取单个元素的内容。
然后是导航的数字部分
{% for num in paginator.page_range %}
<li class="page-item"><a class="page-link"
href="{% url 'page:get_people_with_page' %}?page_num={{ num }}&page_record={{ paginator.per_page }}">{{ num }}</a>
</li>
{% endfor %}
这里直接修改Bootstrap框架代码的href
部分,做一个反向解析,后面加上两个查询参数。
重点是导航的previous部分
{% if page.has_previous == False %}
<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
{% else %}
<li class="page-item"><a class="page-link"
href="{% url 'page:get_people_with_page' %}?page_num={{ page.previous_page_number }}&page_record={{ paginator.per_page }}">Previous</a>
</li>
{% endif %}
首先判断本页码是否还有前页码,如果没有,在Bootstrap自带的代码基础上加一个disabled
类名。如果有前页码,修改href
为前一个页码的url,注意查询参数中是previous_page.number
。
导航的next部分 也同理,就不再重复了。
总结
这篇文章我们一起学习了分页的基本逻辑和导航条的实现。数据展示的逻辑还是不难的,但是导航条的展示却可以千变万化,有带省略号的,有等分显示页码的,有前密后疏的排列的。Bootstrap只是一种最基本的实现,更多有意思的导航条等着我们去慢慢观察和发现。