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函数有些区别,动态参数不需要在定义函数时引入,只需要在使用时调用argskwargs两个内置变量。
所以我们可以把上面的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的一样,所以上述的前端模版也适用。

Comments
Write a Comment