如何在Flask-SQLAlchemy中使用一对多数据库关系

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。

介绍

Flask 是一个轻量级的 Python Web 框架,它为使用 Python 语言创建 Web 应用程序提供了有用的工具和功能。 SQLAlchemy 是一个 SQL 工具包,为关系数据库提供高效和高性能的数据库访问。 它提供了与 SQLite、MySQL 和 PostgreSQL 等多个数据库引擎交互的方法。 它使您可以访问数据库的 SQL 功能。 它还为您提供了一个对象关系映射器 (ORM),它允许您使用简单的 Python 对象和方法进行查询和处理数据。 Flask-SQLAlchemy 是一个 Flask 扩展,它可以更轻松地将 SQLAlchemy 与 Flask 一起使用,为您提供通过 SQLAlchemy 在 Flask 应用程序中与数据库进行交互的工具和方法。

一对多数据库关系 是两个数据库表之间的关系,其中一个表中的一条记录可以引用另一个表中的多条记录。 例如,在博客应用程序中,用于存储帖子的表可以与用于存储评论的表具有一对多的关系。 每个帖子可以引用多条评论,每条评论引用一个帖子; 因此,一个帖子与许多评论有关系。 post表是父表,comments表是子表——父表中的一条记录可以引用子表中的许多记录。 这种关系对于能够访问每个表中的相关数据很重要。

在本教程中,您将构建一个小型博客系统,演示如何使用 Flask-SQLAlchemy 扩展构建一对多关系。 您将在帖子和评论之间创建关系,其中每个博客帖子可以有多个评论。

先决条件

第 1 步 — 安装 Flask 和 Flask-SQLAlchemy

在此步骤中,您将为您的应用程序安装必要的包。

激活虚拟环境后,使用 pip 安装 Flask 和 Flask-SQLAlchemy:

pip install Flask Flask-SQLAlchemy

安装成功完成后,您将在输出末尾看到类似于以下内容的行:

OutputSuccessfully installed Flask-2.1.1 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.1 MarkupSafe-2.1.1 SQLAlchemy-1.4.35 Werkzeug-2.1.1 click-8.1.2 greenlet-1.1.2 itsdangerous-2.1.2

安装所需的 Python 包后,接下来您将设置数据库。

第 2 步 — 设置数据库和模型

在这一步中,您将设置数据库,并创建 SQLAlchemy 数据库模型 — 代表数据库表的 Python 类。 您将为您的博客文章创建一个模型,并为评论创建一个模型。 您将启动数据库,为帖子创建一个表,并根据您将声明的模型添加一个评论表。 您还将在数据库中插入一些帖子和评论。

设置数据库连接

flask_app 目录中打开一个名为 app.py 的文件。 该文件将包含用于设置数据库和 Flask 路由的代码:

nano app.py

该文件将连接到一个名为 database.db 的 SQLite 数据库,并将有两个类:一个名为 Post 的类表示您的数据库帖子表,一个 Comment 类表示评论桌子。 该文件还将包含您的 Flask 路由。 在 app.py 的顶部添加以下 import 语句:

烧瓶应用程序/app.py

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

在这里,您导入 os 模块,它使您可以访问各种操作系统接口。 您将使用它为 database.db 数据库文件构建文件路径。

然后从 flask 包中导入应用程序所需的必要帮助程序:创建 Flask 应用程序实例的 Flask 类,渲染模板的 render_template() 函数, request 对象用于处理请求,url_for() 函数用于构造路由的 URL,redirect() 函数用于重定向用户。 有关路由和模板的更多信息,请参阅 如何在 Flask 应用程序中使用模板

然后,您从 Flask-SQLAlchemy 扩展中导入 SQLAlchemy 类,这使您可以访问 SQLAlchemy 中的所有函数和类,以及帮助程序以及将 Flask 与 SQLAlchemy 集成的功能。 您将使用它来创建连接到 Flask 应用程序的数据库对象,允许您使用 Python 类、对象和函数创建和操作表,而无需使用 SQL 语言。

在导入下方,您将设置一个数据库文件路径,实例化您的 Flask 应用程序,并使用 SQLAlchemy 配置和连接您的应用程序。 添加以下代码:

烧瓶应用程序/app.py

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)

