Flask-SQLAlchemyで1対多のデータベース関係を使用する方法

提供:Dev Guides
移動先:案内検索

著者は、 Write for DOnations プログラムの一環として、 Free and Open SourceFundを選択して寄付を受け取りました。

序章

Flask は、Python言語でWebアプリケーションを作成するための便利なツールと機能を提供する軽量のPythonWebフレームワークです。 SQLAlchemy は、リレーショナルデータベースに効率的で高性能なデータベースアクセスを提供するSQLツールキットです。 SQLite、MySQL、PostgreSQLなどのいくつかのデータベースエンジンと対話する方法を提供します。 これにより、データベースのSQL機能にアクセスできます。 また、オブジェクトリレーショナルマッパー(ORM)も提供します。これにより、単純なPythonオブジェクトとメソッドを使用してクエリを作成し、データを処理できます。 Flask-SQLAlchemy は、FlaskでSQLAlchemyを簡単に使用できるようにする、Flask拡張機能であり、SQLAlchemyを介してFlaskアプリケーションのデータベースと対話するためのツールとメソッドを提供します。

1対多のデータベース関係は、あるテーブルのレコードが別のテーブルの複数のレコードを参照できる2つのデータベーステーブル間の関係です。 たとえば、ブログアプリケーションでは、投稿を保存するためのテーブルは、コメントを保存するためのテーブルと1対多の関係を持つことができます。 各投稿は多くのコメントを参照でき、各コメントは1つの投稿を参照します。 したがって、1つの投稿には多くのコメントとの関係があります。 postテーブルは親テーブルであり、commentsテーブルは子テーブルです。親テーブルのレコードは、子テーブルの多くのレコードを参照できます。 この関係は、各テーブルの関連データにアクセスできるようにするために重要です。

このチュートリアルでは、Flask-SQLAlchemy拡張機能を使用して1対多の関係を構築する方法を示す小さなブログシステムを構築します。 投稿とコメントの間に関係を作成します。各ブログ投稿には複数のコメントを含めることができます。

前提条件

ステップ1—FlaskとFlaskのインストール-SQLAlchemy

このステップでは、アプリケーションに必要なパッケージをインストールします。

仮想環境をアクティブにした状態で、pipを使用してFlaskとFlask-SQLAlchemyをインストールします。

pip install Flask Flask-SQLAlchemy

インストールが正常に完了すると、出力の最後に次のような行が表示されます。

OutputSuccessfully installed Flask-2.1.1 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.1 MarkupSafe-2.1.1 SQLAlchemy-1.4.35 Werkzeug-2.1.1 click-8.1.2 greenlet-1.1.2 itsdangerous-2.1.2

必要なPythonパッケージがインストールされたら、次にデータベースをセットアップします。

ステップ2—データベースとモデルの設定

このステップでは、データベースをセットアップし、SQLAlchemyデータベースモデル—データベーステーブルを表すPythonクラスを作成します。 ブログ投稿のモデルとコメントのモデルを作成します。 データベースを開始し、投稿用のテーブルを作成し、宣言するモデルに基づいてコメント用のテーブルを追加します。 また、データベースにいくつかの投稿とコメントを挿入します。

データベース接続の設定

flask_appディレクトリにあるapp.pyというファイルを開きます。 このファイルには、データベースとFlaskルートを設定するためのコードが含まれています。

nano app.py

このファイルはdatabase.dbというSQLiteデータベースに接続し、データベース投稿テーブルを表すPostというクラスと、コメントを表すCommentクラスの2つのクラスがあります。テーブル。 このファイルには、Flaskルートも含まれます。 app.pyの先頭に次のimportステートメントを追加します。

フラスコ_app/app.py

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

ここでは、 osモジュールをインポートします。これにより、さまざまなオペレーティングシステムインターフェイスにアクセスできます。 これを使用して、database.dbデータベースファイルのファイルパスを作成します。

次に、flaskパッケージから、アプリケーションに必要なヘルパーをインポートします。Flaskアプリケーションインスタンスを作成するためのFlaskクラス、テンプレートをレンダリングするためのrender_template()関数、リクエストを処理するrequestオブジェクト、ルートのURLを作成するurl_for()関数、ユーザーをリダイレクトするredirect()関数。 ルートとテンプレートの詳細については、Flaskアプリケーションでテンプレートを使用する方法を参照してください。

