Flask-SQLAlchemy による多対多のデータベース関係の使用方法

著者は、無料かつオープンソースのファンドを、寄付のためのライティングプログラムの一環として受け取ることにしました。

はじめに

Flaskは、Python言語でウェブアプリケーションを作成するための便利なツールと機能を提供する軽量なPythonウェブフレームワークです。SQLAlchemyは、リレーショナルデータベース用の効率的で高性能なデータベースアクセスを提供するSQLツールキットです。SQLite、MySQL、およびPostgreSQLなどのさまざまなデータベースエンジンとやり取りする方法を提供します。データベースのSQL機能にアクセスできます。また、Pythonオブジェクトとメソッドを使用してデータをクエリおよび操作するためのオブジェクト関係マッパー(ORM)も提供します。Flask-SQLAlchemyは、Flask拡張機能であり、FlaskとSQLAlchemyを使用することを容易にし、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を使用した一対多のデータベース関係の利用方法のチュートリアルである「Flask-SQLAlchemyを使用した一対多のデータベース関係の利用方法」の続きでもあります。このチュートリアルでは、投稿とコメントの間に一対多の関係を持つマルチテーブルデータベースを構築します。これはブログアプリケーションです。

チュートリアルの最後までに、あなたのアプリケーションには、投稿にタグを追加する新機能が追加されます。投稿には複数のタグを付けることができ、各タグページにはそれにタグ付けされたすべての投稿が表示されます。

前提条件

ステップ1 — ウェブアプリケーションのセットアップ

このステップでは、ブログアプリケーションを変更の準備ができるようにセットアップします。また、Flask-SQLAlchemyのデータベースモデルとFlaskのルートを確認して、アプリケーションの構造を理解します。事前条件のセクションでチュートリアルに従った場合で、コードと仮想環境がローカルマシンにまだある場合は、このステップをスキップできます。

Flask-SQLAlchemyを使用してFlask Webアプリケーションに多対多の関係を追加する方法を示すために、以前のチュートリアルのアプリケーションコードを使用します。これは、投稿を追加して表示し、投稿にコメントし、既存のコメントを読み取りおよび削除する機能を備えたブログシステムです。

リポジトリをクローンして、次のコマンドで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.pyです。FLASK_ENVはモードを指定します。開発モードに設定し、これによりアプリケーションをデバッグできるようにします。本番環境ではこのモードを使用しないでください。

次に、データベーステーブルを作成するためにFlaskシェルを開きます:

  1. flask shell

次に、db.create_all()関数を使用して、db Flask-SQLAlchemyデータベースオブジェクト、Postモデル、およびCommentモデルをインポートし、データベーステーブルを作成します:

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

次に、init_db.pyプログラムを使用してデータベースをポピュレートします:

  1. python init_db.py

これにより、データベースに3つの投稿と4つのコメントが追加されます。

開発サーバーを実行します:

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

ここでは、2つのテーブルを表す2つのデータベースモデルがあります:

  • Post:ID列、タイトル、コンテンツを持ち、コメントテーブルとのOne-to-Manyの関係を持ちます。

  • Comment:ID列、コンテンツの列、コメントが属する投稿を参照するpost_id 列を持ちます。

モデルの下には次のルートがあります:

  • /:データベース内のすべての投稿を表示するインデックスページ。
  • /<int:post_id>/:個々の投稿ページ。たとえば、リンク http://127.0.0.1:5000/2/ は、データベース内の2番目の投稿の詳細とそのコメントを表示します。
  • /comments/: データベース内のすべてのコメントを表示し、各コメントが投稿された投稿へのリンクを表示するページ。
  • /comments/<int:comment_id>/delete: コメントを削除ボタンを介してコメントを削除するルート。

app.pyファイルを閉じます。

次のステップでは、多対多の関係を使用して2つのテーブル間のリンクを作成します。

ステップ2 — 多対多の関係のデータベースモデルのセットアップ

このステップでは、タグテーブルを表すデータベースモデルを追加します。既存の投稿テーブルと関連付けるために、関連テーブルを使用します。関連テーブルは、多対多の関係で2つのテーブルを接続するテーブルです。多対多の関係は、1つのテーブルの各アイテムが他のテーブルの多くの関連アイテムを持つ場合に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のタグを付けたいとします。これは、次のように投稿テーブルに新しい行を追加することで行うことができます:

Posts
+----+-----------------------------------+------+
| id | content                           | tags |
+----+-----------------------------------+------+
| 1  | A post on life and death          | 1, 2 |
| 2  | A post on joy                     |      |
+----+------------------------------------------+

このアプローチは機能しません。なぜなら、各列には1つの値しか持たないべきだからです。複数の値があると、データの追加や更新などの基本的な操作が煩雑になり、遅くなります。代わりに、関連するテーブルの主キーを参照する第3のテーブルが必要です。このテーブルはしばしば関連テーブルまたは結合テーブルと呼ばれ、各テーブルの各アイテムのIDを格納します。

以下は、投稿とタグの間の関連テーブルの例です:

