Django自带分页器的实现
介绍
Django提供了一个新的类来帮助你管理分页数据,这个模块存放在django.core.paginator.py。
其中有两个核心类,一个是Paginator类,另一个是Page类。
Paginator类
初始化操作
class Paginator(object):
def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
self.object_list = object_list
self.per_page = int(per_page)
self.orphans = int(orphans)
self.allow_empty_first_page = allow_empty_first_page
self._num_pages = self._count = None
解释:
- object_list:可以是列表,元组,查询集或其他含有 count() 或 len()方法的可切片对象。对于连续的分页,查询集应该有序,例如有order_by()项或默认ordering参数。
- per_page:每一页中包含条目数目的最大值,不包括独立成页的那页。(见下面 orphans参数解释)。
- orphans=0:当你使用此参数时说明你不希望最后一页只有很少的条目。如果最后一页的条目数少于等于orphans的值,则这些条目会被归并到上一页中(此时的上一页变为最后一页)。
- allow_empty_first_page=True: 默认允许第一页为空。
类方法:
- Paginator.page(number):根据参数number返回一个Page对象。(number为1的倍数)
类属型:
- Paginator.count:所有页面对象总数,即统计object_list中item数目。
当计算object_list所含对象的数量时, Paginator会首先尝试调用object_list.count()。
如果object_list没有 count() 方法,Paginator 接着会回退使用len(object_list)。 - Pagnator.num_pages:页面总数。
- pagiator.page_range:页面范围,从1开始,例如[1,2,3,4]。
Page类
初始化操作
class Page(collections.Sequence):
def __init__(self, object_list, number, paginator):
self.object_list = object_list
self.number = number
self.paginator = paginator
类方法
- Page.has_next() 如果有下一页,则返回True。
- Page.has_previous() 如果有上一页,返回 True。
- Page.has_other_pages() 如果有上一页或下一页,返回True。
- Page.next_page_number() 返回下一页的页码。如果下一页不存在,抛出InvlidPage异常。
- Page.previous_page_number() 返回上一页的页码。如果上一页不存在,抛出InvalidPage异常。
- Page.start_index() 返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始。
比如,将五个对象的列表分为每页两个对象,第二页的start_index()会返回3。 - Page.end_index() 返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。
比如,将五个对象的列表分为每页两个对象,第二页的end_index() 会返回 4。
类属型
- Page.object_list 当前页上所有对象的列表。
- Page.number 当前页的序号,从1开始。
- Page.paginator 相关的Paginator对象。
异常处理
- InvalidPage(Exception): 异常的基类,当paginator传入一个无效的页码时抛出。
Paginator.page()放回在所请求的页面无效(比如不是一个整数)时,或者不包含任何对象时抛出异常。通常,捕获InvalidPage异常就够了,但是如果你想更加精细一些,可以捕获以下两个异常之一: - exception PageNotAnInteger,当向page()提供一个不是整数的值时抛出。
exception EmptyPage,当向page()提供一个有效值,但是那个页面上没有任何对象时抛出。
这两个异常都是InalidPage的子类,所以可以通过简单的except InvalidPage来处理它们。
使用方式
- views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
def listing(request):
contact_list = Contacts.objects.all() # 获取所有contacts,假设在models.py中已定义了Contacts模型
paginator = Paginator(contact_list, 25) # 每页25条
page = request.GET.get('page')
try:
contacts = paginator.page(page) # contacts为Page对象!
except PageNotAnInteger:
# If page is not an integer, deliver first page.
contacts = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
contacts = paginator.page(paginator.num_pages)
return render(request, 'list.html', {'contacts': contacts})
- *.html
{% for contact in contacts %}
{# Each "contact" is a Contact model object. #}
{{ contact.full_name|upper }}<br />
...
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
<a href="?page={{ contacts.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
<a href="?page={{ contacts.next_page_number }}">next</a>
{% endif %}
</span>
</div>
或者另一种形式
<div id="pagination">
<ul id="pagination-flickr">
{% if article_list.has_previous %}
<li class="previous"><a href="?page={{ article_list.previous_page_number }}{% if request.GET.year %}&year={{ request.GET.year }}{% endif %}{% if request.GET.month %}&month={{ request.GET.month }}{% endif %}{% if request.GET.cid %}&cid={{ request.GET.cid }}{% endif %}">«上一页</a></li>
{% else %}
<li class="previous-off">«上一页</li>
{% endif %}
<li class="active">{{ article_list.number }}/{{ article_list.paginator.num_pages }}</li>
{% if article_list.has_next %}
<li class="next"><a href="?page={{ article_list.next_page_number }}{% if request.GET.year %}&year={{ request.GET.year }}{% endif %}{% if request.GET.month %}&month={{ request.GET.month }}{% endif %}{% if request.GET.cid %}&cid={{ request.GET.cid }}{% endif %}">下一页 »</a></li>
{% else %}
<li class="next-off">下一页 »</li>
{% endif %}
</ul>
</div>
存在问题
描述
上面这种分页方式,在数据量较小的时候是没有效率问题,但是随着数据量的不断增大,查询速度越来越慢。
Django分页器优化的实现
原理
利用Django查询集的惰性查询特性
实现
def owner_page(request, obj):
"""
# 自定义分页器
:param request: request请求
:param obj: 分页对象
:return: 所在页码的对象集,所有页码,当前页码,分页对象的总数
"""
current_page = 1
all_page = 1
page_type = ''
try:
current_page = int(request.GET.get('cur', '1'))
all_page = int(request.GET.get('all', '1'))
page_type = str(request.GET.get('action', '')) # 向前翻页还是向下翻页
except ValueError:
current_page = 1
all_page = 1
page_type = ''
if page_type == 'next':
current_page += 1
elif page_type == 'previous':
current_page -= 1
if isinstance(obj, list):
count = len(obj)
else:
count = obj.count()
start = (current_page - 1) * PAGE_SIZE
end = current_page * PAGE_SIZE
data = obj[start:end]
if current_page == 1 and current_page == 1: # 标记1
all_page = math.ceil(count / PAGE_SIZE)
return data, all_page, current_page, count
使用方式
- views.py
def get_all_goods(request):
user_id = request.session.get('user_id')
goods = Good.objects.get_user_goods(user_id=user_id)
data, all_page, current_page, count = owner_page(request, goods)
return render(
request,
"goods_all_list.html",
{'data': data, 'allPage': all_page, 'curPage': current_page, 'count': count}
)
- page.html
<div id="pages" class="text-left">
<nav>
<ul class="pagination">
<li class="step-links">
{% ifnotequal curPage 1 %}
<a href="?cur={{ curPage }}&&all={{ allPage }}&&action=previous">
上一页
</a>
{% endifnotequal %}
<span class="current">第{{ curPage }} / {{ allPage }}页</span>
{% ifnotequal curPage allPage %}
<a href="?cur={{ curPage }}&&all={{ allPage }}&&action=next">
下一页
</a>
{% endifnotequal %}
</li>
<li><span class="[object Object]"
style="color:blank;sex:black;border:1px solid #ddd;">共{{ count }}条记录</span></li>
</ul>
</nav>
</div>
- goods_all_list.html
...
{% include "page.html" %}
...