在这里,您构建 SQLite 数据库文件的路径。 您首先将基本目录定义为当前目录。 您使用 os.path.abspath() 函数来获取当前文件目录的绝对路径。 特殊的 __file__ 变量保存当前 app.py 文件的路径名。 您将基本目录的绝对路径存储在名为 basedir 的变量中。

然后创建一个名为 app 的 Flask 应用程序实例,用于配置两个 Flask-SQLAlchemy 配置键

  • SQLALCHEMY_DATABASE_URI:指定要与之建立连接的数据库的数据库 URI。 在这种情况下,URI 遵循 sqlite:///path/to/database.db 格式。 您使用 os.path.join() 函数智能地连接您构建并存储在 basedir 变量中的基本目录和 database.db 文件名。 这将连接到 flask_app 目录中的 database.db 数据库文件。 启动数据库后,将创建该文件。
  • SQLALCHEMY_TRACK_MODIFICATIONS:启用或禁用跟踪对象修改的配置。 您将其设置为 False 以禁用跟踪并使用更少的内存。 有关更多信息,请参阅 Flask-SQLAlchemy 文档中的 配置页面

注意: 如果您想使用其他数据库引擎,例如 PostgreSQL 或 MySQL,您需要使用正确的 URI。

对于 PostgreSQL,使用以下格式:

postgresql://username:password@host:port/database_name

对于 MySQL:

mysql://username:password@host:port/database_name

有关更多信息,请参阅引擎配置 SQLAlchemy 文档。


通过设置数据库 URI 并禁用跟踪来配置 SQLAlchemy 后,您使用 SQLAlchemy 类创建数据库对象,传递应用程序实例以将 Flask 应用程序与 SQLAlchemy 连接。 您将数据库对象存储在一个名为 db 的变量中。 您将使用此 db 对象与您的数据库进行交互。

声明表格

建立数据库连接并创建数据库对象后,您将使用数据库对象为帖子和评论创建一个数据库表。 表由 model 表示 — 一个 Python 类,它继承自 Flask-SQLAlchemy 通过您之前创建的 db 数据库实例提供的基类。 要将帖子和评论表定义为模型,请将以下两个类添加到您的 app.py 文件中:

烧瓶应用程序/app.py

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'

在这里,您创建一个 Post 模型和一个 Comment 模型,它们继承自 db.Model 类。

Post 模型表示后表。 您使用 db.Column 类来定义它的列。 第一个参数表示列类型,其他参数表示列配置。

您为 Post 模型定义以下列:

  • id:帖子ID。 您可以使用 db.Integer 将其定义为整数。 primary_key=True 将此列定义为 主键 ,它将由数据库为每个条目(即每个帖子)分配一个唯一值。
  • title:帖子的标题。 最大长度为 100 个字符的字符串。
  • content:帖子的内容。 db.Text 表示该列包含长文本。

comments 类属性定义了 Post 模型和 Comment 模型之间的一对多关系。 您使用 db.relationship() 方法,将评论模型的名称传递给它(在本例中为 Comment)。 您使用 backref 参数向 Comment 模型添加行为类似于列的反向引用。 这样,您可以使用 post 属性访问发表评论的帖子。 例如,如果您在名为 comment 的变量中有一个评论对象,您将能够使用 comment.post 访问评论所属的帖子。 稍后您将看到一个示例来演示这一点。

请参阅 SQLAlchemy 文档,了解与您在前面代码块中使用的类型不同的列类型。

特殊的 __repr__ 函数允许您为每个对象提供一个字符串表示,以便在调试时识别它。

Comment 模型代表注释表。 您为它定义以下列:

  • id:评论ID。 您可以使用 db.Integer 将其定义为整数。 primary_key=True 将此列定义为 主键 ,它将由数据库为每个条目(即每个评论)分配一个唯一值。
  • content:评论的内容。 db.Text 表示该列包含长文本。
  • post_id:使用 db.ForeignKey() 类构造的整数 外键 ,它是使用该表的主键将表与另一个表链接的键。 这使用帖子的主键(即其 ID)将评论链接到帖子。 这里的post表是父表,表示每个帖子有很多评论。 comment 表是 子表。 每条评论都使用帖子的 ID 与父帖子相关。 因此,每条评论都有一个 post_id 列,可用于访问发布评论的帖子。

Comment 模型中的特殊 __repr__ 函数显示评论内容的前 20 个字符,为评论对象提供一个短字符串表示。

app.py 文件现在如下所示:

烧瓶应用程序/app.py

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'

保存并关闭 app.py