次に、Flask-SQLAlchemy拡張機能からSQLAlchemyクラスをインポートします。これにより、ヘルパーに加えて、SQLAlchemyのすべての関数とクラス、およびFlaskをSQLAlchemyと統合する機能にアクセスできます。 これを使用して、Flaskアプリケーションに接続するデータベースオブジェクトを作成します。これにより、SQL言語を使用せずに、Pythonクラス、オブジェクト、および関数を使用してテーブルを作成および操作できます。

インポートの下で、データベースファイルパスを設定し、Flaskアプリケーションをインスタンス化し、アプリケーションを構成してSQLAlchemyに接続します。 次のコードを追加します。

フラスコ_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アプリケーションインスタンスを作成します。これを使用して、2つの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クラスを使用してデータベースオブジェクトを作成し、アプリケーションインスタンスを渡してFlaskアプリケーションをSQLAlchemyに接続します。 データベースオブジェクトをdbという変数に格納します。 このdbオブジェクトを使用して、データベースを操作します。

テーブルの宣言

データベース接続が確立され、データベースオブジェクトが作成されたら、データベースオブジェクトを使用して、投稿用のデータベーステーブルとコメント用のデータベーステーブルを作成します。 テーブルはmodelで表されます—以前に作成したdbデータベースインスタンスを介してFlask-SQLAlchemyが提供する基本クラスを継承するPythonクラスです。 投稿テーブルとコメントテーブルをモデルとして定義するには、app.pyファイルに次の2つのクラスを追加します。

フラスコ_app/app.py

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]}...">'

ここでは、db.Modelクラスから継承するPostモデルとCommentモデルを作成します。

Postモデルはポストテーブルを表します。 db.Column クラスを使用して、その列を定義します。 最初の引数は列タイプを表し、追加の引数は列構成を表します。

Postモデルには次の列を定義します。

  • id:投稿ID。 db.Integerを使用して整数として定義します。 primary_key=Trueは、この列を主キーとして定義します。これにより、データベースによって各エントリ(つまり、各投稿)に一意の値が割り当てられます。
  • title:投稿のタイトル。 最大長が100文字の文字列。
  • content:投稿のコンテンツ。 db.Textは、列が長いテキストを保持していることを示します。

commentsクラス属性は、PostモデルとCommentモデルの間の1対多の関係を定義します。 db.relationship()メソッドを使用して、コメントモデルの名前(この場合はComment)を渡します。 backrefパラメーターを使用して、列のように動作する後方参照をCommentモデルに追加します。 このようにして、post属性を使用して、コメントが投稿された投稿にアクセスできます。 たとえば、commentという変数にコメントオブジェクトがある場合、comment.postを使用してコメントが属する投稿にアクセスできます。 これを示す例は後で表示されます。

前のコードブロックで使用したタイプ以外の列タイプについては、SQLAlchemyのドキュメントを参照してください。

特別な__repr __ 関数を使用すると、デバッグ目的で各オブジェクトを認識するための文字列表現を各オブジェクトに与えることができます。

Commentモデルはコメントテーブルを表します。 次の列を定義します。

  • id:コメントID。 db.Integerを使用して整数として定義します。 primary_key=Trueは、この列を主キーとして定義します。これにより、データベースによって各エントリ(つまり、各コメント)に一意の値が割り当てられます。
  • content:コメントの内容。 db.Textは、列が長いテキストを保持していることを示します。
  • post_iddb.ForeignKey()クラスを使用して作成する整数外部キー。これは、テーブルの主キーを使用してテーブルを別のテーブルにリンクするキーです。 これにより、投稿の主キーであるIDを使用して、コメントが投稿にリンクされます。 ここで、postテーブルは親テーブルであり、各投稿に多くのコメントがあることを示しています。 commentテーブルは子テーブルです。 各コメントは、投稿のIDを使用して親投稿に関連付けられています。 したがって、各コメントには、コメントが投稿された投稿にアクセスするために使用できるpost_id列があります。

Commentモデルの特別な__repr __ 関数は、コメントのコンテンツの最初の20文字を表示して、コメントオブジェクトに短い文字列表現を提供します。

app.pyファイルは次のようになります。

