如何在Flask应用程序中使用Flask-SQLAlchemy与数据库交互

作者选择了自由开源基金作为为捐赠写作计划的受赠方。

介绍

在网络应用中,通常需要一个数据库,它是一组组织良好的数据。您使用数据库存储和维护可以高效检索和操作的持久数据。例如,在社交媒体应用中,您有一个数据库,其中存储了用户数据(个人信息、帖子、评论、关注者),这些数据以可以高效操作的方式存储。您可以根据不同的要求和条件向数据库中添加数据、检索数据、修改数据或删除数据。在网络应用中,这些需求可能是用户添加新帖子、删除帖子或删除他们的帐户,这可能会或可能不会删除他们的帖子。您执行的操作取决于应用程序中的具体特性。例如,您可能不希望用户添加没有标题的帖子。

使用DigitalOcean 应用平台从 GitHub 部署您的 Flask 应用。让 DigitalOcean 关注您的应用程序扩展。

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

在本教程中,您将构建一个小型的学生管理系统,演示如何使用 Flask-SQLAlchemy 扩展。您将与 Flask 一起使用它来执行基本任务,如连接到数据库服务器,创建表,向表中添加数据,检索数据,并更新和删除数据库中的项目。您将与SQLite一起使用 SQLAlchemy,尽管您也可以与其他数据库引擎一起使用,例如 PostgreSQL 和 MySQL。SQLite 与 Python 配合良好,因为 Python 标准库提供了sqlite3模块,SQLAlchemy 在后台使用它与 SQLite 数据库进行交互,而无需安装任何东西。在 Linux 系统上,默认情况下会安装 SQlite,在 Windows 上作为 Python 包的一部分安装。

先决条件

  • 本地Python 3编程环境。按照你的发行版在《如何安装和设置本地Python 3编程环境》系列教程中进行操作。在本教程中,我们将把项目目录称为flask_app

  • 对基本的Flask概念有一定的了解,比如路由、视图函数和模板。如果你对Flask不熟悉,可以查看《如何使用Flask和Python创建你的第一个Web应用程序》和《如何在Flask应用程序中使用模板》。

  • 了解基本的HTML概念。您可以查看我们的HTML构建网站教程系列以获取背景知识。

步骤1 — 安装Flask和Flask-SQLAlchemy

在这一步中,您将安装应用程序所需的软件包。

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

  1. pip install Flask Flask-SQLAlchemy

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

Output
Successfully installed Flask-2.0.3 Flask-SQLAlchemy-2.5.1 Jinja2-3.0.3 MarkupSafe-2.1.0 SQLAlchemy-1.4.31 Werkzeug-2.0.3 click-8.0.4 greenlet-1.1.2 itsdangerous-2.1.0

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

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

在这一步中,您将设置数据库连接,并创建一个SQLAlchemy数据库模型,这是一个表示存储数据的表的Python类。您将初始化数据库,根据您声明的模型创建一个学生表,并将一些学生添加到您的学生表中。设置数据库连接打开名为app.py的文件,该文件位于flask_app目录中。此文件将包含用于设置数据库和您的Flask路由的代码:此文件将连接到名为database.db的SQLite数据库,并具有一个名为Student的类,该类表示用于存储学生信息的数据库学生表,除了您的Flask路由。在app.py的顶部添加以下import语句:在这里,您导入os模块,它为您提供了访问杂项操作系统接口的权限。您将使用它来构造database.db数据库文件的文件路径。

设置数据库连接

  1. nano app.py
flask_app/app.py
import os
from flask import Flask, render_template, request, url_for, redirect
from flask_sqlalchemy import SQLAlchemy

from sqlalchemy.sql import func

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

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

您还需要从 sqlalchemy.sql 模块中导入 func 帮助程序以访问 SQL 函数。在您的学生管理系统中,您将需要它来为创建学生记录时设置默认的创建日期和时间。

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

flask_app/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:///路径/到/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对象与数据库交互。

声明表

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

flask_app/app.py
# ...

class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)
    age = db.Column(db.Integer)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now())
    bio = db.Column(db.Text)

    def __repr__(self):
        return f'<Student {self.firstname}>'

在这里,您创建了一个Student模型,它继承自db.Model类。这代表学生表。您使用db.Column类来定义表的列。第一个参数表示列类型,而附加参数表示列配置。

