Python的Flask框架中实现分页功能的教程

   这篇文章主要介绍了Python的Flask框架中实现分页功能的教程,文中的示例基于一个博客来实现,需要的朋友可以参考下

  Blog Posts的提交

  让我们从简单的开始。首页上必须有一张用户提交新的post的表单。

  首先我们定义一个单域表单对象(fileapp/forms.py):

  ?

1
2

class PostForm(Form):
post = TextField('post', validators = [Required()])

  下面,我们把这个表单添加到template中(fileapp/templates/index.html):

  ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

<!-- extend base layout -->
{% extends "base.html" %}
 
{% block content %}
<h1>Hi, {{g.user.nickname}}!</h1>
<form action="" method="post" name="post">
{{form.hidden_tag()}}
<table>
<tr>
<td>Say something:</td>
<td>{{ form.post(size = 30, maxlength = 140) }}</td>
<td>
{% for error in form.errors.post %}
<span style="color: red;">[{{error}}]</span><br>
{% endfor %}
</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Post!"></td>
<td></td>
</tr>
</table>
</form>
{% for post in posts %}
<p>
{{post.author.nickname}} says: <b>{{post.body}}</b>
</p>
{% endfor %}
{% endblock %}

  到目前为止没啥新的东西,你可以看到,我们仅仅添加了另一表单,就像我们上一次做的那样。

  最后,功能试图把所有东西都联系在一起,并被扩展来处理这个表单(fileapp/views.py):

  ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

from forms import LoginForm, EditForm, PostForm
from models import User, ROLE_USER, ROLE_ADMIN, Post
 
@app.route('/', methods = ['GET', 'POST'])
@app.route('/index', methods = ['GET', 'POST'])
@login_required
def index():
form = PostForm()
if form.validate_on_submit():
post = Post(body = form.post.data, timestamp = datetime.utcnow(), author = g.user)
db.session.add(post)
db.session.commit()
flash('Your post is now live!')
return redirect(url_for('index'))
posts = [
{
'author': { 'nickname': 'John' },
'body': 'Beautiful day in Portland!'
},
{
'author': { 'nickname': 'Susan' },
'body': 'The Avengers movie was so cool!'
}
]
return render_template('index.html',
title = 'Home',
form = form,
posts = posts)

  下面让我们逐一回顾一下这个功能中的变动:

  我们导入了Post和PostForm类

  我们接收了来自两个路径下的index和视图的POST请求,因为那就是我们如何接收提交的请求。

  当我们通过表单提交到功能视图后,我们会把新的Post记录录入数据库。然后就像之前做的一样,通过常规的GET请求来访问它。

  Templat会收到一条额外的内容--表单,所以它会提交给文本域。

  在我们继续之前还有最后一点提醒:注意下面我们如何添加一条新的Post请求到数据库中:

  ?

1

return redirect(url_for('index'))

  我们可以很容易的跳过重定向,并且允许它跳到模板渲染部分,而且效率更高。因为所做的所有重定向在经过web浏览器之后,都返回到这个相同的功能视图中来。

  所以,为什么选择重定向?考虑到当用户写下一个blog post请求之后,它只需提交然后点击浏览器刷新按钮。“Refresh”命令能做什么呢?浏览器会重新发送最后发布的请求作为一个“Refresh”命令的结果。(译者注:由于个人水平有限,如果您发现译处与原文有出入敬请指正。谢谢!)

  如果没有重定向,那么最后提交给表单的就是POST请求,所以一个“Refresh Action”会重新提交那个表单,将会导致第二次提交的post记录和第一次写入数据库中的是相同的。这样的行为Not so good.

  若是有了重定向,我们可以强制浏览器在表单提交之后发出另一个请求,它抓取了重定向的页面。这是一个简单的“GET”请求,所以“Refresh”动作会重复“GET”请求而不是再次提交表单。

  这个简单的小技巧避免用户在提交一个blog post请求之后,不小心刷新页面导致重复写入post请求。

  展现blog post请求

  下面我们来说点有意思的东西。我们要从数据库中抓取blog post请求并失之显示。

  如果你回忆一下之前部分文章,我们曾创建了许多所谓“虚假的”的请求并且在首页上面显示了很长时间。这些“虚假的”对象是作为Python list在索引视图中创建的。

  ?

1
2
3
4
5
6
7
8
9
10

posts = [
{
'author': { 'nickname': 'John' },
'body': 'Beautiful day in Portland!'
},
{
'author': { 'nickname': 'Susan' },
'body': 'The Avengers movie was so cool!'
}
]

  但是在上一篇文章中,我们创建的查询语句允许我们从“关注的人”当中获取所有的请求,所以我们可以用下面的这个语句来替换上文(fileapp/views.py):

  ?