フラスコ_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.pyを保存して閉じます。

データベースの作成

データベース接続と投稿およびコメントモデルを設定したので、Flaskシェルを使用して、宣言したモデルに基づいてデータベースと投稿およびコメントテーブルを作成します。

仮想環境をアクティブにした状態で、FLASK_APP環境変数を使用して、app.pyファイルをFlaskアプリケーションとして設定します。

export FLASK_APP=app

次に、flask_appディレクトリで次のコマンドを使用してFlaskシェルを開きます。

flask shell

Pythonインタラクティブシェルが開きます。 この特別なシェルは、Flaskアプリケーションのコンテキストでコマンドを実行するため、呼び出すFlask-SQLAlchemy関数がアプリケーションに接続されます。

データベースオブジェクトと投稿モデルおよびコメントモデルをインポートしてから、db.create_all()関数を実行して、モデルに関連付けられているテーブルを作成します。

from app import db, Post, Comment
db.create_all()

シェルを実行したまま、別のターミナルウィンドウを開き、flask_appディレクトリに移動します。 これで、flask_appdatabase.dbという新しいファイルが表示されます。

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

db.drop_all()
db.create_all()

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


エラーが発生した場合は、データベースURIとモデル宣言が正しいことを確認してください。

テーブルへの入力

データベースと投稿テーブルおよびコメントテーブルを作成したら、flask_appディレクトリにファイルを作成して、データベースに投稿とコメントを追加します。

init_db.pyという名前の新しいファイルを開きます。

nano init_db.py

次のコードを追加します。 このファイルは、3つの投稿オブジェクトと4つのコメントオブジェクトを作成し、それらをデータベースに追加します。

フラスコ_app/init_db.py

from app import db, Post, Comment

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)


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

db.session.commit()

ファイルを保存して閉じます。

ここでは、データベースオブジェクト、Postモデル、およびCommentモデルをapp.pyファイルからインポートします。

Postモデルを使用していくつかの投稿オブジェクトを作成し、投稿のタイトルをtitleパラメーターに渡し、投稿のコンテンツをcontentパラメーターに渡します。

次に、コメントのコンテンツを渡して、いくつかのコメントオブジェクトを作成します。 コメントをそれが属する投稿に関連付けるために使用できる2つの方法があります。 comment1およびcomment2オブジェクトに示されているように、postオブジェクトをpostパラメーターに渡すことができます。 また、comment3およびcomment4オブジェクトに示されているように、投稿IDをpost_idパラメーターに渡すこともできます。 したがって、コードにpostオブジェクトがない場合は、postの整数IDを渡すことができます。

投稿オブジェクトとコメントオブジェクトを定義した後、db.session.add_all()を使用して、トランザクションを管理するデータベースセッションにすべての投稿オブジェクトとコメントオブジェクトを追加します。 次に、db.session.commit()メソッドを使用してトランザクションをコミットし、変更をデータベースに適用します。 SQLAlchemyデータベースセッションの詳細については、FlaskアプリケーションチュートリアルでFlask-SQLAlchemyを使用してデータベースと対話する方法のステップ2を参照してください。

init_db.pyファイルを実行してコードを実行し、データベースにデータを追加します。

python init_db.py

データベースに追加したデータを確認するには、フラスコシェルを開いてすべての投稿をクエリし、それらのタイトルと各投稿のコメントの内容を表示します。

flask shell

次のコードを実行します。 これにより、すべての投稿がクエリされ、各投稿のタイトルとその下の各投稿のコメントが表示されます。

from app import Post

posts = Post.query.all()

for post in posts:
    print(f'## {post.title}')
    for comment in post.comments:
            print(f'> {comment.content}')
    print('----')

ここでは、app.pyファイルからPostモデルをインポートします。 query属性でall()メソッドを使用してデータベースに存在するすべての投稿をクエリし、結果をpostsという変数に保存します。 次に、forループを使用して、posts変数の各項目を調べます。 タイトルを印刷してから、別のforループを使用して、投稿に属する各コメントを調べます。 post.commentsを使用して投稿のコメントにアクセスします。 コメントの内容を印刷してから、文字列'----'を印刷して投稿を区切ります。

次の出力が得られます。

Output
## Post The First
> Comment for the first post
> Another comment for the first post
----
## Post The Second
> Comment for the second post
> Another comment for the second post
----
## Post The Third
----