您为Student模型定义了以下列:

  • id:学生ID。您将其定义为整数,使用db.Integerprimary_key=True将此列定义为主键,数据库将为每个条目(即学生)分配一个唯一值。
  • firstname:学生的名字。一个最大长度为100个字符的字符串。 nullable=False表示此列不应为空。
  • lastname:学生的姓氏。一个最大长度为100个字符的字符串。 nullable=False表示此列不应为空。
  • email:学生的电子邮件。一个最大长度为80个字符的字符串。 unique=True表示每个学生的电子邮件应该是唯一的。 nullable=False表示此列不应为空。
  • age:学生的年龄。
  • created_at:学生记录在数据库中创建的时间。您使用db.DateTime将其定义为Pythondatetime对象。timezone=True启用时区支持。server_default设置创建表时数据库中的默认值,因此默认值由数据库而不是模型处理。您将其传递给func.now()函数,该函数调用SQLnow() datetime函数。在SQLite中,创建学生表时它被呈现为CURRENT_TIMESTAMP
  • bio:学生的个人简介。db.Text()指示列保存长文本。

有关除您在前面代码块中使用的类型之外的列类型,请参阅SQLAlchemy文档

特殊的__repr__函数允许您为每个对象提供字符串表示以识别调试目的。在这种情况下,您使用学生的名字。

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

flask_app/app.py
import os
from flask import Flask, render_template, request, url_for, redirect
from flask_sqlalchemy import SQLAlchemy

from sqlalchemy.sql import func


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 Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)
    age = db.Column(db.Integer)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now())
    bio = db.Column(db.Text)

    def __repr__(self):
        return f'<Student {self.firstname}>'

保存并关闭app.py

创建数据库

现在你已经设置了数据库连接和学生模型,你将使用 Flask shell 来创建你的数据库和基于 Student 模型的学生表。

在激活虚拟环境后,将 app.py 文件设置为你的 Flask 应用,使用 FLASK_APP 环境变量。然后在你的 flask_app 目录中使用以下命令打开 Flask shell:

  1. export FLASK_APP=app
  2. flask shell

A Python interactive shell will be opened. This special shell runs commands in the context of your Flask application, so that the Flask-SQLAlchemy functions you’ll call are connected to your application.

导入数据库对象和学生模型,然后运行 db.create_all() 函数来创建与你的模型相关的表。在这种情况下,你只有一个模型,这意味着函数调用将只在你的数据库中创建一个表:

  1. from app import db, Student
  2. db.create_all()

保持 shell 运行状态,打开另一个终端窗口并导航到你的 flask_app 目录。你现在将在 flask_app 中看到一个名为 database.db 的新文件。

注意:

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

  1. db.drop_all()
  2. db.create_all()

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

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

填充表

创建数据库和学生表后,您将通过Student模型使用flask shell向数据库添加一些学生。

使用之前打开的相同flask shell,或者在您的flask_app目录中激活虚拟环境后打开一个新的。

  1. flask shell

要将学生添加到您的数据库中,您将导入数据库对象和Student模型,并创建Student模型的实例,通过关键字参数传递学生数据如下:

  1. from app import db, Student
  2. student_john = Student(firstname='john', lastname='doe',
  3. email='[email protected]', age=23,
  4. bio='Biology student')

student_john对象代表将要添加到数据库中的学生,但此对象尚未写入数据库。在flask shell中查看对象,以查看您使用__repr__()方法构造的表示字符串:

  1. student_john

您将收到以下输出:

Output
<Student john>

您可以使用Student模型中定义的类属性来获取列的值:

  1. student_john.firstname
  2. student_john.bio
Output
'john' 'Biology student'

因为此学生尚未添加到数据库,所以其ID将为None

  1. print(student_john.id)
Output
None

要将此学生添加到数据库中,您首先需要将其添加到数据库会话中,该会话管理数据库事务。 Flask-SQLAlchemy通过db.session对象提供给您,通过它可以管理数据库更改。使用db.session.add()方法将student_john对象添加到会话中,以准备写入数据库:

  1. db.session.add(student_john)

这将发出一个INSERT语句,但是您不会得到一个ID,因为数据库事务仍然没有提交。要提交事务并将更改应用到数据库,请使用db.session.commit()方法:

  1. db.session.commit()