1

posts = g.user.followed_posts().all()

  然后当你运行这个应用的时候,你将会看到冲数据库中抓取到的bolg post请求。

  User类的followed_posts方法返回了一条抓取我们感兴趣请求的SQL查询语句。在这个查询语句中,Callingall()检索所有的请求到一个list当中,所以我们以这个很像我们一直沿用至今的“虚假”请求的结构结束。他们如此的相像甚至template都没有注意到。

  此时您可以在此应用上自由发挥。你可以创建多个用户,让他们follow其他人,然后发布一些信息来看每一个用户是如何看到它的bolg post请求数据流的。

  分页

  我们的程序是越来越像样了,但是我们面临另外一个问题。我们在首页显示了所有的followed post。如果一个用户有上千篇followed post将会发生什么情况?或者一百万篇?就像我们可以想象到的,抓取并处理这么庞大的对象列表是十分低效率的。

  我们可以显示把这么大量的post分组来显示,或者分页。

  Flask-SQLAlchemy可以很好的支持分页。例如,我们可以通过如下方法,轻松获取某个用户的前3篇的followed posts:

  ?

1

posts = g.user.followed_posts().paginate(1, 3, False).items

  分页方法可以被任何query对象调用。它接受3个参数:

  页码,从1开始

  每页显示的记录数

  错误标记。如果是True,如果接收到超出记录范围的页面请求,那么404错误请求将会自动反馈到客户端浏览器。如果是False,那么一个空列表将会被返回,而不显示错误。

  paginate的返回值是一个Pagination对象。这个对象里面的成员包括了请求页面中的记录列表。稍后,我们会探讨Pagination对象中另外一些有用的东西。

  现在让我们来想想,如何在我们的view函数中实现分页。我们可以先把一些配置信息添加到我们的应用中,包括我们每页需要显示多少条记录(fileconfig.py):

  ?

1
2

# pagination
POSTS_PER_PAGE = 3

  使用全局配置文件去改变我们应用是一个很好的构思,因为我们只需要去一个地方就可以修改所有的配置。

  在最后的应用中,我们当然会使用比3更大的数字,但是作为测试,使用小的数字会更有效。

  之后,让我们来看看URLs是如何判断请求不同的页面的。我们之前已知道,Flask的routes可以接受参数的,所以我们可以在URL添加后缀,来指明我们想要的页面:

  ?

1
2
3
4

http://localhost:5000/ <-- page #1 (default)
http://localhost:5000/index <-- page #1 (default)
http://localhost:5000/index/1 <-- page #1
http://localhost:5000/index/2 <-- page #2

  我们可以轻易地实现URLs格式,并添加新的route到我们的view函数当中(fileapp/views.py):

  ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

from config import POSTS_PER_PAGE
 
@app.route('/', methods = ['GET', 'POST'])
@app.route('/index', methods = ['GET', 'POST'])
@app.route('/index/<int:page>', methods = ['GET', 'POST'])
@login_required
def index(page = 1):
form = PostForm()
if form.validate_on_submit():
post = Post(body = form.post.data, timestamp = datetime.utcnow(), author = g.user)
db.session.add(post)
db.session.commit()
flash('Your post is now live!')
return redirect(url_for('index'))
posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items
return render_template('index.html',
title = 'Home',
form = form,
posts = posts)

  我们的新route接受分页参数,并把参数定义为整数。我们还需要添加分页参数到index函数当中,并给它们一个默认值,因为那三个route当中有两个route没有分页参数,所以这些route就必须用到默认值。

  现在我们已经得到了一个页码参数,并且可以轻易的应用到我们的followed_post查询当中,而且还有一个我们之前以定义好的配置常量:POSTS_PER_PAGE。

  我们可以注意到,这些更改是如何的简单,并且当我们更改一个页面的时候,很少代码会被影响。我们尝试在写应用中的每个部分的时候,不去假设其他部分是如何的工作,这样就会让我们写出模块化的和健壮的应用,使得测试更简单,而且更少的出错机会,或者缺陷。

  现在你可以尝试在你的浏览器地址栏中,在URLs中输入页码。请确保你的pots超过3个,这样你就可以看到多个页面。

  页面导航

  现在我们需要添加链接,好让我们的用户可以查看上一个/下一个页面,很幸运,这个功能很容易实现,Flask-SQLAlchemy给我们完成了大部分的工作。

  我们现在给view函数更改一小部分代码。我们在paginate方法中修改:

  ?

1

posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items

  我们只保留了paginate方法返回的Pagination对象的item成员。但是这个对象包含一些非常有用的信息在里面,所以我们更改为保留Pagination对象(fileapp/views.py):

  ?

1

posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False)

  为了完成这个修改,我们还需要修改模板(fileapp/templates/index.html):

  ?