ご覧のとおり、ごくわずかなコードで各投稿のデータと各投稿のコメントにアクセスできます。

次に、シェルを終了します。

exit()

この時点で、データベースにいくつかの投稿とコメントがあります。 次に、インデックスページのFlaskルートを作成し、データベース内のすべての投稿をそのページに表示します。

ステップ3—すべての投稿を表示する

このステップでは、データベース内のすべての投稿をインデックスページに表示するためのルートとテンプレートを作成します。

app.pyファイルを開いて、インデックスページのルートを追加します。

nano app.py

ファイルの最後に次のルートを追加します。

フラスコ_app/app.py

# ...

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

ファイルを保存して閉じます。

ここでは、app.route()デコレータを使用してindex()ビュー関数を作成します。 この関数では、前の手順で行ったように、データベースにクエリを実行してすべての投稿を取得します。 クエリ結果をpostsという変数に保存し、render_template()ヘルパー関数を使用してレンダリングするindex.htmlテンプレートファイルに渡します。

データベース内の既存の投稿を表示するindex.htmlテンプレートファイルを作成する前に、まずベーステンプレートを作成します。このテンプレートには、他のテンプレートがコードを回避するために使用するすべての基本的なHTMLコードが含まれます。繰り返し。 次に、index()関数でレンダリングしたindex.htmlテンプレートファイルを作成します。 テンプレートの詳細については、Flaskアプリケーションでテンプレートを使用する方法を参照してください。

templatesディレクトリを作成し、base.htmlという名前の新しいテンプレートを開きます。

mkdir templates
nano templates/base.html

base.htmlファイル内に次のコードを追加します。

フラスコ_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;
        }

        .comment {
            padding: 10px;
            margin: 10px;
            background-color: #fff;
        }

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

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

ファイルを保存して閉じます。

この基本テンプレートには、他のテンプレートで再利用する必要があるすべてのHTMLボイラープレートが含まれています。 titleブロックは各ページのタイトルを設定するために置き換えられ、contentブロックは各ページのコンテンツに置き換えられます。 ナビゲーションバーには3つのリンクがあります。1つはurl_for()ヘルパー機能を使用してindex()ビュー機能にリンクするインデックスページ用、もう1つはコメントページ用です。アプリケーションに追加することを選択した場合は、Aboutページの場合。 コメントリンクを機能させるために最新のコメントをすべて表示するページを追加した後、このファイルを後で編集します。

次に、新しいindex.htmlテンプレートファイルを開きます。 これは、app.pyファイルで参照したテンプレートです。

nano templates/index.html

次のコードを追加します。

フラスコ_app/templates / index.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Posts {% endblock %}</h1></span>
    <div class="content">
        {% for post in posts %}
            <div class="post">
                <p><b>#{{ post.id }}</b></p>
                <b>
                    <p class="title">
                        <a href="#">
                            {{ post.title }}
                        </a>
                    </p>
                </b>
                <div class="content">
                    <p>{{ post.content }}</p>
                </div>
                <hr>
            </div>
        {% endfor %}
    </div>
{% endblock %}

ファイルを保存して閉じます。

ここでは、ベーステンプレートを拡張し、コンテンツブロックのコンテンツを置き換えます。 タイトルを兼ねる<h1>の見出しを使用します。 {% for post in posts %}行のJinjafor loop を使用して、index()ビュー関数からこれに渡したposts変数の各投稿を調べます。レンプレート。 投稿ID、タイトル、投稿内容を表示します。 投稿のタイトルは、後で個々の投稿とそのコメントを表示するページにリンクされます。

仮想環境をアクティブにしてflask_appディレクトリにいるときに、FLASK_APP環境変数を使用して、アプリケーション(この場合はapp.py)についてFlaskに通知します。 次に、FLASK_ENV環境変数をdevelopmentに設定して、アプリケーションを開発モードで実行し、デバッガーにアクセスします。 Flaskデバッガーの詳細については、Flaskアプリケーションでエラーを処理する方法を参照してください。 これを行うには、次のコマンドを使用します。

export FLASK_APP=app
export FLASK_ENV=development

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

flask run

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

http://127.0.0.1:5000/