现在John学生已添加到数据库中,您可以获取其ID:

  1. print(student_john.id)
Output
1

您还可以使用db.session.add()方法编辑数据库中的项目。例如,您可以这样修改学生的电子邮件:

  1. student_john.email = '[email protected]'
  2. db.session.add(student_john)
  3. db.session.commit()

使用 Flask shell 将更多学生添加到您的数据库中:

  1. sammy = Student(firstname='Sammy',
  2. lastname='Shark',
  3. email='[email protected]',
  4. age=20,
  5. bio='Marine biology student')
  6. carl = Student(firstname='Carl',
  7. lastname='White',
  8. email='[email protected]',
  9. age=22,
  10. bio='Marine geology student')
  11. db.session.add(sammy)
  12. db.session.add(carl)
  13. db.session.commit()

现在,您可以使用 query 属性和 all() 方法查询学生表中的所有记录:

  1. Student.query.all()

您将收到以下输出:

Output
[<Student john>, <Student Sammy>, <Student Carl>]

此时,您的数据库中有三名学生。接下来,您将为首页创建一个 Flask 路由,并在其中显示数据库中的所有学生。

步骤 3 — 显示所有记录

在此步骤中,您将创建一个路由和一个模板,在首页上显示数据库中的所有学生。

保持 Flask shell 运行,并打开一个新的终端窗口。

打开您的 app.py 文件,为首页添加一个路由:

  1. nano app.py

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

flask_app/app.py

# ...

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

保存并关闭文件。

在这里,您使用app.route()装饰器创建了一个index()视图函数。在这个函数中,您使用Student模型的query属性查询数据库并获取所有学生,该属性允许您使用不同的方法从数据库中检索一个或多个项目。您使用all()方法获取数据库中的所有学生条目。您将查询结果存储在一个名为students的变量中,并将其传递给一个名为index.html的模板,您可以使用render_template()助手函数渲染该模板。

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

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

  1. mkdir templates
  2. 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;
        }

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

        .bio {
            padding: 10px;
            margin: 5px;
            background-color: #ffffff;
            color: #004835;
        }

        .name 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="#">Create</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

这个基本模板包含了你在其他模板中需要重用的所有 HTML 样板。title 区块将被替换以设置每个页面的标题,content 区块将被替换为每个页面的内容。导航栏有三个链接:一个是指向主页的链接,它使用 url_for() 辅助函数链接到 index() 视图函数,一个是指向 创建 页面的链接,另一个是指向一个 关于 页面的链接,如果你选择在你的应用程序中添加这个页面的话。之后在你添加一个用于创建新学生页面的页面后,你将编辑这个文件以使 创建 链接生效。

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

  1. nano templates/index.html

将以下代码添加到其中:

flask_app/templates/index.html
{% extends 'base.html' %}

{% block content %}
    <h1 class="title">{% block title %} Students {% endblock %}</h1>
    <div class="content">
        {% for student in students %}
            <div class="student">
                <p><b>#{{ student.id }}</b></p>
                <b>
                    <p class="name">{{ student.firstname }} {{ student.lastname }}</p>
                </b>
                <p>{{ student.email }}</p>
                <p>{{ student.age }} years old.</p>
                <p>Joined: {{ student.created_at }}</p>
                <div class="bio">
                    <h4>Bio</h4>
                    <p>{{ student.bio }}</p>
                </div>
            </div>
        {% endfor %}
    </div>
{% endblock %}

保存并关闭文件。

在这里,你扩展了基本模板并替换了内容块的内容。你使用了一个同时充当标题的 <h1> 标题。在 {% for student in students %} 行中,你使用了一个 Jinja for 循环来遍历你从 index() 视图函数传递到这个模板中的 students 变量中的每个学生。你展示了学生的 ID、他们的名字和姓氏、邮箱、年龄、他们被添加到数据库的日期以及他们的个人简介。

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

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

接下来,运行应用程序:

  1. flask run

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

http://127.0.0.1:5000/

您将在类似以下页面中看到您添加到数据库的学生:

您已在索引页面上显示了您的数据库中的学生。接下来,您将创建一个用于学生页面的路由,您可以在该页面上显示每个学生的详细信息。

步骤 4 —— 显示单个记录

