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


Posted in Python onApril 20, 2015

Blog Posts的提交

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

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

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

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

<!-- 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):
 

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请求到数据库中:
 

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在索引视图中创建的。
 

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

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

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:
 

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

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

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

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

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

# pagination
POSTS_PER_PAGE = 3

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

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

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

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):

 

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方法中修改:
 

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

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

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

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

<!-- 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):
 

<!-- 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):
 

<!-- 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):
 

@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):
 

<!-- 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 %}

结束语

以下我把本文开发的带分页的微型博客应用代码打包并提供大家下载:

下载:microblog-0.9.zip.

Python 相关文章推荐
Python写的Socks5协议代理服务器
Aug 06 Python
Python中动态获取对象的属性和方法的教程
Apr 09 Python
Hadoop中的Python框架的使用指南
Apr 22 Python
python遍历数组的方法小结
Apr 30 Python
Python实现在matplotlib中两个坐标轴之间画一条直线光标的方法
May 20 Python
Django中对数据查询结果进行排序的方法
Jul 17 Python
人生苦短我用python python如何快速入门?
Mar 12 Python
下载python中Crypto库报错:ModuleNotFoundError: No module named ‘Crypto’的解决
Apr 23 Python
python实现n个数中选出m个数的方法
Nov 13 Python
Django Form and ModelForm的区别与使用
Dec 06 Python
pytorch实现线性拟合方式
Jan 15 Python
matplotlib.pyplot.matshow 矩阵可视化实例
Jun 16 Python
在Python的Flask框架中实现单元测试的教程
Apr 20 #Python
Python的Flask框架中实现登录用户的个人资料和头像的教程
Apr 20 #Python
Python的Flask框架中实现简单的登录功能的教程
Apr 20 #Python
Python的Flask框架与数据库连接的教程
Apr 20 #Python
Python的Flask框架中web表单的教程
Apr 20 #Python
在Python的Flask框架中使用模版的入门教程
Apr 20 #Python
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 #Python
You might like
php中长文章分页显示实现代码
2012/09/29 PHP
迅速确定php多维数组的深度的方法
2014/01/07 PHP
PHP判断表达式中括号是否匹配的简单实例
2016/10/22 PHP
PHP mysqli事务操作常用方法分析
2017/07/22 PHP
xml 与javascript结合的问题解决方法
2007/03/24 Javascript
Javascript-Mozilla和IE中的一个函数直接量的问题分析
2007/08/12 Javascript
用apply让javascript函数仅执行一次的代码
2010/06/27 Javascript
Windows 系统下安装和部署Egret的开发环境
2014/07/31 Javascript
jQuery中children()方法用法实例
2015/01/07 Javascript
BootStrap 模态框实现刷新网页并关闭功能
2017/01/04 Javascript
Bootstrap导航条学习使用(一)
2017/02/08 Javascript
微信小程序-横向滑动scroll-view隐藏滚动条
2017/04/20 Javascript
浅谈vue项目利用Hbuilder打包成APP流程,以及遇到的坑
2020/09/12 Javascript
[02:09]EHOME夺得首届辉夜杯冠军—现场颁奖仪式
2015/12/28 DOTA
python找出因数与质因数的方法
2019/07/25 Python
python求加权平均值的实例(附纯python写法)
2019/08/22 Python
Python object类中的特殊方法代码讲解
2020/03/06 Python
Python PyQt5运行程序把输出信息展示到GUI图形界面上
2020/04/27 Python
Python wordcloud库安装方法总结
2020/12/31 Python
如何使用amaze ui的分页样式封装一个通用的JS分页控件
2020/08/21 HTML / CSS
世界上最好的旅行夹克:BauBax
2018/12/23 全球购物
设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
2014/12/30 面试题
应届生妇产科护士求职信
2013/10/27 职场文书
汽车技术服务英文求职信范文
2014/01/02 职场文书
精彩自我鉴定
2014/01/16 职场文书
城市精细化管理实施方案
2014/03/04 职场文书
公司晚会主持词
2014/03/22 职场文书
临床护理求职信
2014/04/26 职场文书
单位绩效考核方案
2014/05/11 职场文书
银行求职信怎么写
2014/05/26 职场文书
关于读书的演讲稿500字
2014/08/27 职场文书
2014年办公室文秘工作总结
2014/12/09 职场文书
安全第一课观后感
2015/06/18 职场文书
听证会主持词
2015/07/03 职场文书
初中军训感言
2015/08/01 职场文书
「回转企鹅罐」10周年纪念展「輪るピングドラム展」海报公开
2022/03/22 日漫