Flaskアプリケーションでデータベースとやり取りするためのFlask-SQLAlchemyの使用方法

著者は、Free and Open Source Fundを、Write for DOnationsプログラムの一環として寄付先として選択しました。

導入

Webアプリケーションでは、通常、データベースが必要です。これはデータの整理されたコレクションです。データベースを使用して、取得および効率的に操作できる永続データを保存および維持します。例えば、ソーシャルメディアアプリケーションでは、ユーザーデータ(個人情報、投稿、コメント、フォロワーなど)が効率的に操作できるように保存されているデータベースがあります。データベースにデータを追加したり、取得したり、変更したり、削除したりすることができますが、これは異なる要件や条件に応じて行います。Webアプリケーションでは、これらの要件が、ユーザーが新しい投稿を追加したり、投稿を削除したり、アカウントを削除したりする場合になります。これらのデータを操作するためのアクションは、アプリケーションの特定の機能に依存します。たとえば、ユーザーがタイトルのない投稿を追加することを許可したくない場合があります。

DigitalOcean App Platformを使用してGitHubからFlaskアプリケーションをデプロイします。DigitalOceanにアプリのスケーリングを任せましょう。

Flaskは、Python言語でウェブアプリケーションを作成するための便利なツールや機能を提供する軽量なPythonウェブフレームワークです。SQLAlchemyは、リレーショナルデータベースに効率的で高性能なデータベースアクセスを提供するSQLツールキットです。SQLite、MySQL、およびPostgreSQLなどの複数のデータベースエンジンとやり取りする方法を提供します。データベースのSQL機能にアクセスできます。また、Object Relational Mapper(ORM)を提供し、簡単なPythonオブジェクトとメソッドを使用してクエリを作成し、データを処理できます。Flask-SQLAlchemyは、FlaskとSQLAlchemyを簡単に使用できるようにするFlask拡張機能であり、Flaskアプリケーション内でSQLAlchemyを使用してデータベースとやり取りするためのツールやメソッドを提供します。

このチュートリアルでは、Flask-SQLAlchemy拡張機能の使用方法を示す小さな学生管理システムを構築します。 Flaskと組み合わせて、データベースサーバーに接続したり、テーブルを作成したり、テーブルにデータを追加したり、データを取得したり、データベースから項目を更新したり削除したりするなど、基本的なタスクを実行します。 SQLiteを使用してSQLAlchemyを使用しますが、PostgreSQLやMySQLなどの他のデータベースエンジンでも使用できます。Python標準ライブラリには、何もインストールせずにSQLAlchemyがSQLiteデータベースとやり取りするために背後で使用されるsqlite3モジュールが提供されているため、Pythonとよく動作します。 SQliteはLinuxシステムにデフォルトでインストールされており、WindowsではPythonパッケージの一部としてインストールされています。

前提条件

ステップ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 — データベースとモデルの設定

このステップでは、データベース接続を設定し、データを格納するテーブルを表すPythonクラスであるSQLAlchemy データベースモデルを作成します。データベースを初期化し、宣言したモデルに基づいて学生用のテーブルを作成し、いくつかの学生を学生テーブルに追加します。

データベース接続の設定

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

  1. nano app.py

このファイルは、database.dbという名前のSQLiteデータベースに接続し、学生情報を格納するためのデータベース学生テーブルを表すStudentというクラスを持ち、Flaskのルートも含みます。次のimport文を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

ここで、osモジュールをインポートしています。これにより、database.dbデータベースファイルのファイルパスを構築できます。

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

次に、Flask-SQLAlchemy拡張からSQLAlchemyクラスをインポートし、これにより、SQLAlchemyのすべての関数やクラスにアクセスできるようになります。FlaskをSQLAlchemyと統合するためのヘルパーや機能に加えて、SQLAlchemyの機能も利用できます。これを使用して、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アプリケーションインスタンスを作成し、それを使用して2つのFlask-SQLAlchemy 設定キーを構成します。

  • SQLALCHEMY_DATABASE_URI:データベースとの接続を確立するためのデータベースURI。この場合、URIはsqlite:///path/to/database.dbの形式に従います。basedir変数に構築して保存したベースディレクトリとdatabase.dbファイル名をos.path.join()関数を使用してスマートに結合します。これにより、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のエンジン構成に関するドキュメントを参照してください。

