Flask分页
在向页面输出内容的时候,如果内容过多,就会导致页面渲染缓慢。这时需要将内容分页,一次只输出一小部分内容,加快页面渲染的速度。
在Flask中,常用的数据库引擎如Flask-Mongoengine,Flask-SQLAlchemy都集成了分页功能,可以很方便的完成分页。
Flask-Mongoengine分页
Flask-Mongoengine默认的搜索结果是QuerySet类型,分页函数就是对QuerySet进行操作。分页函数有两个:paginate、paginate_field。其中,paginate是对搜索结果进行分页,而paginate_field是对搜索结果的某一个document 的内容分页,这里用paginate作为示例。
view.py
@main.route(/some_page)
def post_list():
posts = Posts.objects().order_by('-published_at')
page = request.args.get('page', 1, type=int) #从request中获取页码参数
posts_with_paginate = posts.paginate(page=page, per_page=20) #默认每页20条内容
return render_template('post_list.html', posts=posts_with_paginate)
分页后的查询结果是Pagination类,它的成员变量有
* `has_next`:是否有下一页
* `has_prev`: 是否有前一页
* `next_num`:下一页的页码
* `prev_num`: 前一页的页码
* `page`: 当前页
* `pages`: 总页数
* `per_page`: 每页内容数
* `total`: 内容总条数
* `items`: 原查询结果的list
templates
在前端使用Jinja2模版引擎,可以将分页写为一个macro宏,这里需要bootstrap支持。
{% macro render_pagination(pagination, endpoint) %}
<div class="row">
<div class="col-md-12">
<ul class="pagination pagination-without-border pull-right">
<li {% if pagination.has_prev %}class="active"{% else %}class="disabled"{% endif %}><a href="{{ url_for(endpoint, page=1) }}">«</a></li>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<li><a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a></li>
{% else %}
<li class="active"><a href="javascript:;">{{ page }}</a></li>
{% endif %}
{% else %}
<li><span class=ellipsis>…</span></li>
{% endif %}
{%- endfor %}
<li {% if pagination.has_next %}class="active"{% else %}class="disabled"{% endif %}><a href="{{ url_for(endpoint, page=pagination.pages) }}">»</a></li>
</ul>
</div>
</div>
{% endmacro %}
在需要显示页码的地方插入
{{ render_pagination(items, request.endpoint) }}
带参数url分页
当我们对某一个查询的结果进行分页时,如果url本身就带了查询参数,那么上面的函数就不适用了,比如:
/your_url?keyword=XXXX
需要达到如下的效果:
/your_url?keyword=XXXX&page=1
这时可以给macro宏传递动态参数,macro宏和python函数有些区别,动态参数不需要在定义函数时引入,只需要在使用时调用args
和kwargs
两个内置变量。
所以我们可以把上面的macro宏里,三个调用url_for
函数的地方做一下修改:
{{ url_for(endpoint, page=pagination.pages, **kwargs) }}
调用时,把相应的参数加上:
{{ render_pagination(items, endpoint=request.endpoint, keyword=request.args.get('keyword')) }}
对list分页
有时候要输出的结果可能不只是来源于一个数据表,比如需要将几个表的数据整合起来,生成一个列表,这时候以上的分页方式就不适用了。不过,拿flask_mongoengine的源码稍微修改下,就可以用于列表了。
import math
from flask import abort
class ListPagination(object):
def ___init__(self, iterable, page=1, per_page=20):
if page < 1:
abort(404)
self.iterable = iterable
self.page = page
self.per_page = per_page
self.total = len(iterable)
start_index = (page - 1) * per_page
end_index = page * per_page
self.items = iterable[start_index:end_index]
if not self.items and page != 1:
abort(404)
@property
def pages(self):
"""The total number of pages"""
return int(math.ceil(self.total / float(self.per_page)))
def prev(self):
assert self.iterable is not None, ('an object is required for this method to work')
iterable = self.iterable
return self.__class__(iterable, self.page -1, self.per_page)
@property
def prev_num(self):
"""Number of the previous page."""
return self.page - 1
@property
def has_prev(self):
"""True if a previous page exists"""
return self.page > 1
def next(self):
assert self.iterable is not None, ('an object is required for this method to work')
iterable = self.iterable
return self.__class__(iterable, self.page + 1, self.per_page)
@property
def has_next(self):
"""True if a next page exists."""
return self.page < self.pages
@property
def next_num(self):
"""Number of the next page"""
return self.page + 1
def iter_pages(self, left_edge=2, left_current=2,
right_current=5, right_edge=2):
"""Iterates over the page numbers in the pagination. The four
parameters control the thresholds how many numbers should be produced
from the sides. Skipped page numbers are represented as `None`.
"""
last = 0
for num in range(1, self.pages + 1):
if (
num <= left_edge or
num > self.pages - right_edge or
(num >= self.page - left_current and
num <= self.page + right_current)
):
if last + 1 != num:
yield None
yield num
last = num
if last != self.pages:
yield None
接口和flask_mongoengine的Pagination的一样,所以上述的前端模版也适用。