次のようなページに、データベースに追加した投稿が表示されます。

データベースにある投稿をインデックスページに表示しました。 次に、投稿ページのルートを作成します。ここには、各投稿の詳細とその下のコメントが表示されます。

ステップ4—単一の投稿とそのコメントを表示する

このステップでは、ルートとテンプレートを作成して、各投稿の詳細を専用ページに表示し、その下に投稿のコメントを表示します。

このステップの終わりまでに、URL http://127.0.0.1:5000/1は、最初の投稿(ID 1を持っているため)とそのコメントを表示するページになります。 URL http://127.0.0.1:5000/IDは、関連するID番号(存在する場合)を含む投稿を表示します。

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

変更するには、app.pyを開きます。

nano app.py

ファイルの最後に次のルートを追加します。

フラスコ_app/app.py

# ...

@app.route('/<int:post_id>/')
def post(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post.html', post=post)

ファイルを保存して閉じます。

ここでは、ルート'/<int:post_id>/'を使用します。ここで、int:は、URLのデフォルトの文字列を整数に変換するコンバーターです。 post_idは、ページに表示する投稿を決定するURL変数です。

IDは、post_idパラメーターを介してURLからpost()ビュー関数に渡されます。 関数内で、postテーブルをクエリし、get_or_404()メソッドを使用してIDで投稿を取得します。 これにより、投稿データがpost変数に保存され、指定されたIDの投稿がデータベースに存在しない場合は、404 Not FoundHTTPエラーで応答します。

post.htmlというテンプレートをレンダリングし、取得した投稿を渡します。

この新しいpost.htmlテンプレートファイルを開きます。

nano templates/post.html

次のコードを入力します。 これは、index.htmlテンプレートに似ていますが、単一の投稿のみが表示される点が異なります。

フラスコ_app/templates / post.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} {{ post.title }}  {% endblock %}</h1></span>
    <div class="content">
            <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>
                <hr>
                <h3>Comments</h3>
                {% for comment in post.comments %}
                    <div class="comment">
                        <p>#{{ comment.id }}</p>
                        <p>{{ comment.content }}</p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

ファイルを保存して閉じます。

ここでは、基本テンプレートを拡張し、投稿タイトルをページタイトルとして設定し、投稿ID、投稿タイトル、および投稿コンテンツを表示します。 次に、post.commentsから入手できる投稿コメントを確認します。 コメントIDとコメントの内容を表示します。

ブラウザを使用して、2番目の投稿のURLに移動します。

http://127.0.0.1:5000/2/

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

次に、index.htmlを編集して、投稿のタイトルを個々の投稿にリンクします。

nano templates/index.html

forループ内の投稿タイトルのリンクのhref属性の値を編集します。

フラスコ_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>
        <hr>
    </div>
{% endfor %}

ファイルを保存して閉じます。

インデックスページに移動するか、更新します。

http://127.0.0.1:5000/

インデックスページの各投稿タイトルをクリックします。 これで、各投稿が適切な投稿ページにリンクしていることがわかります。

これで、個々の投稿を表示するためのページが作成されました。 次に、投稿ページにWebフォームを追加して、ユーザーが新しいコメントを追加できるようにします。

ステップ5—新しいコメントを追加する

このステップでは、/<int:post_id>/ルートと、個々の投稿の表示を処理するpost()ビュー機能を編集します。 各投稿の下にWebフォームを追加して、ユーザーがその投稿にコメントを追加できるようにします。次に、コメントの送信を処理してデータベースに追加します。

まず、post.htmlテンプレートファイルを開いて、コメントのコンテンツのテキスト領域とコメントの追加送信ボタンで構成されるWebフォームを追加します。

nano templates/post.html

Comments H3見出しの下、およびforループのすぐ上にフォームを追加して、ファイルを編集します。

フラスコ_app/templates / post.html

<hr>
<h3>Comments</h3>
<form method="post">
    <p>
        <textarea name="content"
                    placeholder="Comment"
                    cols="60"
                    rows="5"></textarea>
    </p>

    <p>
        <button type="submit">Add comment</button>
    </p>
</form>
{% for comment in post.comments %}

ファイルを保存して閉じます。

ここでは、属性methodpostに設定された<form>タグを追加して、フォームがPOSTリクエストを送信することを示します。

