如何使用Flask-SQLAlchemy中的多對多數據庫關係

作者選擇了自由開源基金作為撰寫捐贈計劃的捐贈對象。

介紹

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建立一對多的數據庫關係教程的延續,該教程中您將建立一個多表數據庫,其中帖子和評論在一個博客應用程序中具有一對多的關係。

到教程結束時,您的應用程序將具有一個新功能,用於向帖子添加標籤。帖子可以被標記為多個標籤,每個標籤頁將顯示所有與之標記的帖子。

先決條件

第1步 — 设置Web应用程序

在这一步中,您将设置博客应用程序以准备进行修改。您还将查看Flask-SQLAlchemy数据库模型和Flask路由,以了解应用程序的结构。如果您已经按照先决条件部分的教程,并且仍然在本地计算机上拥有代码和虚拟环境,则可以跳过此步骤。

為了演示在 Flask Web 應用程式中使用 Flask-SQLAlchemy 添加多對多關係,您將使用先前教程中的應用程式代碼,該代碼是一個具有添加和顯示文章、對文章發表評論以及閱讀和刪除現有評論功能的部落格系統。

複製存儲庫並將其從 flask-slqa-bloggy 重新命名為 flask_app,使用以下命令:

  1. git clone https://github.com/do-community/flask-slqa-bloggy flask_app

切換到 flask_app 目錄:

  1. cd flask_app

然後創建一個新的虛擬環境:

  1. python -m venv env

啟動該虛擬環境:

  1. source env/bin/activate

安裝 Flask 和 Flask-SQLAlchemy:

  1. pip install Flask Flask-SQLAlchemy

接下來,設置以下環境變量:

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

FLASK_APP 指定您當前正在開發的應用程式,這在本例中是 app.pyFLASK_ENV 指定模式。您將其設置為 development 以使用開發模式;這將允許您調試應用程式。請記住不要在生產環境中使用此模式。

接下來,打開 Flask shell 以創建數據庫表:

  1. flask shell

然後導入 db Flask-SQLAlchemy 數據庫對象,Post 模型和 Comment 模型,並使用 db.create_all() 函數創建數據庫表:

  1. from app import db, Post, Comment
  2. db.create_all()
  3. exit()

然後使用 init_db.py 程序填充數據庫:

  1. python init_db.py

這將向數據庫添加三篇文章和四個評論。

啟動開發服務器:

  1. flask run

如果您前往瀏覽器,應用程式將在以下 URL 上運行:

http://127.0.0.1:5000/

你會看到類似以下的頁面:

如果出現錯誤,請確保你已經正確按照上述步驟操作。

要停止開發伺服器,請使用 CTRL + C

接下來,你將會查看 Flask-SQLAlchemy 資料庫模型,以了解表格之間目前的關係。如果你熟悉 app.py 檔案的內容,你可以跳過下一步。

打開 app.py 檔案:

  1. nano app.py

檔案內容如下:

flask_app/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.route('/')
def index():
    posts = Post.query.all()
    return render_template('index.html', posts=posts)


@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)


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


@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))

這裡,你有兩個代表兩個表格的資料庫模型:

  • 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   |
+----+-------+

让我们说你想要使用 lifedeath 标签标记 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)相关联。这意味着该帖子被标记为lifedeath。同样,你可以给每个帖子打上多个标签。

现在,你将修改 app.py 文件以添加一个新的数据库模型,代表你将用于存储标签的表。你还将添加一个名为 post_tag 的关联表,将帖子与标签联系起来。

首先,打开 app.py 来建立帖子和标签之间的关系:

  1. nano app.py

db对象下方和Post模型上方添加一个post_tag表和一个Tag模型,然后在Post模型中添加一个tags关系伪列,这样你可以通过post.tags访问帖子的标签,并通过tag.posts访问标签的帖子:

flask_app/app.py

# ...

db = SQLAlchemy(app)


post_tag = db.Table('post_tag',
                    db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
                    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
                    )


class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

    def __repr__(self):
        return f'<Tag "{self.name}">' 



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')
    tags = db.relationship('Tag', secondary=post_tag, backref='posts')

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

保存并关闭文件。

在这里,你使用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模型的标签表来修改数据库:

  1. nano init_db.py