1
2
3
4
5
6

<!-- posts is a Paginate object -->
{% for post in posts.items %}
<p>
{{post.author.nickname}} says: <b>{{post.body}}</b>
</p>
{% endfor %}

  我们所做的这些是为了把全套分页对象应用到我们的模板当中。分页对象会用到如下成员:

  has_next: 如果本页之后还有超过一个分页,则返回True

  has_prev: 如果本页之前还有超过一个分页,则返回True

  next_num: 返回下一页的页码

  prev_num: 返回上一页的页码

  应用这些成员,我们可以生成如下代码(app/templates/index.html):

  ?

1
2
3
4
5
6
7
8

<!-- posts is a Paginate object -->
{% for post in posts.items %}
<p>
{{post.author.nickname}} says: <b>{{post.body}}</b>
</p>
{% endfor %}
{% if posts.has_prev %}<a href="{{ url_for('index', page = posts.prev_num) }}"><< Newer posts</a>{% else %}<< Newer posts{% endif %} |
{% if posts.has_next %}<a href="{{ url_for('index', page = posts.next_num) }}">Older posts >></a>{% else %}Older posts >>{% endif %}

  现在我们有两个超链接。第一个是叫做"Newer posts",可以链接到上一页(请记住,我们是把最新的post排在最前面,所以上一页的post是更加新的post)。相反,"Older posts"指向的是下一页。

  当我们在第一页的时候,我们不想显示上一页的链接,因为根本不存在。我们可以轻易的检测得到,因为posts.has_prev返回False。针对这种情况,我们可以只显示"上一页"的文本而不带链接属性。而下一页也是如此。

  实现Post子模板

  回到我们添加了阿凡达图片的文章,我们使用HTML给一个单独的post定义了一个子模板。原因是我们只需创建一次就可以多个地方使用,避免重复代码。现在我们就把这个子模板应用到我们的index页面中。就如我们今天做的事情一样,这是非常简单的事情(app/templates/index.html):

  ?

1
2
3
4

<!-- posts is a Paginate object -->
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}

  神奇吧?我们只是把子模板替换旧的渲染代码。现在我们得到了包含了用户的阿凡达图片的post页面。

  用户个人页面

  我们已完成了index页面的开发。但是我们在用户个人页面中也引入了posts,该页面只列出用户自己所发表的post。相应的,用户个人页面也需要按照index的设计去更改。

  和index页面的更改相似,以下是我们要做的几点:

  增加一个接受页码的route

  为view函数增加一个页码参数,默认值为1

  使用合适的数据库查询与分页代码,替换post列表

  使用分页对象更新模板

  下面是更新后的view函数(app/views.py):

  ?

1
2
3
4
5
6
7
8
9
10
11
12

@app.route('/user/<nickname>')
@app.route('/user/<nickname>/<int:page>')
@login_required
def user(nickname, page = 1):
user = User.query.filter_by(nickname = nickname).first()
if user == None:
flash('User ' + nickname + ' not found.')
return redirect(url_for('index'))
posts = user.posts.paginate(page, POSTS_PER_PAGE, False)
return render_template('user.html',
user = user,
posts = posts)

  我们注意到,这个函数已经包含了一个参数(用户的昵称),所以我们把页码添加到第二个参数。

  更改模板也是相当简单(app/templates/user.html):

  ?

1
2
3
4
5
6

<!-- posts is a Paginate object -->
{% for post in posts.items %}
{% include 'post.html' %}
{% endfor %}
{% if posts.has_prev %}<a href="{{ url_for('user', nickname = user.nickname, page = posts.prev_num) }}"><< Newer posts</a>{% else %}<< Newer posts{% endif %} |
{% if posts.has_next %}<a href="{{ url_for('user', nickname = user.nickname, page = posts.next_num) }}">Older posts >></a>{% else %}Older posts >>{% endif %}

时间: 2024-10-24 17:14:47

Python的Flask框架中实现分页功能的教程的相关文章

如何在Python的Flask框架中使用模版的入门教程

  如何在Python的Flask框架中使用模版的入门教程?          概述 如果你已经阅读过上一个章节,那么你应该已经完成了充分的准备工作并且创建了一个很简单的具有如下文件结构的Web应用: microblog |-flask文件夹 |-<一些虚拟环境的文件> |-app文件夹 | |-static文件夹 | |-templates文件夹 | |-__init__.py文件 | |-views.py文件 |-tmp文件夹 |-run.py文件 以上给你介绍了在Python的Flask

