前言
经过了上一篇添加侧边栏之后,我们的博客主页就基本有个博客样子了,但是博客主页会将所有的文章一次性显示出来,这就不太科学了,所以需要对文章进行分页,同时添加一个分页导航栏
Paginator和Page
Paginator
是分页器,可以将一个列表,按要求分页,而Page
就是其中的一页
from blog.models import Post
from django.core.paginator import Paginator
posts = Post.objects.all()
#将posts分页,每页有10篇文章,假设有100篇post,这里就会分成10页
paginator = Paginator(posts, 10)
paginator.page_range
#页码范围 range(1, 11)
paginator.num_pages
#页数 10
paginator.object_list
#所以文章 就是posts
page = paginator.page(2)
#返回的是Page对象,参数2是第几页
page.paginator
#返回的是Paginator对象
page.object_list
#当前页的对象列表
page.number
#当期的页码 2
page.has_next()
page.has_previous()
page.has_other_pages()
page.next_page_number()
page.previous_page_number()
#判断是否有上下一页以及对应的页码
#还有其他的一些用法没列出来,可以查看官方文档
在ListView里使用paginator
只要在ListView
里定义一个paginate_by
,就可以使用分页了
比如在IndexView
里指定paginate_by = 8
,这样每一页就只有8篇文章了
class IndexView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'blog/index.html'
paginate_by = 8
定义了paginate_by
之后,模板上下文就多了三个变量paginator
、page_obj
和is_paginated
paginator
就是Paginator对象,page_obj
是Page对象,is_paginated
用来判断是否需要分页,如果只有一页就不用分页了
有了这几个变量之后,最简单的就是直接在模板里添加
{% if is_paginated %}
<ul>
{% for page in paginator.page_range %}
<li><a href="?page={{ page }}">{{ page }}</a></li>
{% endfor %}
</ul>
{% endif %}
这里的超链接url
是?page=页码
,点击就会显示对应页码的页面是因为ListView
已经对这个查询字符串做了处理
只要定义了paginate_by
,就会去解析page
参数,page
参数名可以通过page_kwarg
修改
具体实现源码可以查看MultipleObjectMixin
类
同时当前页码的文章列表不再从posts
里获取,而是通过page_obj.object_list
获取
美化分页
上面的模板代码有个弊端,如果文章很多的话,页面范围就很大,分页组件显示出来也非常的长
可以将中间的页码用省略号来表示,然后根据当前页面动态更新分页组件
但是如果在模板中加这些判断代码的话,会很难实现,因此可以用模板标签来实现
我们要实现的是如图所示,两端至少2个可见页码,当前页码两侧至多3个可见页码
这里使用一个新的自定义标签装饰器inclusion_tag
,可以直接返回HTML,而不是一堆模板变量
inclusion_tag
装饰器需要制定模板文件,同时将takes_context
设置为True
,可以自动获取标签所在模板的上下文,这样就不用自己给标签传递参数了
新建blog/templatetags/paginator.py
,内容如下
from django import template
register = template.Library()
@register.inclusion_tag('blog/paginator.html', takes_context=True)
def pagination(context, *args, **kwargs):
paginator = context['paginator']
page_obj = context['page_obj']
is_paginated = context['is_paginated']
page_num = page_obj.number
pagination_required = is_paginated
if not pagination_required:
page_range = []
else:
ON_EACH_SIDE = 3
ON_ENDS = 2
if paginator.num_pages <= 10:
page_range = paginator.page_range
else:
page_range = []
if page_num > (ON_EACH_SIDE + ON_ENDS + 1):
page_range.extend(range(1, ON_ENDS + 1))
page_range.append(None)
page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
else:
page_range.extend(range(1, page_num + 1))
if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS + 1):
page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
page_range.append(None)
page_range.extend(range(paginator.num_pages - ON_ENDS + 1, paginator.num_pages + 1))
else:
page_range.extend(range(page_num + 1, paginator.num_pages + 1))
return {
'pagination_required': pagination_required,
'page_range': page_range,
'page_num': page_num,
'page_obj': page_obj,
}
我们是在ListView
的模板里使用这个标签的,所以当takes_context=True
时,就可以通过context
,得到paginator
、page_obj
和is_paginated
这个函数主要作用是当页数小于10页,不对页码范围做处理,当大于10页时,会按情况省略掉中间的页面,并用一个None
表示
这个函数返回的是一个字典,这个字典可以在inclusion_tag
的模板里面使用,接下来编写这个模板文件
新建blog/templates/blog/paginator.html
文件,内容如下
{% if pagination_required %}
<ul>
{% if page_obj.has_previous %}
<li><a href="?page={{ page_obj.previous_page_number }}">previous</a></li>
{% else %}
<li><a>previous</a></li>
{% endif %}
{% for page in page_range %}
{% if page is not None %}
{% if page == page_num %}
<li><span>{{ page }}</span></li>
{% else %}
<li><a href="?page={{ page }}">{{ page }}</a></li>
{% endif %}
{% else %}
<li><span>...</span></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}">next</a></li>
{% else %}
<li><a>next</a></li>
{% endif %}
</ul>
{% endif %}
根据是否有上下一页添加对应的超链接。根据页码的值是否为None
,是的话就显示...
至此,美化后的分页组件也写好了,只需要在主页的模板里添加如下两句,就可以显示分页组件了
{% load paginator %}
{% pagination %}