创建数据库

现在您已经设置了数据库连接以及帖子和评论模型,您将使用 Flask shell 根据您声明的模型创建数据库以及帖子和评论表。

激活虚拟环境后,使用 FLASK_APP 环境变量将 app.py 文件设置为 Flask 应用程序:

export FLASK_APP=app

然后在 flask_app 目录中使用以下命令打开 Flask shell:

flask shell

将打开一个 Python 交互式 shell。 这个特殊的 shell 在您的 Flask 应用程序的上下文中运行命令,以便您将调用的 Flask-SQLAlchemy 函数连接到您的应用程序。

导入数据库对象以及帖子和评论模型,然后运行 db.create_all() 函数来创建与您的模型关联的表:

from app import db, Post, Comment
db.create_all()

让 shell 继续运行,打开另一个终端窗口并导航到您的 flask_app 目录。 您现在将在 flask_app 中看到一个名为 database.db 的新文件。

注意: db.create_all() 函数不会重新创建或更新已存在的表。 例如,如果您通过添加新列来修改模型,并运行 db.create_all() 函数,如果表已存在于数据库中,则您对模型所做的更改将不会应用于表。 解决方案是使用 db.drop_all() 函数删除所有现有的数据库表,然后使用 db.create_all() 函数重新创建它们,如下所示:

db.drop_all()
db.create_all()

这将应用您对模型所做的修改,但也会删除数据库中的所有现有数据。 要更新数据库结构并保留现有数据,您需要使用 schema migration,它允许您修改表并保留数据。 您可以使用 Flask-Migrate 扩展通过 Flask 命令行界面执行 SQLAlchemy 模式迁移。


如果您收到错误,请确保您的数据库 URI 和模型声明正确。

填充表

创建数据库以及帖子和评论表后,您将在 flask_app 目录中创建一个文件,以将一些帖子和评论添加到您的数据库中。

打开一个名为 init_db.py 的新文件:

nano init_db.py

将以下代码添加到其中。 该文件将创建三个帖子对象和四个评论对象,并将它们添加到数据库中:

烧瓶应用程序/init_db.py

from app import db, Post, Comment

post1 = Post(title='Post The First', content='Content for the first post')
post2 = Post(title='Post The Second', content='Content for the Second post')
post3 = Post(title='Post The Third', content='Content for the third post')

comment1 = Comment(content='Comment for the first post', post=post1)
comment2 = Comment(content='Comment for the second post', post=post2)
comment3 = Comment(content='Another comment for the second post', post_id=2)
comment4 = Comment(content='Another comment for the first post', post_id=1)


db.session.add_all([post1, post2, post3])
db.session.add_all([comment1, comment2, comment3, comment4])

db.session.commit()

保存并关闭文件。

在这里,您从 app.py 文件导入数据库对象、Post 模型和 Comment 模型。

您使用 Post 模型创建了一些帖子对象,将帖子的标题传递给 title 参数,并将帖子的内容传递给 content 参数。

然后创建一些评论对象,传递评论的内容。 您可以使用两种方法将评论与其所属的帖子相关联。 您可以将 post 对象传递给 post 参数,如 comment1comment2 对象中所示。 您还可以将帖子 ID 传递给 post_id 参数,如 comment3comment4 对象中所示。 因此,如果您的代码中没有帖子对象,您可以只传递帖子的整数 ID。

定义帖子和评论对象后,使用 db.session.add_all() 将所有帖子和评论对象添加到管理事务的数据库会话中。 然后使用 db.session.commit() 方法提交事务并将更改应用到数据库。 有关 SQLAlchemy 数据库会话的更多信息,请参阅 如何在 Flask 应用程序中使用 Flask-SQLAlchemy 与数据库进行交互 教程的第 2 步。

运行init_db.py文件执行代码,将数据添加到数据库中:

python init_db.py

要查看您添加到数据库中的数据,请打开烧瓶外壳以查询所有帖子并显示它们的标题和每个帖子的评论内容:

flask shell

运行以下代码。 这将查询所有帖子并显示每个帖子标题及其下方每个帖子的评论:

from app import Post

posts = Post.query.all()

for post in posts:
    print(f'## {post.title}')
    for comment in post.comments:
            print(f'> {comment.content}')
    print('----')

