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搭建FTP服务器的方法示例
Jan 19 Python
Python实现简单文本字符串处理的方法
Jan 22 Python
Django视图和URL配置详解
Jan 31 Python
python实现傅里叶级数展开的实现
Jul 21 Python
朴素贝叶斯Python实例及解析
Nov 19 Python
Python跳出多重循环的方法示例
Jul 03 Python
python使用pandas处理excel文件转为csv文件的方法示例
Jul 18 Python
python requests使用socks5的例子
Jul 25 Python
python使用 request 发送表单数据操作示例
Sep 25 Python
如何基于线程池提升request模块效率
Apr 18 Python
详解Windows下PyCharm安装Numpy包及无法安装问题解决方案
Jun 18 Python
解决IDEA翻译插件Translation报错更新TTK失败不能使用
Apr 24 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
一个简单的自动发送邮件系统(三)
2006/10/09 PHP
兼容firefox,chrome的网页灰度效果
2011/08/08 PHP
WAMP环境中扩展oracle函数库(oci)
2015/06/26 PHP
PHP单例模式定义与使用实例详解
2017/02/06 PHP
PHP中让json_encode不自动转义斜杠“/”的方法
2017/02/28 PHP
PHP设计模式之模板方法模式实例浅析
2018/12/20 PHP
javascript &amp;&amp;和||运算法的另类使用技巧
2009/11/28 Javascript
javascript encodeURI和encodeURIComponent的比较
2010/04/03 Javascript
Javascript中valueOf与toString区别浅析
2013/03/19 Javascript
JS的参数传递示例介绍
2014/02/08 Javascript
JavaScript字符串对象split方法入门实例(用于把字符串分割成数组)
2014/10/16 Javascript
js 通过cookie实现刷新不变化树形菜单
2014/10/30 Javascript
JS中的Replace方法使用经验分享
2015/05/20 Javascript
JS日期对象简单操作(获取当前年份、星期、时间)
2016/10/26 Javascript
详解JavaScript中数组的reduce方法
2016/12/02 Javascript
jQuery遍历节点方法汇总(推荐)
2017/05/13 jQuery
vue-prop父组件向子组件进行传值的方法
2018/03/01 Javascript
layui实现table加载的示例代码
2018/08/14 Javascript
vue实现div拖拽互换位置
2020/07/29 Javascript
layui实现数据分页功能
2019/07/27 Javascript
用smtplib和email封装python发送邮件模块类分享
2014/02/17 Python
Python遍历目录中的所有文件的方法
2016/07/08 Python
解决tensorflow测试模型时NotFoundError错误的问题
2018/07/27 Python
Python3自动签到 定时任务 判断节假日的实例
2018/11/13 Python
pygame游戏之旅 调用按钮实现游戏开始功能
2018/11/21 Python
Python创建字典的八种方式
2019/02/27 Python
对Python强大的可变参数传递机制详解
2019/06/13 Python
python实现三种随机请求头方式
2021/01/05 Python
爱游人:Travelliker
2017/09/05 全球购物
雅诗兰黛澳大利亚官网:Estée Lauder澳大利亚
2019/05/31 全球购物
测控技术与通信工程毕业生自荐信范文
2013/12/28 职场文书
运动会解说词200字
2014/02/06 职场文书
青春寄语大全
2014/04/09 职场文书
在职员工证明书
2014/09/19 职场文书
建国大业观后感
2015/06/01 职场文书
中学生打架检讨书之500字
2019/08/06 职场文书