Python的Flask框架中web表单的教程

  下面为你介绍了Python的Flask框架中web表单的教程,表单是学习各个web框架中的基础知识,需要的朋友可以参考下 概要 在前面章节我们为主页定义了一个简单的模板,部分尚未实现的模块如用户或帖子等使用模拟的对象作为临时占位. 本章我们将看到如何利用web表单填补这些空白. web表单是web应用中最基本的构建要素,我们将通过表单来实现用户发帖和应用登录功能. 完成本章内容你需要基于前面章节完成的微博应用代码,请确认这些代码已安装并能正常运行. 配置 Flask-WTF是WTForms项

在Python的Flask框架中使用模版的入门教程_python

 概述 如果你已经阅读过上一个章节,那么你应该已经完成了充分的准备工作并且创建了一个很简单的具有如下文件结构的Web应用:   microblog     |-flask文件夹     |-<一些虚拟环境的文件>     |-app文件夹     |  |-static文件夹     |  |-templates文件夹     |  |-__init__.py文件     |  |-views.py文件     |-tmp文件夹     |-run.py文件 亲,想要运行这个程序么?那就运行这

Python的Flask框架中@app.route的用法教程_python

在我上一篇文章,我搭了一个框架,模拟了Flask网站上"@app.route('/')"第一条例子的行为. 如果你错过了那篇"这不是魔法",请点击这里. 在这篇文章中,我们打算稍微调高点难度,为我们的URL加入可变参数的能力,在本文的最后,我们将支持下述代码段所期望达到的行为. app = Flask(__name__) @app.route("/hello/<username>") def hello_user(username):

Python的Flask框架中web表单的教程_python

 概要 在前面章节我们为主页定义了一个简单的模板,部分尚未实现的模块如用户或帖子等使用模拟的对象作为临时占位. 本章我们将看到如何利用web表单填补这些空白. web表单是web应用中最基本的构建要素,我们将通过表单来实现用户发帖和应用登录功能. 完成本章内容你需要基于前面章节完成的微博应用代码,请确认这些代码已安装并能正常运行. 配置 Flask-WTF是WTForms项目的Flask框架扩展,我们将用他来帮助我们处理web表单. 大部分Flask扩展都需要定义相关配置项,所以我们先来在应用根

Python的Flask框架中实现简单的登录功能的教程

  Python的Flask框架中实现简单的登录功能的教程,登录是各个web框架中的基础功能,需要的朋友可以参考下 回顾 在前面的系列章节中,我们创建了一个数据库并且学着用用户和邮件来填充,但是到现在我们还没能够植入到我们的程序中. 两章之前,我们已经看到怎么去创建网络表单并且留下了一个实现完全的登陆表单. 在这篇文章中,我们将基于我门所学的网络表单和数据库来构建并实现我们自己的用户登录系统.教程的最后我们小程序会实现新用户注册,登陆和退出的功能. 为了能跟上这章节,你需要前一章节最后部分,我们

在Python的Flask框架中实现单元测试的教程

  在Python的Flask框架中实现单元测试的教程,属于自动化部署的方面,可以给debug工作带来诸多便利,需要的朋友可以参考下 概要 在前面的章节里我们专注于在我们的小应用程序上一步步的添加功能上.到现在为止我们有了一个带有数据库的应用程序,可以注册用户,记录用户登陆退出日志以及查看修改配置文件. 在本节中,我们不为应用程序添加任何新功能,相反,我们要寻找一种方法来增加我们已写代码的稳定性,我们还将创建一个测试框架来帮助我们防止将来程序中出现的失败和回滚. 让我们来找bug 在上一章的结尾

如何在Python的Flask框架中实现全文搜索?

  这篇文章主要介绍了在Python的Flask框架中实现全文搜索功能,这个基本的web功能实现起来非常简单,需要的朋友可以参考下 全文检索引擎入门 灰常不幸的是,关系型数据库对全文检索的支持没有被标准化.不同的数据库通过它们自己的方式来实现全文检索,而且SQLAlchemy在全文检索上也没有提供一个好的抽象. 我们现在使用SQLite作为我们的数据库,所以我们可以绕开SQLAlchemy而使用SQLite提供的工具来创建一个全文检索索引.但这么做不怎么好,因为如果有一天我们换用别的数据库,那么

在Python的Flask框架中使用日期和时间的教程

  这篇文章主要介绍了在Python的Flask框架中使用日期和时间的教程,包括对各个时区之间转换的一些处理,需要的朋友可以参考下 时间戳的问题 我们的微博应用的一个忽略了很久的问题就是日间和日期的显示. 直到现在,我们在我们的User和Post对象中使用Python它自己的方式来渲染时间对象,但这并不是一个好的解决方案. 考虑下这样的例子.我正在写这篇文章,此时正是12月31号下午3:54.我的时区是PST(或者你们更习惯的:UTC-8). 在Python解释器中运行,我得到下面输出: ? 1