在这里,您从 app.py 文件中导入 Post 模型。 您使用 query 属性上的 all() 方法查询数据库中存在的所有帖子,并将结果保存在名为 posts 的变量中。 然后使用 for 循环遍历 posts 变量中的每个项目。 您打印标题,然后使用另一个 for 循环遍历属于该帖子的每个评论。 您可以使用 post.comments 访问帖子的评论。 您打印评论的内容,然后打印字符串 '----' 以分隔帖子。

您将获得以下输出:

Output
## Post The First
> Comment for the first post
> Another comment for the first post
----
## Post The Second
> Comment for the second post
> Another comment for the second post
----
## Post The Third
----

如您所见,您只需很少的代码即可访问每个帖子的数据和每个帖子的评论。

现在退出外壳:

exit()

此时,您的数据库中有多个帖子和评论。 接下来,您将为索引页面创建一个 Flask 路由,并在其上显示数据库中的所有帖子。

第 3 步 — 显示所有帖子

在这一步中,您将创建一个路由和一个模板,以在索引页面上显示数据库中的所有帖子。

打开您的 app.py 文件以向其中添加索引页面的路由:

nano app.py

在文件末尾添加以下路由:

烧瓶应用程序/app.py

# ...

@app.route('/')
def index():
    posts = Post.query.all()
    return render_template('index.html', posts=posts)

保存并关闭文件。

在这里,您使用 app.route() 装饰器创建了一个 index() 视图函数。 在此函数中,您查询数据库并像上一步一样获取所有帖子。 您将查询结果存储在一个名为 posts 的变量中,然后将其传递给您使用 render_template() 辅助函数呈现的 index.html 模板文件。

在创建 index.html 模板文件之前,您将在其中显示数据库中的现有帖子,您将首先创建一个基本模板,该模板将包含其他模板也将使用的所有基本 HTML 代码以避免代码重复。 然后,您将创建在 index() 函数中呈现的 index.html 模板文件。 要了解有关模板的更多信息,请参阅 如何在 Flask 应用程序中使用模板

创建一个 templates 目录,然后打开一个名为 base.html 的新模板:

mkdir templates
nano templates/base.html

base.html 文件中添加以下代码:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        .title {
            margin: 5px;
        }

        .content {
            margin: 5px;
            width: 100%;
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
        }

        .comment {
            padding: 10px;
            margin: 10px;
            background-color: #fff;
        }

        .post {
            flex: 20%;
            padding: 10px;
            margin: 5px;
            background-color: #f3f3f3;
            inline-size: 100%;
        }

        .title a {
            color: #00a36f;
            text-decoration: none;
        }

        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">Comments</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

此基本模板包含您需要在其他模板中重用的所有 HTML 样板。 title 块将被替换为每个页面设置标题,content 块将被替换为每个页面的内容。 导航栏有三个链接:一个用于索引页面,它使用 url_for() 辅助函数链接到 index() 视图函数,一个用于 Comments 页面,还有一个如果您选择向您的应用程序添加一个 About 页面。 在添加显示所有最新评论的页面以使 Comments 链接正常工作后,您稍后将编辑此文件。

接下来,打开一个新的 index.html 模板文件。 这是您在 app.py 文件中引用的模板:

nano templates/index.html

向其中添加以下代码:

flask_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Posts {% endblock %}</h1></span>
    <div class="content">
        {% for post in posts %}
            <div class="post">
                <p><b>#{{ post.id }}</b></p>
                <b>
                    <p class="title">
                        <a href="#">
                            {{ post.title }}
                        </a>
                    </p>
                </b>
                <div class="content">
                    <p>{{ post.content }}</p>
                </div>
                <hr>
            </div>
        {% endfor %}
    </div>
{% endblock %}

保存并关闭文件。

在这里,您扩展基本模板并替换内容块的内容。 您使用的 <h1> 标题也用作标题。 您在 {% for post in posts %} 行中使用 Jinja for loop 遍历从 index() 视图函数传递给此的 posts 变量中的每个帖子模板。 您显示帖子 ID、标题和帖子内容。 帖子标题稍后将链接到显示单个帖子及其评论的页面。

在激活虚拟环境的 flask_app 目录中,使用 FLASK_APP 环境变量告诉 Flask 应用程序(在本例中为 app.py)。 然后将 FLASK_ENV 环境变量设置为 development 以在开发模式下运行应用程序并访问调试器。 有关 Flask 调试器的更多信息,请参阅 如何处理 Flask 应用程序中的错误 。 使用以下命令执行此操作:

export FLASK_APP=app
export FLASK_ENV=development

接下来,运行应用程序:

flask run

在开发服务器运行的情况下,使用浏览器访问以下 URL:

http://127.0.0.1:5000/

您将在类似于以下内容的页面中看到您添加到数据库的帖子:

您已经在索引页面上显示了您在数据库中的帖子。 接下来,您将为帖子页面创建一个路由,您将在其中显示每个帖子的详细信息及其下方的评论。

第 4 步 — 显示单个帖子及其评论

在此步骤中,您将创建一个路由和一个模板,以在专用页面上显示每个帖子的详细信息,并在其下方显示帖子的评论。

在此步骤结束时,URL http://127.0.0.1:5000/1 将成为显示第一篇文章(因为它的 ID 为 1)及其评论的页面。 URL http://127.0.0.1:5000/ID 将显示带有相关 ID 编号的帖子(如果存在)。

让开发服务器保持运行并打开一个新的终端窗口。

打开app.py进行修改:

nano app.py

在文件末尾添加以下路由:

烧瓶应用程序/app.py

# ...

@app.route('/<int:post_id>/')
def post(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post.html', post=post)

保存并关闭文件。

在这里,您使用路由 '/<int:post_id>/',其中 int: 是一个 转换器 ,它将 URL 中的默认字符串转换为整数。 post_id 是 URL 变量,它将确定您将在页面上显示的帖子。

ID 通过 post_id 参数从 URL 传递到 post() 视图函数。 在函数内部,您使用 get_or_404() 方法查询 post 表并通过其 ID 检索帖子。 如果存在,这会将帖子数据保存在 post 变量中,如果数据库中不存在具有给定 ID 的帖子,则会以 404 Not Found HTTP 错误响应。

您渲染一个名为 post.html 的模板并将其传递给您检索到的帖子。

打开这个新的 post.html 模板文件:

nano templates/post.html

在其中键入以下代码。 这将类似于 index.html 模板,除了它只会显示一个帖子:

flask_app/templates/post.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} {{ post.title }}  {% endblock %}</h1></span>
    <div class="content">
            <div class="post">
                <p><b>#{{ post.id }}</b></p>
                <b>
                    <p class="title">{{ post.title }}</p>
                </b>
                <div class="content">
                    <p>{{ post.content }}</p>
                </div>
                <hr>
                <h3>Comments</h3>
                {% for comment in post.comments %}
                    <div class="comment">
                        <p>#{{ comment.id }}</p>
                        <p>{{ comment.content }}</p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

保存并关闭文件。

在这里,您扩展基本模板,将帖子标题设置为页面标题,显示帖子 ID、帖子标题和帖子内容。 然后,您通过 post.comments 浏览可用的帖子评论。 您显示评论 ID 和评论的内容。

使用浏览器导航到第二篇文章的 URL:

http://127.0.0.1:5000/2/

您将看到类似于以下内容的页面:

接下来,编辑 index.html 以使帖子的标题链接到单个帖子:

nano templates/index.html

for 循环内编辑帖子标题链接的 href 属性值:

flask_app/templates/index.html

...

{% for post in posts %}
    <div class="post">
        <p><b>#{{ post.id }}</b></p>
        <b>
            <p class="title">
                <a href="{{ url_for('post', post_id=post.id)}}">
                {{ post.title }}
                </a>
            </p>
        </b>
        <div class="content">
            <p>{{ post.content }}</p>
        </div>
        <hr>
    </div>
{% endfor %}

保存并关闭文件。

导航到您的索引页面或刷新它:

http://127.0.0.1:5000/

单击索引页面上的每个帖子标题。 您现在将看到每个帖子都链接到正确的帖子页面。

您现在已经创建了一个用于显示单个帖子的页面。 接下来,您将向帖子页面添加一个 Web 表单,以允许用户添加新评论。

第 5 步 — 添加新评论

在此步骤中,您将编辑 /<int:post_id>/ 路由及其 post() 视图函数,该函数处理显示单个帖子。 您将在每个帖子下方添加一个 Web 表单,以允许用户向该帖子添加评论,然后您将处理评论提交并将其添加到数据库中。

首先,打开 post.html 模板文件,添加一个 Web 表单,该表单由一个用于评论内容的文本区域和一个 Add Comment 提交按钮组成。

nano templates/post.html