编辑文件如下:

flask_app/init_db.py
from app import db, Post, Comment, Tag

db.drop_all()
db.create_all()

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)

tag1 = Tag(name='animals')
tag2 = Tag(name='tech')
tag3 = Tag(name='cooking')
tag4 = Tag(name='writing')

post1.tags.append(tag1)  # 将第一篇帖子标记为'animals'
post1.tags.append(tag4)  # 将第一篇帖子标记为'writing'
post3.tags.append(tag3)  # 将第三篇帖子标记为'cooking'
post3.tags.append(tag2)  # 将第三篇帖子标记为'tech'
post3.tags.append(tag4)  # 将第三篇帖子标记为'writing'


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

db.session.commit()

保存并关闭文件。

在這裡,您導入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程序來應用對數據庫的更改:

  1. python init_db.py

該程序應該成功執行並且不會輸出任何內容。如果您看到錯誤,請確保您對init_db.py文件進行了正確的更改。

要查看當前在數據庫中的文章和標籤,打開Flask shell:

  1. flask shell

執行以下Python代碼,循環遍歷文章和標籤:

from app import Post

posts = Post.query.all()

for post in posts:
    print(post.title)
    print(post.tags)
    print('---')

在這裡,您從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運行它:

from app import Post

posts = Post.query.all()

for post in posts:
    print('TITLE: ', post.title)
    print('-')
    print('TAGS:')
    for tag in post.tags:
        print('> ', tag.name)
    print('-'*30)

在這裡,除了打印文章的標題外,您還會循環遍歷每篇文章的標籤並打印標籤名稱。

您將獲得類似以下的輸出:

Output
TITLE: 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 中运行以下代码:

from app import Tag

writing_tag = Tag.query.filter_by(name='writing').first()

for post in writing_tag.posts:
    print(post.title)
    print('-'*6)
    print(post.content)
    print('-')
    print([tag.name for tag in post.tags])
    print('-'*20)

您导入了 Tag 模型。然后,您在 query 属性上使用 filter_by() 方法,传递一个 name 参数以通过名称获取 writing 标签,并使用 first() 方法获取第一个结果。您将标签对象存储在一个名为 writing_tag 的变量中。有关 filter_by 方法的更多信息,请参阅 如何使用 Flask-SQLAlchemy 与 Flask 应用程序中的数据库进行交互的第 4 步 教程。

您循环遍历通过 writing_tag.posts 访问的带有 writing 标签的帖子。您打印帖子的标题、内容以及您使用 列表推导式 基于帖子的标签访问的标签名称列表,您可以通过 post.tags 访问。

您将获得类似以下的输出:

Output
Post 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,如果还没有:

  1. 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">] ---

你可以看到帖子已经添加,并附带了它们的标签。

為了演示如何在多對多的數據庫關係中斷兩個項目之間的關係,假設第三篇帖子不再是關於烹飪的,因此您需要從中刪除烹飪標籤。

首先,獲取要刪除的帖子和標籤:

  1. from app import db, Post, Tag
  2. post = Post.query.filter_by(title='Post The Third').first()
  3. tag = Tag.query.filter_by(name='cooking').first()
  4. print(post.title)
  5. print(post.tags)
  6. print(tag.posts)

在此處,使用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()方法,如下所示:

  1. post.tags.remove(tag)
  2. db.session.commit()
  3. print(post.tags)
  4. print(tag.posts)

在這裡,您使用remove()方法將烹飪標籤與帖子解除關聯。然後,您使用db.session.commit()方法將更改應用到數據庫中。

你将得到一個確認標籤已從帖子中移除的輸出:

Output
[<Tag "tech">, <Tag "writing">] []

正如你所看到的,cooking 標籤不再在 post.tags 列表中,該帖子已從 tag.posts 列表中刪除。

退出 Flask shell:

  1. exit()

你已添加了新的帖子和標籤。你給帖子打了標籤,也從帖子中刪除了標籤。接下來,你將在 Flask 網誌的首頁中顯示每個帖子的標籤。

步驟 4 — 在每個帖子下顯示標籤

在這一步中,你將編輯首頁模板,以在每個帖子下顯示標籤。

