当网页上显示的数据过多时,通常需要进行分页显示。Django 内置的 Pagination 能够帮助我们实现简单的分页功能。
Paginator 类的常用方法
分页功能由 Django 内置的 Paginator
类提供。这个类位于 django/core/paginator.py,需要使用它时,只需在适当的地方导入这个类即可。
from django.core.paginator import Paginator
只需实例化一个 Paginator
对象,并在实例化时传入一个需要分页的对象列表,就可以得到分页后的对象数据。
# 对 item_list 进行分页,每页包含 2 个数据。
>>> item_list = ['john', 'paul', 'george', 'ringo']
>>> p = Paginator(item_list, 2)
取特定页的数据:
# 取第 2 页的数据
>>> page2 = p.page(2)
>>> page2.object_list
['george', 'ringo']
查询特定页的当前页码数:
>>> page2.number
2
查看分页后的总页数:
>>> p.num_pages
2
查看某一页是否还有上一页,以及查询该页上一页的页码:
# 查询第二页是否还有上一页
>>> page2.has_previous()
True
# 查询第二页上一页的页码
>>> page2.previous_page_number()
1
查看某一页是否还有下一页,以及查询该页下一页的页码:
# 查询第二页是否还有下一页
>>> page2.has_next()
False
# 查询第二页下一页的页码
>>> page2.next_page_number()
django.core.paginator.EmptyPage: That page contains no results
更多方法和属性请参阅 Django Pagination 的官方文档。
用 Paginator 给文章列表分页
使用上面的一些方法,我们可以实现一个类似于 Django 官方博客一样的简单分页效果,效果如下。
假设我们在博客的首页展示全部文章,其对应的视图函数为:
def index(request):
post_list = Post.objects.all()
return render(request, 'blog/index.html', context={'post_list': post_list})
这里我们把全部文章取出来后存在 post_list
变量里,然后将它传递给了模板。现在来使用 Paginator
类对 post_list
进行分页。在视图函数里不再将全部的文章数据 post_list
传给模板了,而是把用户请求页的数据传给模板,这样用户看到的就是其请求页的文章数据。
视图函数修改如下:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def index(request):
post_list = Post.objects.all() ①
paginator = Paginator(post_list, 10) ②
page = request.GET.get('page') ③
try:
post_list = paginator.page(page) ④
except PageNotAnInteger:
post_list = paginator.page(1) ⑤
except EmptyPage:
post_list = paginator.page(paginator.num_pages) ⑥
return render(request, 'blog/index.html', context={'post_list': post_list})
① 获取全部的文章数据,保存在 post_list
中。
② 对 post_list
进行分页,每页 10 篇文章。为了测试分页你可以把数字改小点。
③ 获取用户请求页的页码。我们给页码设置的 URL 类似于 http://zmrenwu.com/?page=2。其中 ? 号后面的 page=2 表示用户请求的页码数。Django 会将问号后面的请求参数保存到 request.GET
属性里,这是一个类字典的属性。例如这里 page
作为键被保存,其值为 2。
④ 尝试获取用户请求页的文章列表。
⑤ 用户请求的 URL 中,page 的值可能不一定是整数,例如用户可能请求 http://zmrenwu.com/?page=xyz 这样的 URL。这时候将 page
作为参数传给 paginator.page
方法将抛出一个 PageNotAnInteger
异常。我们处理这个异常的方式是:将第一页的数据返回给用户。
⑥ 如果 page
的值是一个整数,但是值太大了。例如总共只有 4 页,但用户请求第 10 页的数据,这时候 paginator.page
方法会抛出 EmptyPage
异常。这里处理这个异常的方式是:返回最后一页的数据给用户。
在模板中设置分页导航
接下来便是在模板中设置分页导航,比如上一页、下一页的按钮,以及显示一些页面信息。我们这里设置和 Django 官方博客那样的分页导航样式(具体的样式见上图)。
在你想要显示分页信息的地方使用下面的代码。这里以 index.html
为例:
<div class="pagination">
{% if post_list.has_previous %}
<!-- 如果当前页还有上一页,显示一个上一页的按钮 -->
<a href="?page={{ post_list.previous_page_number }}">上一页</a>
{% endif %}
<span class="current">
<!-- 显示当前页面信息 -->
第 {{ post_list.number }} 页 / 共 {{ post_list.paginator.num_pages }} 页
</span>
{% if post_list.has_next %}
<!-- 如果当前页还有上下页,显示一个下一页的按钮 -->
<a href="?page={{ post_list.next_page_number }}">下一页</a>
{% endif %}
</div>
其中 {{ }} 模板变量中的内容,其含义已在文章开头部分的Paginator 类的常用方法中已有介绍。最终我们得到如下的分页效果:
当然这只是一个简单示例,分页导航处的视觉效果并不是很好看,你可以自行为其添加 CSS 样式使其看上去更加美观。
进一步拓展
使用 Django 内置的 Pagination 只能实现上面的简单分页效果,但通常更加高级的分页效果应该像下图这样:
当前页面高亮显示,且显示当前页面前后几页的页码,始终显示第一页和最后一页的页码,中间可能还有省略号的效果,表示还有未显示的页码。
仅仅使用 Django Pagination 内置的方法无法实现这样的效果,需要我们写一些额外的代码来拓展 Pagination 的功能。下一篇文章将详细说明该如何拓展 Pagination 以实现一个完善的分页效果。
-- EOF --