通过在 Comments H3 标题下方和 for 循环正上方添加一个表单来编辑文件:

flask_app/templates/post.html

<hr>
<h3>Comments</h3>
<form method="post">
    <p>
        <textarea name="content"
                    placeholder="Comment"
                    cols="60"
                    rows="5"></textarea>
    </p>

    <p>
        <button type="submit">Add comment</button>
    </p>
</form>
{% for comment in post.comments %}

保存并关闭文件。

在这里,您添加一个 <form> 标记,其属性 method 设置为 post 以指示表单将提交 POST 请求。

您有一个评论内容的文本区域和一个提交按钮。

在开发服务器运行的情况下,使用浏览器导航到帖子:

http://127.0.0.1:5000/2/

您将看到类似于以下内容的页面:

这个表单向post()视图函数发送了一个POST请求,但是因为没有处理表单提交的代码,所以表单目前不起作用。

接下来,您将向 post() 视图函数添加代码以处理表单提交并将新评论添加到数据库。 打开 app.py 处理用户提交的 POST 请求:

nano app.py

编辑 /<int:post_id>/ 路由及其 post() 视图函数,如下所示:

烧瓶应用程序/app.py

@app.route('/<int:post_id>/', methods=('GET', 'POST'))
def post(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        comment = Comment(content=request.form['content'], post=post)
        db.session.add(comment)
        db.session.commit()
        return redirect(url_for('post', post_id=post.id))

    return render_template('post.html', post=post)

保存并关闭文件。

您允许使用 methods 参数的 GET 和 POST 请求。 GET 请求用于从服务器检索数据。 POST 请求用于将数据发布到特定路由。 默认情况下,只允许 GET 请求。

if request.method == 'POST' 条件中,您处理用户将通过表单提交的 POST 请求。 您使用 Comment 模型创建一个评论对象,将您从 request.form 对象中提取的提交评论的内容传递给它。 您可以使用 post 参数指定评论所属的帖子,并使用 get_or_404() 方法将您使用帖子 ID 检索到的 post 对象传递给它。

您将构建的评论对象添加到数据库会话中,提交事务,然后重定向到帖子页面。

现在刷新浏览器上的帖子页面,写评论,然后提交。 您会在帖子下方看到您的新评论。

您现在拥有一个允许用户向帖子添加评论的 Web 表单。 有关 Web 表单的更多信息,请参阅 如何在 Flask 应用程序中使用 Web 表单。 有关管理 Web 表单的更高级和更安全的方法,请参阅 How To Use and Validate Web Forms with Flask-WTF。 接下来,您将添加一个页面,该页面显示数据库中的所有评论以及发布这些评论的帖子。

第 6 步 — 显示所有评论

在这一步中,您将添加一个 Comments 页面,您将在其中显示数据库中的所有评论,并通过首先显示最新评论来对它们进行排序。 每条评论都将具有发布评论的帖子的标题和链接。

打开app.py

nano app.py

将以下路由添加到文件末尾。 这将获取数据库中的所有评论,按最新的优先排序。 然后它将它们传递给一个名为 comments.html 的模板文件,您稍后将创建该文件:

烧瓶应用程序/app.py

# ...

@app.route('/comments/')
def comments():
    comments = Comment.query.order_by(Comment.id.desc()).all()
    return render_template('comments.html', comments=comments)

保存并关闭文件。

您使用 query 属性上的 order_by() 方法以特定顺序获取所有评论。 在这种情况下,您使用 Comment.id 列上的 desc() 方法按降序获取评论,最新评论排在第一位。 然后使用 all() 方法获取结果并将其保存到名为 comments 的变量中。

您渲染一个名为 comments.html 的模板,将 comments 对象传递给它,该对象包含所有按最新排序的注释。

打开这个新的 comments.html 模板文件:

nano templates/comments.html

在其中键入以下代码。 这将显示评论并链接到他们所属的帖子:

flask_app/templates/comments.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Latest Comments {% endblock %}</h1></span>
    <div class="content">
                {% for comment in comments %}
                    <div class="comment">
                        <i>
                            (#{{ comment.id }})
                            <p>{{ comment.content }}</p>
                        </i>
                        <p class="title">
                        On <a href="{{ url_for('post',
                                                post_id=comment.post.id) }}">
                                {{ comment.post.title }}
                              </a>
                        </p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

保存并关闭文件。