post_tag
+----+---------+-------------+
| id | post_id | tag_id      |
+----+---------+-------------+
| 1  | 1       | 1           |
| 2  | 1       | 2           |
+----+---------+-------------+

最初の行では、ID 1(つまり生と死に関する投稿)の投稿が、ID 1life)のタグに関連付けられています。2番目の行では、同じ投稿がID 2death)のタグにも関連付けられています。これは、投稿にlifedeathの両方のタグが付けられていることを意味します。同様に、各投稿に複数のタグを付けることができます。

次に、app.pyファイルを変更して、タグを格納するために使用するテーブルを表す新しいデータベースモデルを追加します。また、投稿をタグと関連付けるpost_tagという関連テーブルを追加します。

まず、投稿とタグの関係を確立するためにapp.pyを開きます:

  1. nano app.py

dbオブジェクトの下にpost_tagテーブルとTagモデルを追加し、Postモデルの上に配置し、Postモデルでpost.tags経由で投稿のタグにアクセスし、tag.posts経由でタグの投稿にアクセスできるようにtags関連擬似列を追加します:

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()関数を使用して、2つの列を持つテーブルを作成します。関連テーブルでは、データベースモデルの代わりにテーブルを使用するのがベストプラクティスです。

  • post_id:投稿IDを表す整数型外部キーで、postテーブルのID列を参照します。
  • tag_id:タグIDを表す整数型外部キーで、tagテーブルのID列を参照します。

これらのキーは、テーブル間の関係を確立します。

  • id:タグのID。

各タグオブジェクトにデバッグ目的で明確な文字列表現を提供するために、タグの名前を特別な__repr__()メソッドで使用します。

tagsクラス変数をPostモデルに追加します。 db.relationship()メソッドを使用し、タグモデル(この場合はTag)の名前を渡します。

投稿とタグの間の多対多の関係を確立するために、secondaryパラメータにpost_tag関連テーブルを渡します。

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)  # 3番目の投稿に 'cooking' をタグ付け
post3.tags.append(tag2)  # 3番目の投稿に 'tech' をタグ付け
post3.tags.append(tag4)  # 3番目の投稿に '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モデルを使用して4つのタグを作成します。

次に、app.pyファイルのtags = db.relationship('Tag', secondary=post_tag, backref='posts')行を介して追加されたtags属性を使用して、投稿にタグを追加します。タグを投稿に割り当てるには、Pythonのリストと同様にappend()メソッドを使用します。

その後、作成したタグをデータベースセッションに追加するためにdb.session.add_all()関数を使用します。

注意:

db.create_all()関数は、テーブルが既に存在する場合にはテーブルを再作成または更新しません。たとえば、モデルに新しい列を追加してdb.create_all()関数を実行した場合、テーブルがデータベースに既に存在する場合、モデルへの変更がテーブルに適用されません。解決策は、init_db.pyファイルで示されているように、db.drop_all()関数を使用して既存のすべてのデータベーステーブルを削除し、db.create_all()関数を使用してそれらを再作成することです。

このプロセスは、モデルへの変更を適用しますが、データベース内のすべての既存データも削除します。データベースを更新して既存データを保持するには、スキーママイグレーションを使用する必要があります。これにより、テーブルを変更してデータを保持することができます。SQLAlchemyスキーママイグレーションをFlaskコマンドラインインターフェイスを介して実行するには、Flask-Migrate拡張機能を使用できます。

変更をデータベースに適用するには、init_db.pyプログラムを実行します:

  1. python init_db.py

このプログラムは、出力なしで正常に実行されるはずです。エラーが表示された場合は、init_db.pyファイルを適切に変更したことを確認してください。

データベースに現在存在する投稿とタグを確認するには、Flaskシェルを開きます:

  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 [] ---

Flaskシェルを使用して実行できる次の例を示します:

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: ------------------------------

投稿に追加したタグが、それらがタグ付けされた投稿と適切にリンクされていることがわかります。

特定のタグが付いた投稿にアクセスする方法のデモを見るには、次のコードをFlaskシェルで実行してください:

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.postsを介して特定のタグが付いた投稿にアクセスする方法を示します。まずTagモデルをインポートします。次に、query属性上のfilter_by()メソッドを使用して、nameパラメータを渡してwritingタグを名前で取得し、first()メソッドを使用して最初の結果を取得します。タグオブジェクトをwriting_tagという変数に格納します。filter_byメソッドの詳細については、Flaskアプリケーションでデータベースとのやり取りにFlask-SQLAlchemyを使用する方法のステップ4チュートリアルを参照してください。

writingタグが付いた投稿をループ処理し、writing_tag.postsを介してアクセスします。投稿のタグにアクセスするには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タグが付いた2つの投稿が表示され、タグ名がPythonリストで表示されています。

これで投稿とそのタグにアクセスし、特定のタグの投稿にアクセスできるようになりました。