在此步骤中,您将使用 Flask shell 按其 ID 查询学生,并创建一个路由和一个模板,以在专用页面上显示每个学生的详细信息。

到这一步结束时,URL http://127.0.0.1:5000/1 将会显示第一个学生(因为其 ID 为 1)。URL http://127.0.0.1:5000/ID 如果存在相关的 ID 编号,将显示对应的帖子。

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

打开 Flask shell 进行演示如何查询学生:

  1. flask shell

要查询记录并从数据库中检索数据,Flask-SQLAlchemy 在模型类上提供了一个 query 属性。您可以使用它的方法来获取具有特定过滤器的记录。

例如,您可以使用 filter_by() 方法,参数为匹配表中某一列的 firstname,以检索特定的学生:

  1. from app import db, Student
  2. Student.query.filter_by(firstname='Sammy').all()
Output
[<Student Sammy>]

在这里,您检索所有名字为 Sammy 的学生。您使用 all() 方法获取所有结果的列表。要获取第一个结果,即此处的唯一结果,您可以使用 first() 方法:

  1. Student.query.filter_by(firstname='Sammy').first()
Output
<Student Sammy>

要通过其 ID 获取学生,您可以使用 filter_by(id=ID)

  1. Student.query.filter_by(id=3).first()

或者,您可以使用更简短的 get() 方法,它允许您使用主键检索特定项:

  1. Student.query.get(3)

两者都将提供相同的输出:

Output
<Student Carl>

现在您可以退出 shell 了:

  1. exit()

为了通过他们的学生ID检索学生,您将创建一个新的路由,为每个单独的学生呈现一个页面。您将使用Flask-SQLAlchemy提供的get_or_404()方法,这是get()方法的一种变体。区别在于,get()在没有结果与给定的ID匹配时返回值None,而get_or_404()返回404 Not Found HTTP响应。打开app.py进行修改:

  1. nano app.py

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

flask_app/app.py
# ...

@app.route('/<int:student_id>/')
def student(student_id):
    student = Student.query.get_or_404(student_id)
    return render_template('student.html', student=student)

保存并关闭文件。

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

ID从URL传递到student()视图函数中的student_id参数。在函数内部,您查询学生集合,并使用get_or_404()方法通过ID检索学生。如果数据库中不存在具有给定ID的学生,则会将学生数据保存在student变量中,并以404 Not Found HTTP错误进行响应。

您呈现一个名为student.html的模板,并将检索到的学生传递给它。

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

  1. nano templates/student.html

将以下代码键入到新的student.html文件中。这将类似于index.html模板,但只会显示单个学生:

flask_app/templates/student.html
{% extends 'base.html' %}

{% block content %}
    <span class="title">
        <h1>{% block title %} {{ student.firstname }} {{ student.lastname }}{% endblock %}</h1>
    </span>
    <div class="content">
            <div class="student">
                <p><b>#{{ student.id }}</b></p>
                <b>
                    <p class="name">{{ student.firstname }} {{ student.lastname }}</p>
                </b>
                <p>{{ student.email }}</p>
                <p>{{ student.age }} years old.</p>
                <p>Joined: {{ student.created_at }}</p>
                <div class="bio">
                    <h4>Bio</h4>
                    <p>{{ student.bio }}</p>
                </div>
            </div>
    </div>
{% endblock %}

保存并关闭文件。

在此文件中,您扩展基础模板,将学生的全名设置为页面的标题。您显示学生ID、学生的名字和姓氏、电子邮件、年龄、记录创建日期和他们的简介。

使用浏览器导航到第二个学生的URL:

http://127.0.0.1:5000/2

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

现在,编辑index.html以使每个学生的姓名链接到他们的页面:

  1. nano templates/index.html

编辑for循环如下:

flask_app/templates/index.html
{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
    </div>
{% endfor %}

保存并关闭文件。

您为学生的全名添加了一个<a>标签,该标签链接到学生页面,使用url_for()函数,将存储在student.id中的学生ID传递给student()视图函数。

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

http://127.0.0.1:5000/

现在,您将看到每个学生姓名链接到正确的学生页面。

在为单个学生创建页面之后,您将下一步添加一个页面来向数据库中添加新的学生。

步骤5 — 创建新记录

在这一步中,您将为您的应用程序添加一个新路由,用于使用 Web 表单将新学生添加到数据库中。

您将渲染一个带有 Web 表单的页面,用户在其中输入学生的数据。然后,您将处理表单提交,创建一个新学生的对象,使用 Student 模型,将其添加到会话中,然后提交事务,类似于您在第 2 步中添加学生条目的方式。

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

首先,打开您的 app.py 文件:

  1. nano app.py

app.py 文件的末尾添加以下路由:

flask_app/app.py
# ...


@app.route('/create/', methods=('GET', 'POST'))
def create():
    return render_template('create.html')

保存并关闭文件。

在这个路由中,您将元组 ('GET', 'POST') 传递给 methods 参数,以允许 GET 和 POST 请求。GET 请求用于从服务器检索数据。POST 请求用于将数据发布到特定路由。默认情况下,只允许 GET 请求。当用户首次使用 GET 请求请求 /create 路由时,将呈现名为 create.html 的模板文件。稍后,您将编辑此路由以处理用户填写并提交用于添加新学生的 Web 表单的 POST 请求。

打开新的 create.html 模板:

  1. nano templates/create.html

将以下代码添加到文件中:

{% extends 'base.html' %}

{% block content %}
    <h1 style="width: 100%">{% block title %} Add a New Student {% endblock %}</h1>
    <form method="post">
        <p>
            <label for="firstname">First Name</label>
            <input type="text" name="firstname"
                   placeholder="First name">
            </input>
        </p>

        <p>
            <label for="lastname">Last Name</label>
            <input type="text" name="lastname"
                   placeholder="Last name">
            </input>
        </p>

        <p>
            <label for="email">Email</label>
            <input type="email" name="email"
                   placeholder="Student email">
            </input>
        </p>

        <p>
            <label for="age">Age</label>
            <input type="number" name="age"
                   placeholder="Age">
            </input>
        </p>

        <p>
        <label for="bio">Bio</label>
        <br>
        <textarea name="bio"
                  placeholder="Bio"
                  rows="15"
                  cols="60"
                  ></textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

保存并关闭文件。

您扩展了基本模板,设置一个标题作为标题,并使用<form>标签,其中method属性设置为post,以指示该表单将提交一个POST请求。

您有两个文本字段,分别命名为firstnamelastname。您将使用这些名称稍后在视图函数中访问用户提交的表单数据。

您有一个名为email的电子邮件字段,一个用于学生年龄的数字字段,以及一个用于学生简介的文本区域。

最后,您在表单末尾有一个提交按钮。

现在,开发服务器正在运行,请使用浏览器导航到/create路由:

http://127.0.0.1:5000/create

您将看到一个添加新学生页面,其中包含一个Web表单和一个提交按钮,如下所示:

如果您填写表单并提交它,将向服务器发送一个POST请求,但是什么也不会发生,因为您没有在/create路由上处理POST请求。

打开app.py以处理用户提交的POST请求:

  1. nano app.py

编辑/create路由,使其如下所示:

flask_app/app.py

@app.route('/create/', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        firstname = request.form['firstname']
        lastname = request.form['lastname']
        email = request.form['email']
        age = int(request.form['age'])
        bio = request.form['bio']
        student = Student(firstname=firstname,
                          lastname=lastname,
                          email=email,
                          age=age,
                          bio=bio)
        db.session.add(student)
        db.session.commit()

        return redirect(url_for('index'))

    return render_template('create.html')

保存并关闭文件。

您在if request.method == 'POST'条件内处理POST请求。您从request.form对象中提取用户提交的名字、姓氏、电子邮件、年龄和简介。您使用int() Python函数将传递的年龄字符串转换为整数。您使用Student模型构建一个student对象。您将学生对象添加到数据库会话中,然后提交事务。

最后,您将用户重定向到索引页面,在该页面下方可以看到新添加的学生。

在运行开发服务器的情况下,使用浏览器导航到/create路由:

http://127.0.0.1:5000/create

填写表单并提交。

您将被重定向到索引页面,在那里您将看到您新添加的学生。

现在您已经具有添加新学生的功能,您需要在导航栏中添加一个到创建页面的链接。打开base.html

  1. nano templates/base.html

通过修改Create链接的href属性的值来编辑<body>标记:

flask_app/templates/base.html
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('create') }}">Create</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>