コメントのコンテンツ用のテキスト領域と送信ボタンがあります。

開発サーバーが実行されている状態で、ブラウザーを使用して投稿に移動します。

http://127.0.0.1:5000/2/

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

このフォームはPOSTリクエストをpost()ビュー機能に送信しますが、フォームの送信を処理するコードがないため、フォームは現在機能しません。

次に、post()ビュー関数にコードを追加して、フォームの送信を処理し、新しいコメントをデータベースに追加します。 app.pyを開いて、ユーザーが送信するPOST要求を処理します。

nano app.py

/<int:post_id>/ルートとそのpost()ビュー機能を次のように編集します。

フラスコ_app/app.py

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

ファイルを保存して閉じます。

methodsパラメーターを使用して、GET要求とPOST要求の両方を許可します。 GETリクエストは、サーバーからデータを取得するために使用されます。 POSTリクエストは、特定のルートにデータを投稿するために使用されます。 デフォルトでは、GETリクエストのみが許可されます。

if request.method == 'POST'条件内で、ユーザーがフォームを介して送信するPOST要求を処理します。 Commentモデルを使用してコメントオブジェクトを作成し、request.formオブジェクトから抽出した送信済みコメントのコンテンツを渡します。 コメントが属する投稿をpostパラメーターを使用して指定し、投稿IDを使用して取得したpostオブジェクトを、get_or_404()メソッドで渡します。

作成したコメントオブジェクトをデータベースセッションに追加し、トランザクションをコミットして、投稿ページにリダイレクトします。

次に、ブラウザの投稿ページを更新し、コメントを書き込んで送信します。 投稿の下に新しいコメントが表示されます。

これで、ユーザーが投稿にコメントを追加できるWebフォームができました。 Webフォームの詳細については、FlaskアプリケーションでWebフォームを使用する方法を参照してください。 Webフォームを管理するためのより高度で安全な方法については、Flask-WTFを使用してWebフォームを使用および検証する方法を参照してください。 次に、データベース内のすべてのコメントとそれらが投稿された投稿を表示するページを追加します。

ステップ6—すべてのコメントを表示する

このステップでは、コメントページを追加します。このページでは、データベース内のすべてのコメントを表示し、最新のコメントを最初に表示して順序付けます。 各コメントには、コメントが投稿された投稿のタイトルとリンクが含まれます。

app.pyを開きます:

nano app.py

ファイルの最後に次のルートを追加します。 これにより、データベース内のすべてのコメントが最新のものから順にフェッチされます。 次に、それらをcomments.htmlというテンプレートファイルに渡します。このファイルは、後で作成します。

フラスコ_app/app.py

# ...

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

ファイルを保存して閉じます。

query属性でorder_by()メソッドを使用して、すべてのコメントを特定の順序でフェッチします。 この場合、Comment.id列でdesc()メソッドを使用して、コメントを降順でフェッチします。最新のコメントが最初になります。 次に、all()メソッドを使用して結果を取得し、commentsという変数に保存します。

comments.htmlというテンプレートをレンダリングし、最新のものから順に並べられたすべてのコメントを含むcommentsオブジェクトを渡します。

この新しいcomments.htmlテンプレートファイルを開きます。

nano templates/comments.html

その中に次のコードを入力します。 これにより、コメントとそれらが属する投稿へのリンクが表示されます。

フラスコ_app/templates/comments.html

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Latest Comments {% endblock %}</h1></span>
    <div class="content">
                {% for comment in comments %}
                    <div class="comment">
                        <i>
                            (#{{ comment.id }})
                            <p>{{ comment.content }}</p>
                        </i>
                        <p class="title">
                        On <a href="{{ url_for('post',
                                                post_id=comment.post.id) }}">
                                {{ comment.post.title }}
                              </a>
                        </p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

ファイルを保存して閉じます。

ここでは、基本テンプレートを拡張し、タイトルを設定し、forループを使用してコメントを確認します。 コメントのID、その内容、およびコメントが属する投稿へのリンクを表示します。 comment.postを介して投稿データにアクセスします。

ブラウザを使用してコメントページに移動します。

http://127.0.0.1:5000/comments/

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

次に、base.htmlテンプレートを編集して、コメントナビゲーションバーのリンクがこのコメントページを指すようにします。

nano templates/base.html