SQLAlchemyを設定してデータベースURIを設定し、トラッキングを無効にした後、SQLAlchemyクラスを使用してデータベースオブジェクトを作成し、FlaskアプリケーションをSQLAlchemyで接続するためにアプリケーションインスタンスを渡します。データベースオブジェクトをdbという変数に格納します。このdbオブジェクトを使用してデータベースとやり取りします。

テーブルの宣言

データベース接続が確立され、データベースオブジェクトが作成されたので、データベースオブジェクトを使用して、学生用のデータベーステーブルを作成します。これはモデルであり、Flask-SQLAlchemyが提供するベースクラスから継承するPythonクラスです。以前に作成した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:データベース内で学生の記録が作成された時刻です。Pythonのdb.DateTimeを使用して、これをdatetimeオブジェクトとして定義します。 timezone=Trueはタイムゾーンのサポートを有効にします。server_defaultは、テーブルを作成するときにデータベースにデフォルト値を設定し、デフォルト値をモデルではなくデータベースで処理するようにします。 func.now()関数を渡します。これはSQLのnow() datetime関数を呼び出します。 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シェルを使用してデータベースと学生テーブルを作成します。 Studentモデルに基づいています。

仮想環境がアクティブ化されている場合は、app.pyファイルをFlaskアプリケーションとして設定し、FLASK_APP環境変数を使用します。 次に、flask_appディレクトリで以下のコマンドを使用してFlaskシェルを開きます:

  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つのモデルしかないため、関数呼び出しはデータベースに1つのテーブルのみを作成します:

  1. from app import db, Student
  2. db.create_all()

シェルを実行したままにして、別のターミナルウィンドウを開き、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()

これにより、モデルに加えた変更が適用されますが、データベース内のすべての既存データも削除されます。データベースを更新して既存のデータを保持するには、スキーママイグレーションを使用する必要があります。これにより、テーブルを変更してデータを保持できます。

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

テーブルの追加

データベースと学生テーブルを作成した後、Studentモデルを介してデータベースにいくつかの学生を追加します。

以前に開いた同じflaskシェルを使用するか、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 シェルで確認するには、__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オブジェクトを提供しており、このオブジェクトを使用してデータベースの変更を管理できます。セッションにstudent_johnオブジェクトを追加するには、db.session.add()メソッドを使用してください。

  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シェルを使用して、データベースにさらにいくつかの学生を追加します:

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

今、all()メソッドを使用してquery属性で学生テーブルのすべてのレコードをクエリできます:

  1. Student.query.all()

以下の出力が表示されます:

Output
[<Student john>, <Student Sammy>, <Student Carl>]

この時点で、データベースに3人の学生がいます。次に、インデックスページ用のFlaskルートを作成し、データベースのすべての学生をそのページに表示します。

ステップ3 — レコードのすべてを表示する

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

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

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()ビュー関数を作成します。この関数では、query属性を使用してStudentモデルを使用してデータベースをクエリし、さまざまなメソッドを使用してデータベースから1つ以上のアイテムを取得します。すべての学生エントリをデータベースから取得するためにall()メソッドを使用します。クエリの結果はstudentsという変数に格納し、render_template()ヘルパー関数を使用してレンダリングするindex.htmlという名前のテンプレートに渡します。

index.htmlテンプレートファイルを作成する前に、データベース内の既存の学生を表示するために使用する基本テンプレートを最初に作成します。これにより、コードの繰り返しを避けるために他のテンプレートも使用するすべての基本HTMLコードが含まれます。その後、index()関数でレンダリングしたindex.htmlテンプレートファイルを作成します。テンプレートについて詳しくは、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ブロックは各ページのコンテンツで置換されます。ナビゲーションバーには3つのリンクがあります: インデックスページへのリンクは、url_for()ヘルパー関数を使用してindex()ビュー関数にリンクします。もう1つはCreateページ用で、もう1つはアプリケーションに追加することを選択した場合のAboutページ用です。後で新しい学生を作成するためのページを追加した後に、このファイルを編集します。