保存并关闭文件。

刷新您的索引页面,您会注意到导航栏中的创建链接现在是可用的。

现在您有一个带有网页表单的页面,用于添加新学生。有关网页表单的更多信息,请参阅在 Flask 应用程序中使用 Web 表单。要使用更高级和更安全的方法管理网页表单,请参阅如何在 Flask-WTF 中使用和验证 Web 表单。接下来,您将添加一个页面来编辑现有学生的数据。

步骤 6 — 编辑记录

在这一步中,您将为编辑现有学生数据添加一个新页面到您的应用程序中。您将添加一个新的 /ID/edit/ 路由来根据其 ID 编辑学生的数据。

打开 app.py:

  1. nano app.py

在文件末尾添加以下路由。这会使用其 ID 检索要编辑的学生条目。它提取后续通过网页表单提交的新学生数据。然后编辑学生数据,并将用户重定向到索引页面:

flask_app/app.py
# ...


@app.route('/<int:student_id>/edit/', methods=('GET', 'POST'))
def edit(student_id):
    student = Student.query.get_or_404(student_id)

    if request.method == 'POST':
        firstname = request.form['firstname']
        lastname = request.form['lastname']
        email = request.form['email']
        age = int(request.form['age'])
        bio = request.form['bio']

        student.firstname = firstname
        student.lastname = lastname
        student.email = email
        student.age = age
        student.bio = bio

        db.session.add(student)
        db.session.commit()

        return redirect(url_for('index'))

    return render_template('edit.html', student=student)

保存并关闭文件。

在这里,您有路由 /<int:student_id>/edit/,接受 POST 和 GET 方法,其中 student_id 是将 ID 传递给 edit() 视图函数的 URL 变量。

您可以使用get_or_404()查询方法在Student模型上获取与给定学生ID关联的学生。如果数据库中不存在具有给定ID的学生,则会响应404 Not Found错误。

如果给定的ID与学生相关联,则代码执行将继续到if request.method == 'POST'条件。如果请求是GET请求,表示用户未提交表单,则此条件为false,并且其中的代码将被跳过到return render_template('edit.html', student=student)行。这将呈现一个edit.html模板,将从数据库中获取的学生对象传递给它,使您能够使用当前学生数据填充学生Web表单。您稍后将创建此edit.html模板。

当用户编辑学生数据并提交表单时,将执行if request.method == 'POST'中的代码。您将提交的学生数据从request.form对象提取到相应的变量中。您将student对象的每个属性设置为新提交的数据,以更改列值,就像在第2步中所做的那样。如果在Web表单上未执行任何更改,则该列的值将保持在数据库中不变。

在将学生数据设置为新提交的数据后,您将student对象添加到数据库会话中,然后提交更改。最后,您将用户重定向到主页。

接下来,您需要创建一个页面,用户可以在该页面进行编辑。打开一个新的edit.html模板:

  1. nano templates/edit.html

这个新文件将有一个类似于create.html文件中的网络表单,其中包含当前学生数据作为字段的默认值。在其中添加以下代码:

flask_app/templates/edit.html

{% extends 'base.html' %}

{% block content %}
    <h1 style="width: 100%">
        {% block title %} Edit {{ student.firstname }}
                               {{ student.lastname }}'s Details
        {% endblock %}
    </h1>
    <form method="post">
        <p>
            <label for="firstname">First Name</label>
            <input type="text" name="firstname"
                   value={{ student.firstname }}
                   placeholder="First name">
            </input>
        </p>

        <p>
            <label for="lastname">Last Name</label>
            <input type="text" name="lastname"
                   value={{ student.lastname }}
                   placeholder="Last name">
            </input>
        </p>

        <p>
            <label for="email">Email</label>
            <input type="email" name="email"
                   value={{ student.email }}
                   placeholder="Student email">
            </input>
        </p>

        <p>
            <label for="age">Age</label>
            <input type="number" name="age"
                   value={{ student.age }}
                   placeholder="Age">
            </input>
        </p>

        <p>
        <label for="bio">Bio</label>
        <br>
        <textarea name="bio"
                  placeholder="Bio"
                  rows="15"
                  cols="60"
                  >{{ student.bio }}</textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

保存并关闭文件。

