flask 学习笔记----分页

时间:2022-09-20 20:19:57

今天开始是对Flask中分页的学习,以下是视图函数的代码:

@main.route('/',methods=['GET','POST'])
def index():
form = PostForm()
if form.validate_on_submit() and current_user.can(Permission.WRITE_ARTICLES):
post = Post(body=form.body.data,author=current_user._get_current_object())
db.session.add(post)
return redirect(url_for('main.index'))
page = request.args.get('page',1,type=int)
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page,
per_page=15,
error_out=False)
posts = pagination.items
return render_template('index.html',form=form,posts=posts,pagination=pagination)

首先是之前主页渲染发表博客的部分,从
page = request.args.get('page',1,type=int)
开始,是分页的内容。
该语句中,request.args.get() 函数中,第一个参数为page,代表页码。
一开始看到这里的时候其实我是很不理解的。这个‘page’是从哪来的呢?
后来我明白了,在访问某个页码代表的网页时,需要的url为

http://127.0.0.1:5000/?page=1

这里的page便是刚刚函数中的第一个参数。
而第二个参数为默认值,当对首页进行访问的时候默认显示第二个参数所代表的页。

由于第三个参数中要求page的类型为整数,所以当我们输出类似于:

http://127.0.0.1:5000/?page=3.5

这样page为非整数的url时,不满足type=int,所以页面会自动返回默认值所代表的那一页。

探讨完了request,再说一下paginate()函数。对于

Post.query.order_by(Post.timestamp.desc()).paginate(page,
per_page=15,
error_out=False)

来说,前半段是将所有的博文按照时间戳降序排列,后半段的paginate()
函数中,第一个参数page是必须的参数,我们可以这样理解:以上的这段代码返回了一个Paginate()对象,该对象有以下属性:
flask 学习笔记----分页
其中page是方法的第一个参数,也是唯一必需的参数,代表了对象的页数。

per_page则是每页的记录,error_out负责在页码超出范围之后进行报错。

接下来就是比较核心的,也是不那么易懂的部分了。

我们需要在jinja2模板中写一个宏(macro),个人理解的话,宏在jinja2模板中的作用与函数在程序语言当中的作用十分类似,都有输入输出,然后都是将整体程序模块化的工具。

下面是分页宏的jinja2模板源码:

{% macro pagination_widget(pagination,endpoint) %}
<ul class="pagination">
<li
{% if not pagination.has_prev %} class="disabled"{% endif %}>
<a href="
{% if pagination.has_prev %}{{ url_for(endpoint,
page=pagination.page-1,**kwargs) }}
{% else %}#{% endif %}">
&laquo;
</a>
</li>
{% for p in pagination.iter_pages() %}
{% if p %}
{% if p==pagination.page %}
<li class="active">
<a href="
{{ url_for(endpoint,page = p,**kwargs) }}">{{ p }}</a>
</li>
{% else %}
<li>
<a href="
{{ url_for(endpoint,page = p,**kwargs) }}">{{ p }}</a>
</li>
{% endif %}
{% else %}
<li class="disbled"><a href="#">&hellip;</a> </li>
{% endif %}
{% endfor %}
<li
{% if not pagination.has_next %} class="disabled"{% endif %}>
<a href="
{% if pagination.has_next %}{{ url_for(endpoint,
page=pagination.page + 1,**kwargs) }}
{% else %}#{% endif %}">
&raquo;
</a>
</li>
</ul>
{% endmacro %}

首先从第一行开始看:

{% macro pagination_widget(pagination,endpoint) %}

以下是一些个人理解,如有错误欢迎指正!!~

个人通过与python中def函数的类比来进行理解,其中macro与def类似。

然后 pagination_widget相当于是宏的名称,也就相当于程序语言当中的函数名。括号中的两个变量就相当于函数的位置参数。

然后

<ul class="pagination">
......
</ul>

来代表整个分页部分的块。

接下来是第一个部分:

<li {% if not pagination.has_prev %} class="disabled"{% endif %}>
<a href="
{% if pagination.has_prev %}{{ url_for(endpoint,
page=pagination.page-1,**kwargs) }}
{% else %}#{% endif %}">
&laquo;
</a>
</li>

我们从内往外分析:
最内层的代码:

<a href="{% if pagination.has_prev %}{{ url_for(endpoint,
page=pagination.page-1,**kwargs) }}
{% else %}#{% endif %}">
&laquo;
</a>

这段代码的目的是对‘上一页’符号进行定义,&laquo;所代表的的是‘《’,代表一个上一页符号。所以我们很容易就可以得到一个思路:上一页不就是点了之后页码减一,并且当页码为1时停止功能么?

那么整个代码就好理解了,其中endpoint为引用母模板(也就是调用这个宏命令的模板)的视图函数的路由。

同样的方法也可以应用于“下一页“部分。

当然endpoint这个定义本身在flask中是属于链接模板和视图函数的一个桥梁,
详细的解释见
http://blog.csdn.net/hello_albee/article/details/51638358
这个兄弟的文章。

上面的部分对翻页‘《》’的功能进行了操作,下面则对‘1234’等页码进行设置。

{% for p in pagination.iter_pages() %}
{% if p %}
{% if p==pagination.page %}
<li class="active">
<a href="
{{ url_for(endpoint,page = p,**kwargs) }}">{{ p }}</a>
</li>
{% else %}
<li>
<a href="
{{ url_for(endpoint,page = p,**kwargs) }}">{{ p }}</a>
</li>
{% endif %}
{% else %}
<li class="disbled"><a href="#">&hellip;</a> </li>
{% endif %}
{% endfor %}

其中第一行:pagination.iter_pages()会 产生如图所示的基于页数的列表:
flask 学习笔记----分页
这个列表是动态产生的,不过逻辑没有大的区别,其中:

{% if p==pagination.page %}
<li class="active">

p如果与当前页面的页码一致则会激活,相应的图标会变为深蓝背景。

如果p!=pagination.page (也就是int),也就是说p现在是上图中的省略号…
(… 代表省略号)那么就给予一个disabled属性。

介绍完了宏如何编写之后,接下来就剩下对宏的引用了。

我们分别在首页和用户个人界面应用这个宏来产生页码。由上文可知,使用这个宏的时候需要传入两个参数,一是主要部分:pagination,二就是路由了。

需要注意的是,在首页中,我们需要将所有的博客显示出来。而在个人的界面中,我们则需要显示该用户个人的博客,因此在视图函数中需要有所改变。
对于首页,代码如下:

@main.route('/',methods=['GET','POST'])
def index():
form = PostForm()
if form.validate_on_submit() and current_user.can(Permission.WRITE_ARTICLES):
post = Post(body=form.body.data,author=current_user._get_current_object())
db.session.add(post)
return redirect(url_for('main.index'))
page = request.args.get('page',1,type=int)
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page,
per_page=15,
error_out=False)
posts = pagination.items
return render_template('index.html',form=form,posts=posts,pagination=pagination)

对于用户界面,代码如下:

@main.route('/user/<username>')
def user(username):
user = User.query.filter_by(username=username).first()
if user is None:
abort(404)
page = request.args.get('page',1,int)
pagination = Post.query.filter_by(author_id=user.id).paginate(page,per_page=5,error_out=False)
posts = pagination.items
return render_template('user.html',user=user,posts=posts,pagination=pagination)

其中不同的地方在于对pagination变量的赋值,一个是将所有的对象按时间排序赋给pagination,而另一个是通过username获得了user.id再按照author_id进行筛选。

为什么通过author_id进行筛选?在model.py中,我们在建立Post类时在User中有这么一句引用:

posts = db.relationship('Post',backref=author,lazy='dynamic')

也就是说User和Post建立了联系,并在posts中创建了author属性,只不过author属性不直接显示,而是通过author_id来进行体现。

其实之前通过进行关于Role和User的数据库操作,我们已经有所察觉:

>>>admin = Role(name='admin')
>>>susan = User(username='susan',role = admin)

User当中,role这个属性不直接显示,而是通过role_id来体现
role_id是User的一个外键,来与Role建立连接。

然后在模板当中,通过以下代码进行宏的调用:

{{ macros.pagination_widget(pagination,'main.user',username=user.username) }}

该宏是通过views.py中的user函数调用的,该函数的路由是这样的:

@main.route('/user/<username>')

因此在使用宏时,必须要给username一个名分,否则会报错。

首页的宏的引用只需要将‘main.user’改成‘main.index’即可。