次に、新しい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>見出しを使用します。 students変数をこのテンプレートに渡すindex()ビュー関数から各学生を通過するためのJinja forループを使用します。学生のID、名前、メール、年齢、データベースに追加された日付、およびバイオを表示します。

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

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

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

  1. flask run

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

http://127.0.0.1:5000/

データベースに追加した学生が含まれたページが表示されます。ページは次のようなものになります:

データベースに登録されている学生をインデックスページに表示しました。次に、各個人の学生の詳細を表示できる学生ページのルートを作成します。

ステップ4 — 単一のレコードを表示する

このステップでは、Flaskシェルを使用して学生をIDでクエリし、それぞれの学生の詳細を専用ページに表示するためのルートとテンプレートを作成します。

このステップの終了時に、URL http://127.0.0.1:5000/1 は最初の学生を表示するページになります(IDが 1 だからです)。URL http://127.0.0.1:5000/ID は、関連する ID 番号の投稿を表示します(存在する場合)。

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

Flaskシェルを開いて、学生をクエリする方法をデモします。

  1. flask shell

レコードをクエリし、データベースからデータを取得するには、Flask-SQLAlchemyはモデルクラスに query 属性を提供します。そのメソッドを使用して、特定のフィルターでレコードを取得できます。

たとえば、firstname のような引数を持つ filter_by() メソッドを使用して、テーブル内の列に一致するパラメータである学生を取得できます:

  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>

シェルを終了できます:

  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で学生コレクションをクエリし、学生を取得します。データが存在する場合は、学生データをstudent変数に保存し、データベースに指定されたIDの学生が存在しない場合は404 Not Found HTTPエラーを返します。

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

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

  1. nano templates/student.html

以下のコードを新しい student.html ファイルに入力してください。これは index.html テンプレートと似ていますが、1人の学生のみを表示します。

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 %}

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

このファイルでは、ベーステンプレートを拡張し、ページのタイトルとして学生のフルネームを設定します。学生のID、名前、メールアドレス、年齢、レコード作成日、およびバイオを表示します。

ブラウザを使用して、2番目の学生の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 %}

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

学生のフルネームに <a> タグを追加し、url_for() 関数を使用して学生ID(student.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')

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

このルートでは、methodsパラメータにタプル('GET', 'POST')を渡して、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 %}

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

ベーステンプレートを拡張し、見出しをタイトルとして設定し、method属性をpostに設定して<form>タグを使用して、フォームがPOSTリクエストを送信することを示します。

名前がfirstnamelastnameの2つのテキストフィールドがあります。後でビュー関数でユーザーが送信したフォームデータにアクセスするために、これらの名前を使用します。

名前が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

<body>タグを編集して、作成リンクのhref属性の値を変更します:

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>

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

インデックスページをリフレッシュすると、ナビゲーションバーの作成リンクが機能することに気付くでしょう。

今、新しい学生を追加するためのWebフォームがあるページがあります。Webフォームの詳細については、FlaskアプリケーションでWebフォームを使用する方法を参照してください。より高度で安全なWebフォームの管理方法については、Flask-WTFを使用したWebフォームの使用と検証方法を参照してください。次に、既存の学生データを編集するためのページを追加します。

ステップ6 — レコードの編集

このステップでは、既存の学生データを編集するための新しいページをアプリケーションに追加します。学生のデータをIDに基づいて編集するための新しい/ID/edit/ルートを追加します。

  1. nano app.py

app.pyを開いてください:

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)

ファイルの末尾に次のルートを追加します。これにより、IDを使用して編集したい学生エントリを取得します。後で作成するWebフォームを介して送信された新しい学生データを抽出します。その後、学生データを編集して、ユーザーをインデックスページにリダイレクトします:

ファイルを保存して閉じてください。/<int:student_id>/edit/というルートがあります。これは、student_idをURL変数として受け取り、そのIDを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テンプレートがレンダリングされ、データベースから取得した学生オブジェクトが渡され、現在の学生データで学生ウェブフォームを入力できます。このedit.htmlテンプレートを後で作成します。

ユーザーが学生データを編集してフォームを送信すると、if request.method == 'POST'内のコードが実行されます。送信された学生データをrequest.formオブジェクトから対応する変数に抽出します。studentオブジェクトの各属性を新しく送信されたデータに設定して、Step 2で行ったように列の値を変更します。ウェブフォームのフィールドに変更が行われなかった場合、その列の値はデータベースで変わりません。

学生データを新しく送信されたデータに設定した後、学生オブジェクトをデータベースセッションに追加し、変更をコミットします。最後に、ユーザーをインデックスページにリダイレクトします。

次に、ユーザーが編集を行うページを作成する必要があります。新しい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 属性とバイオテキストエリアの値は、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> タグを追加して、student.id 値を 編集 リンクとして各学生の編集ページにリンクさせます。

これで、既存の学生を編集するためのページができました。次に、データベースから学生を削除する 削除 ボタンを追加します。

ステップ7 — レコードの削除

このステップでは、既存の学生を削除するための新しいルートと削除ボタンを追加します。

まず、POSTリクエストを受け入れる新しい/id/deleteルートを追加します。新しいdelete()ビュー関数は、削除したい学生のIDを受け取り、存在する場合はStudentモデルのget_or_404()クエリメソッドにIDを渡し、指定された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.routeデコレーターの代わりに、app.postデコレーターを使用します。これはFlaskバージョン2.0.0で導入され、一般的なHTTPメソッドのショートカットが追加されました。たとえば、@app.post("/login")@app.route("/login", methods=["POST"])のショートカットです。これにより、このビュー関数はPOSTリクエストのみを受け付けることになり、ブラウザで/ID/deleteルートに移動すると405 Method Not Allowedエラーが返されます。なぜなら、ウェブブラウザはデフォルトでGETリクエストを行うからです。学生を削除するには、ユーザーがボタンをクリックしてこのルートにPOSTリクエストを送信します。

このdelete()ビュー関数は、student_idURL変数を介して削除する学生のIDを受け取ります。学生を取得し、student変数に保存するためにget_or_404()メソッドを使用し、学生が存在しない場合は404 Not Foundで応答します。行db.session.delete(student)でデータベースセッションのdelete()メソッドを使用し、学生オブジェクトを渡します。これにより、トランザクションがコミットされるときに学生が削除されるようにセッションが設定されます。他に変更を加える必要がないため、db.session.commit()を使用してトランザクションを直接コミットします。最後に、ユーザーをインデックスページにリダイレクトします。

次に、index.htmlテンプレートを編集して学生を削除ボタンを追加します。

  1. nano templates/index.html

forループを編集して、Editリンクの直下に新しい<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 %}

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

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

今、インデックスページを更新してください。

各学生エントリの下にDelete Studentボタンが表示されます。それをクリックして、削除を確認してください。インデックスページにリダイレクトされ、その学生がもう表示されなくなります。

これで、学生管理アプリケーションでデータベースから学生を削除する方法ができました。

結論

FlaskとFlask-SQLAlchemyを使用してSQLiteデータベースを使った小さなFlask Webアプリケーションを構築しました。データベースに接続する方法、テーブルを表すデータベースモデルを設定する方法、データベースにアイテムを追加する方法、テーブルをクエリする方法、データベースデータを変更する方法を学びました。

アプリケーションでSQLAlchemyを使用すると、Pythonのクラスとオブジェクトを使用してSQLデータベースを管理できます。SQLiteの代わりに、他のデータベースエンジンを使用することができ、接続に責任を持つSQLALCHEMY_DATABASE_URI設定以外に、コアアプリケーションコードを変更する必要はありません。これにより、最小限のコード変更で、1つの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