介紹
Flask是一個輕量級的Python Web框架,提供了在Python語言中創建Web應用程序所需的有用工具和功能。SQLAlchemy是一個SQL工具包,提供了對關係數據庫的高效和高性能的訪問。它提供了與幾個數據庫引擎交互的方式,如SQLite、MySQL和PostgreSQL。它讓您可以訪問數據庫的SQL功能。它還提供了一個對象關係映射器(ORM),允許您使用Python對象和方法進行查詢和處理數據。Flask-SQLAlchemy是一個Flask擴展,使使用SQLAlchemy與Flask更容易,為您提供了在Flask應用程序中通過SQLAlchemy與數據庫交互的工具和方法。
A many-to-many database relationship is a relationship between two database tables where a record in each table can reference several records in the other table. For example, in a blog, a table for posts can have a many-to-many relationship with a table for storing authors. Each post can have many authors, and each author can write many posts. Therefore, there is a many-to-many relationship between posts and authors. For another example, in a social media application, each post may have many hashtags, and each hashtag may have many posts.
在本教程中,您將通過向使用Flask和Flask-SQLAlchemy構建的應用程序添加一對多關係來修改它。您將在文章和標籤之間建立一個關係,其中每篇博客文章都可以有多個標籤,而每個標籤都可以標記多篇文章。
儘管您可以獨立遵循本教程,但它也是如何使用Flask-SQLAlchemy建立一對多的數據庫關係教程的延續,該教程中您將建立一個多表數據庫,其中帖子和評論在一個博客應用程序中具有一對多的關係。
到教程結束時,您的應用程序將具有一個新功能,用於向帖子添加標籤。帖子可以被標記為多個標籤,每個標籤頁將顯示所有與之標記的帖子。
先決條件
-
本地Python 3編程環境。請按照如何安裝並設置本地Python 3編程環境系列中您的發行版教程。在本教程中,我們將把我們的項目目錄命名為
flask_app
。 -
對於基本的 Flask 概念有所了解,例如路由、視圖函數和模板。如果您對 Flask 不熟悉,請查看 如何使用 Flask 和 Python 建立您的第一個 Web 應用程式 和 在 Flask 應用程式中使用模板的方法。
-
對於基本的 HTML 概念有所了解。您可以查閱我們的 使用 HTML 構建網站的方法 教程系列以獲取背景知識。
-
(可选)在第一步中,您将克隆您将在本教程中使用的博客应用程序。但是,您也可以选择通过教程如何使用Flask-SQLAlchemy进行一对多数据库关系。您可以从此页面访问最终代码。
第1步 — 设置Web应用程序
在这一步中,您将设置博客应用程序以准备进行修改。您还将查看Flask-SQLAlchemy数据库模型和Flask路由,以了解应用程序的结构。如果您已经按照先决条件部分的教程,并且仍然在本地计算机上拥有代码和虚拟环境,则可以跳过此步骤。
為了演示在 Flask Web 應用程式中使用 Flask-SQLAlchemy 添加多對多關係,您將使用先前教程中的應用程式代碼,該代碼是一個具有添加和顯示文章、對文章發表評論以及閱讀和刪除現有評論功能的部落格系統。
複製存儲庫並將其從 flask-slqa-bloggy
重新命名為 flask_app
,使用以下命令:
切換到 flask_app
目錄:
然後創建一個新的虛擬環境:
啟動該虛擬環境:
安裝 Flask 和 Flask-SQLAlchemy:
接下來,設置以下環境變量:
FLASK_APP
指定您當前正在開發的應用程式,這在本例中是 app.py
。 FLASK_ENV
指定模式。您將其設置為 development
以使用開發模式;這將允許您調試應用程式。請記住不要在生產環境中使用此模式。
接下來,打開 Flask shell 以創建數據庫表:
然後導入 db
Flask-SQLAlchemy 數據庫對象,Post
模型和 Comment
模型,並使用 db.create_all()
函數創建數據庫表:
然後使用 init_db.py
程序填充數據庫:
這將向數據庫添加三篇文章和四個評論。
啟動開發服務器:
如果您前往瀏覽器,應用程式將在以下 URL 上運行:
http://127.0.0.1:5000/
你會看到類似以下的頁面:
如果出現錯誤,請確保你已經正確按照上述步驟操作。
要停止開發伺服器,請使用 CTRL + C
。
接下來,你將會查看 Flask-SQLAlchemy 資料庫模型,以了解表格之間目前的關係。如果你熟悉 app.py
檔案的內容,你可以跳過下一步。
打開 app.py
檔案:
檔案內容如下:
這裡,你有兩個代表兩個表格的資料庫模型:
-
Post
: 具有 ID 欄位、標題、內容,並且與評論表格有一對多的關係。 -
Comment
: 具有 ID 欄位、內容欄位,以及用於參考評論所屬帖子的post_id
欄位。
在模型下方,你有以下路由:
/
: 首頁,顯示資料庫中的所有帖子。/<int:post_id>/
: 個別帖子頁面。例如,鏈接http://127.0.0.1:5000/2/
顯示資料庫中第二篇帖子的詳細信息及其評論。/comments/
:顯示數據庫中所有評論並鏈接到每個評論發布的帖子的頁面。/comments/<int:comment_id>/delete
:通過一個刪除評論按鈕來刪除評論的路由。
關閉app.py
文件。
在下一步中,您將使用多對多關係來建立兩個表之間的連接。
第2步 – 設置多對多關係的數據庫模型
在此步驟中,您將添加一個數據庫模型,該模型將表示標籤表。您將使用關聯表將其與現有的帖子表相關聯,關聯表是一個連接兩個表的表,以實現多對多關係。多對多關係將兩個表鏈接在一起,其中每個表中的每個項目在另一個表中都有多個相關項目。在關聯表中,每個帖子將引用其標籤,每個標籤都引用與其標記的帖子。您還將在數據庫中插入幾篇帖子和標籤,打印帖子及其標籤,以及打印標籤及其相關的帖子。
假設您有一個簡單的博客帖子表如下:
Posts
+----+-----------------------------------+
| id | content |
+----+-----------------------------------+
| 1 | A post on life and death |
| 2 | A post on joy |
+----+-----------------------------------+
以及標籤表如下:
Tags
+----+-------+
| id | name |
+----+-------+
| 1 | life |
| 2 | death |
| 3 | joy |
+----+-------+
让我们说你想要使用 life
和 death
标签标记 A post on life and death
。你可以通过在帖子表中添加一行来实现这一点:
Posts
+----+-----------------------------------+------+
| id | content | tags |
+----+-----------------------------------+------+
| 1 | A post on life and death | 1, 2 |
| 2 | A post on joy | |
+----+------------------------------------------+
这种方法行不通,因为每个列应该只有一个值。如果你有多个值,基本操作,如添加和更新数据会变得繁琐和缓慢。相反,应该有一个第三个表,它引用相关表的主键 —— 这个表通常称为一个关联表或连接表,它存储每个表中每个项目的ID。
以下是一个将帖子和标签联系起来的关联表的示例:
post_tag
+----+---------+-------------+
| id | post_id | tag_id |
+----+---------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
+----+---------+-------------+
在第一行中,具有ID 1
的帖子(即A post on life and death
)相关联到具有ID 1
的标签(life
)。在第二行中,相同的帖子也与具有ID 2
的标签(death
)相关联。这意味着该帖子被标记为life
和 death
。同样,你可以给每个帖子打上多个标签。
现在,你将修改 app.py
文件以添加一个新的数据库模型,代表你将用于存储标签的表。你还将添加一个名为 post_tag
的关联表,将帖子与标签联系起来。
首先,打开 app.py
来建立帖子和标签之间的关系:
在db
对象下方和Post
模型上方添加一个post_tag
表和一个Tag
模型,然后在Post
模型中添加一个tags
关系伪列,这样你可以通过post.tags
访问帖子的标签,并通过tag.posts
访问标签的帖子:
保存并关闭文件。
在这里,你使用db.Table()
函数创建了一个具有两列的表。对于关联表,最佳实践是使用表而不是数据库模型。
post_tag
表有两列,代表两个外键,这些键用于引用另一个表中的主键列:
post_id
:一个整数外键,代表帖子ID,并引用post
表中的ID列。tag_id
:一个整数外键,代表标签ID,并引用tag
表中的ID列。
这些键建立了表之间的关系。
在post_tag
表下方,创建一个Tag
模型,它表示你将存储标签的表。这个标签表有两列:
id
:标签的ID。name
:标签的名称。
你在特殊的__repr__()
方法中使用标签的名称,为每个标签对象提供清晰的字符串表示,用于调试目的。
你在Post
模型中添加了一个tags
类变量。你使用db.relationship()
方法,将其传递给标签模型的名称(在本例中为Tag
)。
你将post_tag
关联表传递给secondary
参数,以建立帖子和标签之间的多对多关系。
你使用backref
参数向Tag
模型添加一个反向引用,它的行为类似于列。这样,你可以通过tag.posts
访问标签的帖子,通过post.tags
访问帖子的标签。稍后你将看到一个示例。
接下来,编辑init_db.py
Python程序,通过添加post_tag
关联表和基于Tag
模型的标签表来修改数据库:
编辑文件如下:
保存并关闭文件。
在這裡,您導入Tag
模型。您使用db.drop_all()
函數刪除數據庫中的所有內容,以安全地添加標籤和post_tag
表,並避免與向數據庫添加新表相關的任何常見問題。然後,您使用db.create_all()
函數重新創建所有表。
在上一個教程的代碼之後,聲明了帖子和評論後,您使用Tag
模型創建了四個標籤。
然後,您使用tags = db.relationship('Tag', secondary=post_tag, backref='posts')
在app.py
文件中添加的tags
屬性,為帖子添加標籤。您使用類似於Python列表的append()
方法為帖子分配標籤。
接下來,您使用db.session.add_all()
函數將您創建的標籤添加到數據庫會話中。
注意:
db.create_all()
函數如果表已存在,將不會重新創建或更新表。例如,如果您修改模型添加新列並運行db.create_all()
函數,則對模型所做的更改將不會應用於數據庫中已存在的表。解決方案是使用db.drop_all()
函數刪除所有現有數據庫表,然後使用db.create_all()
函數重新創建它們,如在init_db.py
文件中演示的那樣。
這個過程將應用你對模型所做的修改,但也會刪除數據庫中的所有現有數據。要更新數據庫並保留現有數據,您需要使用模式遷移,這允許您修改表並保留數據。您可以使用Flask-Migrate
擴展通過Flask命令行界面執行SQLAlchemy模式遷移。
運行init_db.py
程序來應用對數據庫的更改:
該程序應該成功執行並且不會輸出任何內容。如果您看到錯誤,請確保您對init_db.py
文件進行了正確的更改。
要查看當前在數據庫中的文章和標籤,打開Flask shell:
執行以下Python代碼,循環遍歷文章和標籤:
在這裡,您從app.py
中導入Post
模型。您查詢文章表並提取數據庫中的所有文章。您循環遍歷文章,並打印文章標題和與每篇文章相關的標籤列表。
您將獲得類似以下的輸出:
Output
Post The First
[<Tag "animals">, <Tag "writing">]
---
Post The Third
[<Tag "cooking">, <Tag "tech">, <Tag "writing">]
---
Post The Second
[]
---
您可以使用tag.name
來訪問標籤名稱,如下面的示例所示,您可以使用Flask shell運行它:
在這裡,除了打印文章的標題外,您還會循環遍歷每篇文章的標籤並打印標籤名稱。
您將獲得類似以下的輸出:
OutputTITLE: Post The First
-
TAGS:
> animals
> writing
------------------------------
TITLE: Post The Third
-
TAGS:
> cooking
> tech
> writing
------------------------------
TITLE: Post The Second
-
TAGS:
------------------------------
如您所见,您在 init_db.py
程序中添加的标签已经正确地与它们所标记的帖子链接起来。
要查看如何通过 tag.posts
访问具有特定标签的帖子的演示,请在 Flask shell 中运行以下代码:
您导入了 Tag
模型。然后,您在 query
属性上使用 filter_by()
方法,传递一个 name
参数以通过名称获取 writing
标签,并使用 first()
方法获取第一个结果。您将标签对象存储在一个名为 writing_tag
的变量中。有关 filter_by
方法的更多信息,请参阅 如何使用 Flask-SQLAlchemy 与 Flask 应用程序中的数据库进行交互的第 4 步 教程。
您循环遍历通过 writing_tag.posts
访问的带有 writing
标签的帖子。您打印帖子的标题、内容以及您使用 列表推导式 基于帖子的标签访问的标签名称列表,您可以通过 post.tags
访问。
您将获得类似以下的输出:
OutputPost The Third
------
Content for the third post
-
['cooking', 'tech', 'writing']
--------------------
Post The First
------
Content for the first post
-
['animals', 'writing']
--------------------
在这里,您可以看到被标记为 writing
的两篇帖子,并且标签名称显示在 Python 列表中。
您现在可以访问帖子及其标签,以及访问特定标签的帖子。
你已经添加了一个代表标签表的数据库模型。你使用了一个关联表来在帖子和标签之间建立链接,并将一些标签插入了数据库,并给帖子打了标签。你访问了帖子及其标签,以及一个特定标签的帖子。接下来,你将使用 Flask shell 添加新帖子和新标签,并在标签和帖子之间建立链接,然后学习如何从帖子中移除标签。
第三步 — 在多对多关系中管理数据
在这一步中,你将使用 Flask shell 添加新帖子到数据库,添加标签,并在帖子和标签之间建立链接。你将访问带有标签的帖子,并学习如何在多对多关系中解除项目之间的关联。
首先,在你的编程环境中激活 Flask shell,如果还没有:
接下来,添加一些帖子和标签:
from app import db, Post, Tag
life_death_post = Post(title='A post on life and death', content='life and death')
joy_post = Post(title='A post on joy', content='joy')
life_tag = Tag(name='life')
death_tag = Tag(name='death')
joy_tag = Tag(name='joy')
life_death_post.tags.append(life_tag)
life_death_post.tags.append(death_tag)
joy_post.tags.append(joy_tag)
db.session.add_all([life_death_post, joy_post, life_tag, death_tag, joy_tag])
db.session.commit()
这将创建两个帖子和三个标签。你用它们相关的标签给帖子打标签,并使用 add_all()
方法将新创建的项目添加到数据库会话中。然后,你提交更改并使用 commit()
方法将其应用到数据库中。
接下来,使用 Flask shell 获取所有帖子及其标签,就像在上一步中所做的那样:
posts = Post.query.all()
for post in posts:
print(post.title)
print(post.tags)
print('---')
你将得到类似以下的输出:
Output
Post The First
[<Tag "animals">, <Tag "writing">]
---
Post The Third
[<Tag "cooking">, <Tag "tech">, <Tag "writing">]
---
Post The Second
[]
---
A post on life and death
[<Tag "life">, <Tag "death">]
---
A post on joy
[<Tag "joy">]
---
你可以看到帖子已经添加,并附带了它们的标签。
為了演示如何在多對多的數據庫關係中斷兩個項目之間的關係,假設第三篇帖子
不再是關於烹飪的,因此您需要從中刪除烹飪
標籤。
首先,獲取要刪除的帖子和標籤:
在此處,使用filter_by()
方法獲取標題為第三篇帖子
的帖子。您獲得烹飪
標籤。您打印帖子的標題,其標籤以及帶有烹飪
標籤的帖子。
filter_by()
方法返回一個查詢對象,您可以使用all()
方法獲取所有結果的列表。但是因為在這種情況下我們只期望一個結果,所以您使用first()
方法來獲取第一個(也是唯一的)結果。有關first()
和all()
方法的更多信息,請查看如何在Flask應用程序中使用Flask-SQLAlchemy與數據庫交互的第4步。
您將獲得以下輸出:
Output
Post The Third
[<Tag "cooking">, <Tag "tech">, <Tag "writing">]
[<Post "Post The Third">]
在這裡,您可以看到帖子標題,帖子標籤以及帶有烹飪
標籤的帖子列表。
要從帖子中刪除烹飪
標籤,請使用remove()
方法,如下所示:
在這裡,您使用remove()
方法將烹飪
標籤與帖子解除關聯。然後,您使用db.session.commit()
方法將更改應用到數據庫中。
你将得到一個確認標籤已從帖子中移除的輸出:
Output[<Tag "tech">, <Tag "writing">]
[]
正如你所看到的,cooking
標籤不再在 post.tags
列表中,該帖子已從 tag.posts
列表中刪除。
退出 Flask shell:
你已添加了新的帖子和標籤。你給帖子打了標籤,也從帖子中刪除了標籤。接下來,你將在 Flask 網誌的首頁中顯示每個帖子的標籤。
步驟 4 — 在每個帖子下顯示標籤
在這一步中,你將編輯首頁模板,以在每個帖子下顯示標籤。
首先,看一下 Flask 網誌目前的首頁。
在啟用你的編程環境後,使用 FLASK_APP
環境變量告訴 Flask 關於應用程序(在這種情況下為 app.py
)。然後將 FLASK_ENV
環境變量設置為 development
以在開發模式下運行應用程序:
接下來,運行應用程序:
隨著開發服務器運行,訪問你的瀏覽器中的以下 URL:
http://127.0.0.1:5000/
你將看到類似以下的頁面:
保持開發服務器運行,並打開一個新的終端窗口。
您需要在两个页面上显示每个帖子的标签:在索引页面上的每个帖子下方以及帖子页面上的帖子内容下方。您将使用相同的代码来显示标签。为了避免重复编码,您将使用一个Jinja宏,它的行为类似于Python函数。宏包含动态HTML代码,可以在调用宏的任何地方显示,并且编辑它会在调用它的任何地方应用更改,这使得代码可重用。
首先,在您的templates
目录中打开一个名为macros.html
的新文件:
将以下代码添加到其中:
保存并关闭文件。
在这里,您使用macro
关键字声明了一个名为display_tags()
的宏,带有一个名为post
的参数。您使用<div>
标签,在其中显示一个<h4>
标题。您使用for
循环遍历将作为参数传递给宏的帖子对象的标签,类似于在Python函数调用中传递参数。您通过post.tags
获取标签。您在<a>
标签内显示标签名称。稍后将编辑href
属性的值,以链接到您将创建的标记页面,该页面上显示了标记有特定标记的所有帖子。您使用endmacro
关键字指定宏的结束。
接下来,在索引页面上的每个帖子下方显示标签,请打开index.html
模板文件:
首先,您需要從macros.html
文件中導入display_tags()
宏。在{% extends 'base.html' %}
行的上方添加以下導入:
接下來,編輯for post in posts
循環,調用display_tags()
宏,如下所示:
保存並關閉文件。
您調用display_tags()
宏,將post
對象傳遞給它。這將在每個帖子下顯示標籤名稱。
刷新瀏覽器中的主頁,您將看到每個帖子下顯示的標籤,如下圖所示:
接下來,您將在帖子頁面的帖子內容下添加標籤。打開post.html
模板文件:
首先,在頂部導入display_tags
宏:
然後,在帖子內容下方並在<hr>
標籤上方調用display_tags()
宏,將post
對象傳遞給它:
<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>
{{ display_tags(post) }}
<hr>
<h3>Comments</h3>
保存並關閉文件。
現在,轉到帖子頁面:
http://127.0.0.1:5000/2
您將看到標籤的顯示方式與主頁上顯示的標籤相同。
您已經在每個帖子下顯示了添加的標籤。接下來,您將為Flask應用程序添加一個新的路由,該路由顯示所有標記為特定標籤的帖子。然後,您將使此步驟中顯示的標籤鏈接功能正常。
第 5 步 — 顯示標籤及其文章
在此步驟中,您將向您的 Web 應用程序添加一個路由和一個模板,以顯示您在數據庫中擁有的標籤及其文章。
首先,您將添加一個用於顯示每個標籤文章的路由。例如,路由 /tags/tag_name/
將顯示一個頁面,該頁面顯示所有帶有名為 tag_name
的標籤的文章。
打開 app.py
進行編輯:
將以下路由添加到文件末尾:
# ...
@app.route('/tags/<tag_name>/')
def tag(tag_name):
tag = Tag.query.filter_by(name=tag_name).first_or_404()
return render_template('tag.html', tag=tag)
保存並關閉文件。
在這裡,您使用了一個名為 tag_name
的 URL 變量,該變量確定將在標籤頁面上顯示的標籤和標記為它的文章。標籤名稱通過 tag_name
參數傳遞給 tag()
視圖函數,您在 filter_by()
方法上使用它來查詢標籤。您使用 first_or_404()
來獲取標籤對象並將其存儲在名為 tag
的變量中,或者在數據庫中不存在給定名稱的標籤時,以 404 Not Found
錯誤消息做出響應。
然後,您將渲染一個名為 tag.html
的模板文件,並將其傳遞給 tag
對象。
打開新的 templates/tag.html
進行編輯:
將以下代碼添加到其中:
保存並關閉文件。
您從macros.html
中導入display_tags()
宏,並擴展基礎模板。
在內容塊中,您將標題設置為包含標籤名的標題。然後,您通過tag.posts
訪問透過給定標籤進行標記的帖子進行循環。您顯示了帖子ID、帖子標題和帖子內容。然後,您調用display_tags()
宏來顯示所有帖子標籤。
在運行開發服務器的情況下,請瀏覽以下URL:
http://127.0.0.1:5000/tags/writing/
這是writing
標籤的頁面。如您所見,顯示了所有標記為writing
的帖子:
現在,編輯display_tags()
宏以使標籤鏈接可用。打開macros.html
:
將href
屬性的值編輯如下:
保存並關閉文件。
刷新使用了display_tags()
宏的頁面,您將看到標籤鏈接現在可用:
http://127.0.0.1:5000/
http://127.0.0.1:5000/2/
http://127.0.0.1:5000/tags/writing/
正如您所見,使用Jinja宏使您能夠重用代碼,編輯宏將在多個模板中應用更改。
您已添加了個別標籤的頁面,用戶可以在該頁面上查看所有標記為特定標籤的帖子,並且帖子下的標籤現在鏈接到這個新頁面。
結論
你添加到博客系统的标签功能演示了如何使用Flask-SQLAlchemy扩展管理多对多关系。您学会了如何使用关联表(也称为连接表)链接两个相关表,将一个条目与另一个关联,将条目添加到数据库中,并访问和取消与条目相关的数据。
如果您想阅读更多关于Flask的信息,请查看使用Flask构建Web应用程序的其他教程系列。