目录
Hi, {{g.user.nickname}}!
首页 后端开发 Python教程 Python的Flask框架中实现分页功能的教程

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

Jun 06, 2016 am 11:25 AM
python

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 id="Hi-g-user-nickname">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: 返回。

    当我们在第一页的时候,我们不想显示也是如此。


    实现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 %}<< Newer posts{% else %}<< Newer posts{% endif %} |
    {% if posts.has_next %}Older posts >>{% else %}Older posts >>{% endif %}
    
    登录后复制

    结束语

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

    下载:microblog-0.9.zip.

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

vs code 可以在 Windows 8 中运行吗 vs code 可以在 Windows 8 中运行吗 Apr 15, 2025 pm 07:24 PM

VS Code可以在Windows 8上运行,但体验可能不佳。首先确保系统已更新到最新补丁,然后下载与系统架构匹配的VS Code安装包,按照提示安装。安装后,注意某些扩展程序可能与Windows 8不兼容,需要寻找替代扩展或在虚拟机中使用更新的Windows系统。安装必要的扩展,检查是否正常工作。尽管VS Code在Windows 8上可行,但建议升级到更新的Windows系统以获得更好的开发体验和安全保障。

vscode 扩展是否是恶意的 vscode 扩展是否是恶意的 Apr 15, 2025 pm 07:57 PM

VS Code 扩展存在恶意风险,例如隐藏恶意代码、利用漏洞、伪装成合法扩展。识别恶意扩展的方法包括:检查发布者、阅读评论、检查代码、谨慎安装。安全措施还包括:安全意识、良好习惯、定期更新和杀毒软件。

vscode怎么在终端运行程序 vscode怎么在终端运行程序 Apr 15, 2025 pm 06:42 PM

在 VS Code 中,可以通过以下步骤在终端运行程序:准备代码和打开集成终端确保代码目录与终端工作目录一致根据编程语言选择运行命令(如 Python 的 python your_file_name.py)检查是否成功运行并解决错误利用调试器提升调试效率

在PHP和Python之间进行选择:指南 在PHP和Python之间进行选择:指南 Apr 18, 2025 am 12:24 AM

PHP适合网页开发和快速原型开发,Python适用于数据科学和机器学习。1.PHP用于动态网页开发,语法简单,适合快速开发。2.Python语法简洁,适用于多领域,库生态系统强大。

visual studio code 可以用于 python 吗 visual studio code 可以用于 python 吗 Apr 15, 2025 pm 08:18 PM

VS Code 可用于编写 Python,并提供许多功能,使其成为开发 Python 应用程序的理想工具。它允许用户:安装 Python 扩展,以获得代码补全、语法高亮和调试等功能。使用调试器逐步跟踪代码,查找和修复错误。集成 Git,进行版本控制。使用代码格式化工具,保持代码一致性。使用 Linting 工具,提前发现潜在问题。

PHP和Python:解释了不同的范例 PHP和Python:解释了不同的范例 Apr 18, 2025 am 12:26 AM

PHP主要是过程式编程,但也支持面向对象编程(OOP);Python支持多种范式,包括OOP、函数式和过程式编程。PHP适合web开发,Python适用于多种应用,如数据分析和机器学习。

vscode 可以用于 mac 吗 vscode 可以用于 mac 吗 Apr 15, 2025 pm 07:36 PM

VS Code 可以在 Mac 上使用。它具有强大的扩展功能、Git 集成、终端和调试器,同时还提供了丰富的设置选项。但是,对于特别大型项目或专业性较强的开发,VS Code 可能会有性能或功能限制。

vscode 可以运行 ipynb 吗 vscode 可以运行 ipynb 吗 Apr 15, 2025 pm 07:30 PM

VS Code 运行 Jupyter Notebook 的关键是要确保 Python 环境正确配置,理解代码执行顺序与单元格顺序一致,并注意可能影响性能的大型文件或外部库。VS Code 提供的代码补全和调试功能可以大大提高编码效率和减少错误。

See all articles