データベースモデルにタグテーブルを表すモデルを追加しました。関連テーブルを使用して投稿とタグをリンクし、いくつかのタグをデータベースに挿入し、投稿にタグを付けました。投稿とそのタグ、個々のタグの投稿にアクセスしました。次に、Flaskシェルを使用して新しい投稿と新しいタグを追加し、タグと投稿の間のリンクを追加し、投稿からタグを削除する方法を学びます。

ステップ3 — 多対多の関係でデータを管理する

このステップでは、Flaskシェルを使用してデータベースに新しい投稿を追加し、タグを追加し、投稿とタグの間にリンクを付けます。投稿とそれらのタグにアクセスし、多対多の関係でアイテムを切り離す方法を示します。

まず、プログラミング環境をアクティブにして、Flaskシェルを開きます(まだ開いていない場合は):

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

これにより、2つの投稿と3つのタグが作成されます。関連するタグを持つ投稿にタグを付け、新しく作成されたアイテムをデータベースセッションに追加するためにadd_all()メソッドを使用します。その後、commit()メソッドを使用して変更をコミットし、データベースに適用します。

次に、Flaskシェルを使用して前の手順と同様にすべての投稿とそのタグを取得します:

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

投稿がタグとともに追加されたことがわかります。

関係データベース内の多対多の関係を解除する方法を示すために、Post The Third投稿がもはや料理に関するものではないとします。そのため、それからcookingタグを削除する必要があります。

まず、削除したい投稿とタグを取得します。

  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()メソッドを使用してPost The Thirdというタイトルの投稿を取得します。そして、cookingタグを取得します。投稿のタイトル、そのタグ、およびcookingタグが付けられた投稿を表示します。

filter_by()メソッドはクエリオブジェクトを返し、all()メソッドを使用してすべての結果のリストを取得できます。しかし、この場合は1つの結果のみを期待しているため、first()メソッドを使用して最初(かつ唯一)の結果を取得します。 first()メソッドとall()メソッドについては、Flaskアプリケーションでデータベースとのやり取りにFlask-SQLAlchemyを使用する方法のステップ4を参照してください。

次の出力が得られます。

Output
Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] [<Post "Post The Third">]

ここでは、投稿のタイトル、投稿のタグ、およびcookingタグが付いた投稿のリストが表示されます。

投稿からcookingタグを削除するには、次のようにremove()メソッドを使用します。

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

ここでは、remove()メソッドを使用してcookingタグを投稿から関連付けを解除します。その後、db.session.commit()メソッドを使用してデータベースへの変更を適用します。

次に、投稿からタグが削除されたことを確認する出力が得られます:

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

ご覧のように、cooking タグがもはや post.tags リストにありませんし、投稿は tag.posts リストから削除されました。

Flaskシェルを終了します:

  1. exit()

新しい投稿とタグを追加しました。投稿にタグを付け、投稿からタグを削除しました。次に、Flaskウェブブログのインデックスページにおいて、各投稿のタグを表示します。

ステップ4 — 各投稿の下にタグを表示する

このステップでは、インデックステンプレートを編集して、各投稿の下にタグを表示します。

まず、Flaskウェブブログの現在のホームページを見てみましょう。

プログラミング環境がアクティブになっていることを確認し、Flaskにアプリケーション(この場合は app.py)を知らせるために FLASK_APP 環境変数を使用します。次に、開発モードでアプリケーションを実行するために、FLASK_ENV 環境変数を development に設定します:

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

次に、アプリケーションを実行します:

  1. flask run

開発サーバーが実行されている状態で、ブラウザで次のURLにアクセスします:

http://127.0.0.1:5000/

次のようなページが表示されます:

開発サーバーを実行したままにして、新しいターミナルウィンドウを開きます。

各投稿のタグを2つのページに表示する必要があります:インデックスページの各投稿の下と、投稿ページの投稿コンテンツの下に表示します。 同じコードを使用してタグを表示します。 コードの反復を避けるために、Jinjaマクロを使用します。これはPython関数のように動作します。 マクロは、マクロを呼び出す場所で表示される動的なHTMLコードを保持し、それを編集すると、それが呼び出された場所に変更が適用されるため、コードが再利用可能になります。

まず、macros.htmlという新しいファイルをtemplatesディレクトリに開きます。

  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キーワードを使用して、postというパラメータを持つdisplay_tags()というマクロを宣言します。 <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()マクロを呼び出します:

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 — タグとその投稿の表示

このステップでは、ウェブアプリケーションにデータベース内のタグとその投稿を表示するためのルートとテンプレートを追加します。

まず、各タグの投稿を表示するためのルートを追加します。たとえば、/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()ビュー関数にtag_nameパラメーター経由で渡され、それをfilter_by()メソッドでクエリしてタグを取得します。tagオブジェクトを取得して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拡張を使用して多対多の関係を管理する方法を示しています。関連する2つのテーブル間のリンクを作成し、関連するエントリを関連付け、エントリをデータベースに追加し、エントリからデータにアクセスして関連付けを解除する方法を学びました。

Flaskについて詳しく読みたい場合は、FlaskでWebアプリケーションを構築する方法 シリーズの他のチュートリアルをチェックしてください。

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