首先,看一下 Flask 網誌目前的首頁。

在啟用你的編程環境後,使用 FLASK_APP 環境變量告訴 Flask 關於應用程序(在這種情況下為 app.py)。然後將 FLASK_ENV 環境變量設置為 development 以在開發模式下運行應用程序:

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

接下來,運行應用程序:

  1. flask run

隨著開發服務器運行,訪問你的瀏覽器中的以下 URL:

http://127.0.0.1:5000/

你將看到類似以下的頁面:

保持開發服務器運行,並打開一個新的終端窗口。

您需要在两个页面上显示每个帖子的标签:在索引页面上的每个帖子下方以及帖子页面上的帖子内容下方。您将使用相同的代码来显示标签。为了避免重复编码,您将使用一个Jinja宏,它的行为类似于Python函数。宏包含动态HTML代码,可以在调用宏的任何地方显示,并且编辑它会在调用它的任何地方应用更改,这使得代码可重用。

首先,在您的templates目录中打开一个名为macros.html的新文件:

  1. nano templates/macros.html

将以下代码添加到其中:

flask_app/templates/macros.html
{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
                <a href="#" style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

保存并关闭文件。

在这里,您使用macro关键字声明了一个名为display_tags()的宏,带有一个名为post的参数。您使用<div>标签,在其中显示一个<h4>标题。您使用for循环遍历将作为参数传递给宏的帖子对象的标签,类似于在Python函数调用中传递参数。您通过post.tags获取标签。您在<a>标签内显示标签名称。稍后将编辑href属性的值,以链接到您将创建的标记页面,该页面上显示了标记有特定标记的所有帖子。您使用endmacro关键字指定宏的结束。

接下来,在索引页面上的每个帖子下方显示标签,请打开index.html模板文件:

  1. nano templates/index.html

首先,您需要從macros.html文件中導入display_tags()宏。在{% extends 'base.html' %}行的上方添加以下導入:

flask_app/templates/index.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

接下來,編輯for post in posts循環,調用display_tags()宏,如下所示:

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>

        {{ display_tags(post) }}

        <hr>
    </div>
{% endfor %}

保存並關閉文件。

您調用display_tags()宏,將post對象傳遞給它。這將在每個帖子下顯示標籤名稱。

刷新瀏覽器中的主頁,您將看到每個帖子下顯示的標籤,如下圖所示:

接下來,您將在帖子頁面的帖子內容下添加標籤。打開post.html模板文件:

  1. nano templates/post.html

首先,在頂部導入display_tags宏:

flask_app/templates/post.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

然後,在帖子內容下方並在<hr>標籤上方調用display_tags()宏,將post對象傳遞給它:

flask_app/templates/post.html
<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 進行編輯:

  1. nano app.py

將以下路由添加到文件末尾:

flask_app/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 進行編輯:

  1. nano templates/tag.html

將以下代碼添加到其中:

flask_app/templates/tag.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

{% block content %}
    <span class="title">
        <h1>{% block title %} Posts Tagged with "{{ tag.name }}" {% endblock %}</h1>
    </span>
    <div class="content">
        {% for post in tag.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>

            {{ display_tags(post) }}

            <hr>
        </div>
        {% endfor %}
    </div>
{% endblock %}

保存並關閉文件。

您從macros.html中導入display_tags()宏,並擴展基礎模板。

在內容塊中,您將標題設置為包含標籤名的標題。然後,您通過tag.posts訪問透過給定標籤進行標記的帖子進行循環。您顯示了帖子ID、帖子標題和帖子內容。然後,您調用display_tags()宏來顯示所有帖子標籤。

在運行開發服務器的情況下,請瀏覽以下URL:

http://127.0.0.1:5000/tags/writing/

這是writing標籤的頁面。如您所見,顯示了所有標記為writing的帖子:

現在,編輯display_tags()宏以使標籤鏈接可用。打開macros.html

  1. nano templates/macros.html

href屬性的值編輯如下:

flask_app/templates/macros.html

{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
            <a href="{{ url_for('tag', tag_name=tag.name) }}"
               style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

保存並關閉文件。

刷新使用了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应用程序的其他教程系列。

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-many-to-many-database-relationships-with-flask-sqlalchemy