在这里,您扩展基本模板,设置标题,并使用 for 循环浏览注释。 您将显示评论的 ID、其内容以及指向它所属帖子的链接。 您可以通过 comment.post 访问发布数据。

使用浏览器导航到评论页面:

http://127.0.0.1:5000/comments/

您将看到类似于以下内容的页面:

现在编辑 base.html 模板,使 Comments 导航栏链接指向此评论页面:

nano templates/base.html

编辑导航栏,如下所示:

flask_app/templates/base.html

    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('comments') }}">Comments</a>
        <a href="#">About</a>
    </nav>

保存并关闭文件。

刷新您的评论页面,您会看到 Comments 导航栏链接有效。

您现在有一个显示数据库中所有评论的页面。 接下来,您将在帖子页面上的每条评论下方添加一个按钮,以允许用户将其删除。

第 7 步 — 删除评论

在此步骤中,您将在每个评论下方添加一个 删除评论 按钮,以允许用户删除不需要的评论。

首先,您将添加一个接受 POST 请求的新 /comments/ID/delete 路由。 查看功能将接收您要删除的评论的 ID,从数据库中获取它,将其删除,然后重定向到已删除评论所在的发布页面。

打开app.py

nano app.py

将以下路由添加到文件末尾。

烧瓶应用程序/app.py

# ...

@app.post('/comments/<int:comment_id>/delete')
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    post_id = comment.post.id
    db.session.delete(comment)
    db.session.commit()
    return redirect(url_for('post', post_id=post_id))

保存并关闭文件。

在这里,不是使用通常的 app.route 装饰器,而是使用 Flask 版本 2.0.0 中引入的 app.post 装饰器,它为常见的 HTTP 方法添加了快捷方式。 例如,@app.post("/login")@app.route("/login", methods=["POST"]) 的快捷方式。 这意味着这个视图函数只接受 POST 请求,并且在浏览器上导航到 /comments/ID/delete 路由将返回 405 Method Not Allowed 错误,因为 Web 浏览器默认使用 GET 请求。 要删除评论,用户单击向该路由发送 POST 请求的按钮。

这个 delete_comment() 视图函数通过 comment_id URL 变量接收要删除的评论的 ID。 您使用 get_or_404() 方法获取注释并将其保存在 comment 变量中,或者在注释不存在的情况下以 404 Not Found 响应。 您将评论所属帖子的帖子 ID 保存在 post_id 变量中,您将在删除评论后使用该变量重定向到帖子。

您在 db.session.delete(comment) 行中的数据库会话上使用 delete() 方法,将注释对象传递给它。 这会将会话设置为在提交事务时删除评论。 因为您不需要执行任何其他修改,所以您直接使用 db.session.commit() 提交事务。 最后,您将用户重定向到发布了已删除评论的帖子。

接下来,编辑 post.html 模板,在每个评论下方添加一个 Delete Comment 按钮:

nano templates/post.html

通过在评论内容正下方添加一个新的 <form> 标签来编辑 for 循环:

flask_app/templates/post.html

    {% for comment in post.comments %}
        <div class="comment">
            <p>#{{ comment.id }}</p>
            <p>{{ comment.content }}</p>
            <form method="POST"
                action="{{ url_for('delete_comment',
                                    comment_id=comment.id) }}">
                <input type="submit" value="Delete Comment"
                    onclick="return confirm('Are you sure you want to delete this entry?')">
            </form>
        </div>
    {% endfor %}

保存并关闭文件。

在这里,您有一个向 delete_comment() 视图函数提交 POST 请求的 Web 表单。 您将 comment.id 作为参数传递给 comment_id 参数以指定将被删除的注释。 您可以使用 Web 浏览器中可用的 confirm() 方法 函数在提交请求之前显示确认消息。

现在导航到浏览器上的帖子页面:

http://127.0.0.1:5000/2/

您会在每条评论下方看到一个 删除评论 按钮。 单击它,然后确认删除。 您会看到评论已被删除。

您现在可以从数据库中删除评论。

结论

您构建了一个小型博客系统,演示了如何使用 Flask-SQLAlchemy 扩展来管理一对多关系。 您学习了如何将父表与子表连接、将子对象与其父对象关联并将其添加到数据库中,以及如何从父条目访问子数据,反之亦然。

如果您想了解更多关于 Flask 的信息,请查看 如何使用 Flask 构建 Web 应用程序系列中的其他教程。