标题是学生的名字和姓氏。每个输入字段的value属性和生物文本区域的值设置为您从edit()视图函数传递到edit.html模板的student对象中的相应值。

现在,导航到以下网址以编辑第一个学生的详细信息:

http://127.0.0.1:5000/1/edit

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

编辑学生数据并提交表单。您将被重定向到索引页面,并且学生的信息将被更新。

接下来,您将在索引页面上每个学生下方添加一个编辑按钮,以链接到他们的编辑页面。打开index.html模板文件:

  1. nano templates/index.html

编辑此index.html文件中的for循环,使其看起来完全如下:

flask_app/templates/index.html

{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
        <a href="{{ url_for('edit', student_id=student.id) }}">Edit</a>
    </div>
{% endfor %}

保存并关闭文件。

在这里,您将添加一个<a>标签来链接到edit()视图函数,传递student.id值以链接到每个学生的编辑页面,带有编辑链接。

现在,您有一个用于编辑现有学生的页面。接下来,您将添加一个删除按钮以从数据库中删除学生。

步骤 7 — 删除记录

在这一步中,您将添加一个新路由和一个 删除 按钮,用于删除现有的学生。

首先,您将添加一个新的 /id/delete 路由,该路由接受 POST 请求。您的新 delete() 视图函数将接收要删除的学生的 ID,将 ID 传递给 Student 模型上的 get_or_404() 查询方法以获取该学生(如果存在),或者如果在数据库中找不到具有给定 ID 的学生,则响应一个 404 Not Found 页面。

打开 app.py 进行编辑:

  1. nano app.py

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

flask_app/app.py

# ...

@app.post('/<int:student_id>/delete/')
def delete(student_id):
    student = Student.query.get_or_404(student_id)
    db.session.delete(student)
    db.session.commit()
    return redirect(url_for('index'))

保存并关闭文件。

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

delete()视图函数通过student_id URL 变量接收要删除的学生的 ID。您使用get_or_404()方法获取学生并将其保存在student变量中,或者如果学生不存在,则响应404 Not Found。您在db.session.delete(student)行上使用数据库会话的delete()方法,将学生对象传递给它。这设置了会话以在提交事务时删除学生。因为您不需要执行任何其他修改,所以直接使用db.session.commit()提交事务。最后,您将用户重定向到索引页面。

接下来,编辑index.html模板以添加一个删除学生按钮:

  1. nano templates/index.html

编辑for循环,在编辑链接下方直接添加一个新的<form>标签:

flask_app/templates/index.html

{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
        <a href="{{ url_for('edit', student_id=student.id) }}">Edit</a>

        <hr>
        <form method="POST"
                action="{{ url_for('delete', student_id=student.id) }}">
            <input type="submit" value="Delete Student"
                onclick="return confirm('Are you sure you want to delete this entry?')">
        </form>

    </div>
{% endfor %}

保存并关闭文件。

在这里,你有一个网络表单,它向delete()视图函数提交POST请求。你将student.id作为student_id参数的参数传递,以指定要删除的学生条目。你使用在网络浏览器中可用的confirm()方法函数来显示提交请求前的确认消息。

现在刷新你的首页。

你会看到每个学生条目下面有一个删除学生按钮。点击它,并确认删除。你将被重定向到首页,学生将不再存在。

现在,你有了一种在你的学生管理应用程序中删除学生的方法。

结论

你使用Flask和Flask-SQLAlchemy构建了一个小型的用于管理学生的Flask网络应用程序,使用了SQLite数据库。你学会了如何连接到你的数据库,设置代表你的表的数据库模型,向数据库添加项目,查询你的表,并修改数据库数据。

在你的应用程序中使用SQLAlchemy可以让你使用Python类和对象来管理你的SQL数据库。与SQLite不同,你可以使用另一个数据库引擎,除了负责连接的SQLALCHEMY_DATABASE_URI配置外,你不需要在核心应用程序代码中做任何更改。这使得你可以在最小程度的代码更改下从一个SQL数据库引擎迁移到另一个。有关更多信息,请参阅Flask-SQLAlchemy文档

如果你想阅读更多关于Flask的内容,请查看使用Flask构建Web应用程序的其他教程系列。

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-flask-sqlalchemy-to-interact-with-databases-in-a-flask-application