如何使用Flask-SQLAlchemy在Flask應用程序中與數據庫交互

作者選擇了自由開源基金作為寫作以換取捐款計畫的受捐對象。

介紹

在網絡應用中,通常需要一個數據庫,它是一個組織良好的數據集合。您使用數據庫來存儲和維護可以有效檢索和操作的持久數據。例如,在社交媒體應用中,您有一個數據庫,用於存儲用戶數據(個人信息、帖子、評論、追隨者),以便可以有效地操作。您可以根據不同的要求和條件向數據庫添加數據、檢索數據、修改數據或刪除數據。在網絡應用中,這些要求可能是用戶添加新帖子、刪除帖子或刪除他們的帳戶,這可能會刪除他們的帖子或不刪除。您執行的操作來操作數據將取決於應用程序中的具體功能。例如,您可能不希望用戶發表沒有標題的帖子。

使用數字海洋應用平台從GitHub部署您的Flask應用程序。讓數字海洋專注於擴展您的應用。

Flask 是一個輕量級的 Python 網頁框架,提供了在 Python 語言中創建 Web 應用程序所需的有用工具和功能。 SQLAlchemy 是一個 SQL 工具包,為關係型數據庫提供了高效和高性能的數據庫訪問。它提供了與幾個數據庫引擎互動的方法,如 SQLite、MySQL 和 PostgreSQL。它讓您可以訪問數據庫的 SQL 功能。它還提供了一個對象關係映射器(ORM),允許您使用簡單的 Python 對象和方法進行查詢和處理數據。 Flask-SQLAlchemy 是 Flask 的擴展,使使用 SQLAlchemy 與 Flask 更加輕鬆,通過 SQLAlchemy 在 Flask 應用程序中提供工具和方法來與您的數據庫互動。

在本教程中,您將構建一個小型的學生管理系統,演示如何使用 Flask-SQLAlchemy 擴展。您將與 Flask 一起使用它執行基本任務,例如連接到數據庫服務器、創建表、向表中添加數據、檢索數據,以及更新和刪除數據庫中的項目。您將使用 SQLAlchemy 與 SQLite,儘管您也可以將其與其他數據庫引擎一起使用,例如 PostgreSQL 和 MySQL。SQLite 在 Python 中運作良好,因為 Python 標準庫提供了 sqlite3 模塊,它由 SQLAlchemy 在幕後使用來與 SQLite 數據庫進行交互,而無需安裝任何東西。在 Linux 系統上,SQLite 默認安裝,並且作為 Python 包的一部分安裝在 Windows 上。

先決條件

  • 本地 Python 3 編程環境。按照《如何安裝和設置 Python 3 的本地編程環境》系列中適用於您的發行版的教程。在本教程中,我們將將項目目錄稱為 flask_app

  • 對基本 Flask 概念的理解,如路由、視圖函數和模板。如果您對 Flask 不熟悉,請查看《如何使用 Flask 和 Python 創建您的第一個 Web 應用程序》和《如何在 Flask 應用程序中使用模板》。

  • 了解基本的HTML概念。您可以查看我们的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類。您將初始化數據庫,基於您聲明的模型創建一個名為學生的表,並將一些學生添加到您的學生表中。

設置數據庫連接

在您的flask_app目錄中打開一個名為app.py的文件。此文件將包含用於設置數據庫和Flask路由的代碼:

  1. nano app.py

此文件將連接到名為database.db的SQLite數據庫,並具有一個名為Student的類,代表用於存儲學生信息的數據庫學生表,以及您的Flask路由。在app.py的頂部添加以下import語句:

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

在這裡,您導入os模塊,它為您提供了訪問各種操作系統接口的功能。您將使用它來構建database.db數據庫文件的文件路徑。

flask套件中,您需要導入所需的幫助程式來建立應用程式: Flask類別用於建立Flask應用程式實例,render_template()函式用於呈現模板,request物件用於處理請求,url_for()函式用於構建路由的URL,以及redirect()函式用於重新導向使用者。有關路由和模板的更多資訊,請參閱如何在Flask應用程式中使用模板

然後,您從Flask-SQLAlchemy擴展中導入SQLAlchemy類別,該擴展使您可以訪問SQLAlchemy的所有函式和類別,以及與Flask集成的幫助程式和功能。您將使用它來建立一個連接到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:///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類創建數據庫對象,將應用程序實例傳遞給SQLAlchemy類,以將Flask應用程序與SQLAlchemy連接起來。將數據庫對象存儲在名為db的變量中。您將使用此db對象與您的數據庫交互。

聲明表

當數據庫連接建立並創建了數據庫對象後,您將使用數據庫對象為學生創建一個數據庫表,該表由一個模型表示 —— 一個Python類,它繼承自Flask-SQLAlchemy通過您之前創建的db數據庫實例提供的基類。要將學生表定義為模型,請將以下類添加到您的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.Integer 的整数。 primary_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()日期時間函數。在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 和模型声明正确。

填充表

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

使用您之前打开的相同 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 %}行中使用Jinjafor循环来遍历从index()视图函数传递给此模板的students变量中的每个学生。您显示学生的ID、名字、姓氏、电子邮件、年龄、他们被添加到数据库的日期以及他们的个人简介。

在啟用虛擬環境的情況下,位於您的 flask_app 目錄中,請告訴 Flask 有關應用程式(在這種情況下是 app.py )的資訊,使用 FLASK_APP 環境變數。然後將 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 %}

保存並關閉文件。

在此文件中,您擴展了基本模板,將學生的全名設置為頁面的標題。您顯示了學生的學生證號、名和姓、電子郵件、年齡、記錄創建日期和他們的個人簡歷。

使用瀏覽器導航到第二個學生的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 %}

保存並關閉文件。

您將一個標籤添加到學生的全名,該標籤使用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應用中使用網頁表單。對於管理網頁表單的更高級且更安全的方法,請參閱如何使用Flask-WTF來使用和驗證網頁表單。接下來,您將添加一個頁面來編輯現有學生的數據。

步驟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作為URL變量傳遞給edit()視圖函數。

您使用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 屬性和 bio 文本區域的值設置為從 edit() 視圖函數傳遞到 edit.html 模板的 student 對象中的相應值。

現在,前往以下 URL 以編輯第一位學生的詳細信息:

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.post裝飾器來替代通常的app.route裝飾器,這是在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 %}

保存並關閉文件。

在這裡,你有一個網絡表單,它提交一個 POST 請求給 delete() 視圖函數。你將 student.id 作為 student_id 參數的參數傳遞,以指定要刪除的學生記錄。你使用網絡瀏覽器中可用的 confirm() 方法 函數來在提交請求之前顯示確認消息。

現在刷新你的首頁。

你會看到每個學生記錄下方有一個 刪除學生 按鈕。點擊它,然後確認刪除。你將被重定向到首頁,該學生將不再存在。

現在,你已經有一種方法可以從你的學生管理應用程序中刪除學生。

結論

你使用 Flask 和 Flask-SQLAlchemy 構建了一個小型的 Web 應用程序來管理學生,並使用 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