ナビゲーションバーを編集して、次のようにします。

フラスコ_app/templates / base.html

    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('comments') }}">Comments</a>
        <a href="#">About</a>
    </nav>

ファイルを保存して閉じます。

コメントページを更新すると、コメントナビゲーションバーリンクが機能することがわかります。

これで、データベース内のすべてのコメントを表示するページができました。 次に、投稿ページの各コメントの下にボタンを追加して、ユーザーがコメントを削除できるようにします。

ステップ7—コメントの削除

この手順では、各コメントの下にコメントの削除ボタンを追加して、ユーザーが不要なコメントを削除できるようにします。

まず、POSTリクエストを受け入れる新しい/comments/ID/deleteルートを追加します。 ビュー関数は、削除するコメントのIDを受け取り、データベースからフェッチして削除し、削除されたコメントがあった投稿ページにリダイレクトします。

app.pyを開きます:

nano app.py

ファイルの最後に次のルートを追加します。

フラスコ_app/app.py

# ...

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

ファイルを保存して閉じます。

ここでは、通常のapp.routeデコレータを使用する代わりに、Flaskバージョン2.0.0で導入されたapp.post デコレータを使用します。これにより、一般的なHTTPメソッドのショートカットが追加されました。 たとえば、@app.post("/login")@app.route("/login", methods=["POST"])のショートカットです。 つまり、このビュー関数はPOSTリクエストのみを受け入れ、ブラウザで/comments/ID/deleteルートに移動すると、405 Method Not Allowedエラーが返されます。これは、WebブラウザのデフォルトがGETリクエストであるためです。 コメントを削除するには、ユーザーはこのルートにPOSTリクエストを送信するボタンをクリックします。

このdelete_comment()ビュー機能は、comment_idURL変数を介して削除されるコメントのIDを受け取ります。 get_or_404()メソッドを使用してコメントを取得し、それをcomment変数に保存するか、コメントが存在しない場合は404 Not Foundで応答します。 コメントが属する投稿の投稿IDをpost_id変数に保存します。この変数を使用して、コメントを削除した後に投稿にリダイレクトします。

db.session.delete(comment)行のデータベースセッションでdelete()メソッドを使用し、コメントオブジェクトを渡します。 これにより、トランザクションがコミットされるたびにコメントを削除するようにセッションが設定されます。 他の変更を行う必要がないため、db.session.commit()を使用してトランザクションを直接コミットします。 最後に、削除されたコメントが投稿された投稿にユーザーをリダイレクトします。

次に、post.htmlテンプレートを編集して、各コメントの下にコメントの削除ボタンを追加します。

nano templates/post.html

コメントコンテンツのすぐ下に新しい<form>タグを追加して、forループを編集します。

フラスコ_app/templates / post.html

    {% for comment in post.comments %}
        <div class="comment">
            <p>#{{ comment.id }}</p>
            <p>{{ comment.content }}</p>
            <form method="POST"
                action="{{ url_for('delete_comment',
                                    comment_id=comment.id) }}">
                <input type="submit" value="Delete Comment"
                    onclick="return confirm('Are you sure you want to delete this entry?')">
            </form>
        </div>
    {% endfor %}

ファイルを保存して閉じます。

ここに、POSTリクエストをdelete_comment()ビュー関数に送信するWebフォームがあります。 comment.idcomment_idパラメーターの引数として渡して、削除されるコメントを指定します。 Webブラウザで利用可能なconfirm()メソッド関数を使用して、リクエストを送信する前に確認メッセージを表示します。

次に、ブラウザの投稿ページに移動します。

http://127.0.0.1:5000/2/

各コメントの下にコメントの削除ボタンが表示されます。 それをクリックして、削除を確認します。 コメントが削除されたことがわかります。

これで、データベースからコメントを削除する方法があります。

結論

Flask-SQLAlchemy拡張機能を使用して1対多の関係を管理する方法を示す小さなブログシステムを構築しました。 親テーブルを子テーブルに接続する方法、子オブジェクトをその親に関連付けてデータベースに追加する方法、および親エントリから子データにアクセスする方法、およびその逆の方法を学習しました。

Flaskの詳細については、Flaskを使用してWebアプリケーションを構築する方法シリーズの他のチュートリアルをご覧ください。