FlaskアプリケーションでPostgreSQLデータベースを使用する方法

著者は、無料かつオープンソース基金を、寄付のために書くプログラムの一環として選択しました。

はじめに

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

Flaskは、Python言語でウェブアプリケーションを作成するための便利なツールと機能を提供する軽量なPythonウェブフレームワークです。 PostgreSQL、またはPostgresは、SQLクエリ言語の実装を提供する関係データベース管理システムです。標準に準拠しており、信頼性の高いトランザクションや読み取りロックなしの同時実行などの高度な機能があります。

このチュートリアルでは、psycopg2ライブラリを使用して、PythonでPostgreSQLデータベースとやり取りするためのPostgreSQLデータベースアダプタを示す小さな書評ウェブアプリケーションを構築します。Flaskと組み合わせて、データベースサーバへの接続、テーブルの作成、テーブルへのデータ挿入、テーブルからのデータ取得などの基本的なタスクを実行します。

前提条件

ステップ1 — PostgreSQLデータベースとユーザーの作成

このステップでは、Flaskアプリケーション用にflask_dbという名前のデータベースとsammyという名前のデータベースユーザーを作成します。

Postgresのインストール中に、postgresという名前の操作システムユーザーが作成され、postgres PostgreSQL管理ユーザーに対応します。 このユーザーを使用して管理タスクを実行する必要があります。 sudo を使用し、-iuオプションでユーザー名を渡します。

次のコマンドを使用して、インタラクティブなPostgresセッションにログインします:

  1. sudo -iu postgres psql

PostgreSQLプロンプトが表示され、要件を設定できます。

まず、プロジェクト用のデータベースを作成します:

  1. CREATE DATABASE flask_db;

注意: すべてのPostgresステートメントはセミコロンで終わらなければなりませんので、問題が発生している場合は、コマンドがそれで終わるようにしてください。

次に、プロジェクト用のデータベースユーザーを作成します。安全なパスワードを選択してください:

  1. CREATE USER sammy WITH PASSWORD 'password';

その後、この新しいユーザーに新しいデータベースを管理する権限を付与します:

  1. GRANT ALL PRIVILEGES ON DATABASE flask_db TO sammy;

データベースが作成されたことを確認するには、次のコマンドを入力してデータベースのリストを取得します:

  1. \l

リストの中にflask_dbが表示されます。

作業が完了したら、次のコマンドを入力してPostgreSQLプロンプトから終了します:

  1. \q

Postgresは、Pythonを使用してpsycopg2ライブラリを使ってデータベース情報に接続して管理できるように設定されています。次に、このライブラリをFlaskパッケージと一緒にインストールします。

ステップ2 — Flaskとpsycopg2のインストール

このステップでは、Pythonを使用してデータベースとやり取りするためにFlaskとpsycopg2ライブラリをインストールします。

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

  1. pip install Flask psycopg2-binary

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

Output
Successfully installed Flask-2.0.2 Jinja2-3.0.3 MarkupSafe-2.0.1 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1 psycopg2-binary-2.9.2

これで、必要なパッケージが仮想環境にインストールされました。次に、データベースに接続して設定します。

ステップ3 — データベースの設定

このステップでは、flask_db データベースに接続し、本を格納するためのテーブルを作成し、いくつかの書籍とレビューを挿入するための Python ファイルを flask_app プロジェクトディレクトリに作成します。

まず、プログラミング環境がアクティブ化されていることを確認し、flask_app ディレクトリに init_db.py という新しいファイルを開きます。

  1. nano init_db.py

このファイルでは、flask_db データベースに接続し、books というテーブルを作成し、サンプルデータを使用してテーブルを満たします。以下のコードを追加します:

flask_app/init_db.py
import os
import psycopg2

conn = psycopg2.connect(
        host="localhost",
        database="flask_db",
        user=os.environ['DB_USERNAME'],
        password=os.environ['DB_PASSWORD'])

# データベース操作を実行するためのカーソルを開く
cur = conn.cursor()

# コマンドを実行する: 新しいテーブルを作成する
cur.execute('DROP TABLE IF EXISTS books;')
cur.execute('CREATE TABLE books (id serial PRIMARY KEY,'
                                 'title varchar (150) NOT NULL,'
                                 'author varchar (50) NOT NULL,'
                                 'pages_num integer NOT NULL,'
                                 'review text,'
                                 'date_added date DEFAULT CURRENT_TIMESTAMP);'
                                 )

# テーブルにデータを挿入する

cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('A Tale of Two Cities',
             'Charles Dickens',
             489,
             'A great classic!')
            )


cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('Anna Karenina',
             'Leo Tolstoy',
             864,
             'Another great classic!')
            )

conn.commit()

cur.close()
conn.close()

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

このファイルでは、まず、データベースのユーザー名とパスワードを保存するための環境変数にアクセスするために使用する os モジュールをインポートします。これにより、ソースコードでそれらが見えなくなります。

psycopg2 ライブラリをインポートします。その後、psycopg2.connect() 関数を使用して flask_db データベースに接続します。この場合、ホストを指定し、localhost とします。データベース名は database パラメーターに渡します。

プログラミング環境で設定した環境変数にアクセスできる os.environ オブジェクトを介して、ユーザー名とパスワードを提供します。データベースのユーザー名は DB_USERNAME という環境変数に保存し、パスワードは DB_PASSWORD という環境変数に保存します。これにより、ユーザー名とパスワードをソースコードの外部に保存し、ソースコードがソースコントロールに保存されたり、インターネット上のサーバーにアップロードされたりした場合でも、機密情報が漏洩することはありません。攻撃者がソースコードにアクセスしても、データベースにアクセスすることはできません。

connection.cursor() メソッドを使用して、cur というカーソルを作成します。これにより、Python コードがデータベースセッションで PostgreSQL コマンドを実行できるようになります。

カーソルの execute() メソッドを使用して、すでに存在する場合は books テーブルを削除します。これにより、books という別のテーブルが存在する可能性がある場合に混乱することがなくなります(たとえば、異なる列がある場合など)。ここではまだテーブルを作成していないので、SQL コマンドは実行されません。この init_db.py ファイルを実行するたびに既存のデータがすべて削除されますので、注意してください。私たちの目的では、データベースを初期化するためにこのファイルを一度だけ実行しますが、挿入したデータを削除して初期のサンプルデータから再開するために再度実行したいかもしれません。

その後、CREATE TABLE books を使用して、次の列を持つ books という名前のテーブルを作成します:

  • idserial 型の ID。これは自動的に増加する整数です。この列は、PRIMARY KEY キーワードを使用して指定される 主キー を表します。データベースは、各エントリに対してこのキーに一意の値を割り当てます。
  • titlevarchar 型の書籍のタイトル。これは制限付きの可変長文字タイプです。varchar (150) は、タイトルが最大で 150 文字であることを意味します。NOT NULL は、この列が空であってはならないことを示します。
  • author:書籍の著者。最大 50 文字までです。NOT NULL は、この列が空であってはならないことを示します。
  • pages_num:書籍のページ数を表す整数です。NOT NULL は、この列が空であってはならないことを示します。
  • review:書評。 text タイプは、レビューが任意の長さのテキストであることを示します。
  • date_added:書籍がテーブルに追加された日付。 DEFAULT は、列のデフォルト値を CURRENT_TIMESTAMP に設定し、これは書籍がデータベースに追加された時間です。 id と同様に、この列の値を指定する必要はありません。自動的に入力されます。

テーブルを作成した後、カーソルの execute() メソッドを使用して、2つの書籍をテーブルに挿入します。A Tale of Two Cities(チャールズ・ディケンズ著)と、Anna Karenina(レオ・トルストイ著)です。値を SQL ステートメントに渡すために %s プレースホルダーを使用します。psycopg2 は、背後で挿入を処理し、SQL インジェクション攻撃 を防ぎます。

テーブルに書籍データを挿入したら、トランザクションをコミットしてデータベースに変更を適用するために、connection.commit() メソッドを使用します。その後、カーソルを cur.close() で閉じ、接続を conn.close() で閉じてクリーンアップします。

データベース接続を確立するには、次のコマンドを実行して DB_USERNAMEDB_PASSWORD 環境変数を設定します。自分のユーザー名とパスワードを使用してください。

  1. export DB_USERNAME="sammy"
  2. export DB_PASSWORD="password"

次に、ターミナルで python コマンドを使用して init_db.py ファイルを実行します。

  1. python init_db.py

ファイルの実行がエラーなしで完了すると、新しい books テーブルが flask_db データベースに追加されます。

新しい books テーブルを確認するために、対話型のPostgresセッションにログインします。

  1. sudo -iu postgres psql

\c コマンドを使用して flask_db データベースに接続します。

  1. \c flask_db

その後、books テーブルから書籍のタイトルと著者を取得するために SELECT 文を使用します。

  1. SELECT title, author FROM books;

以下のような出力が表示されます。

        title         |      author
----------------------+------------------
 A Tale of Two Cities | Charles Dickens
 Anna Karenina        | Leo Tolstoy

\q を使用して対話型セッションを終了します。

次に、小さなFlaskアプリケーションを作成し、データベースに接続して挿入した2つの書籍レビューを取得し、インデックスページに表示します。

ステップ4 — 書籍の表示

このステップでは、データベース内の書籍を取得し、それらを表示するFlaskアプリケーションを作成します。

プログラムの環境がアクティブ化され、Flaskがインストールされている場合、flask_app ディレクトリ内の app.py ファイルを編集します。

  1. nano app.py

このファイルはデータベース接続を設定し、その接続を使用する単一のFlaskルートを作成します。次のコードをファイルに追加します。

flask_app/app.py
import os
import psycopg2
from flask import Flask, render_template

app = Flask(__name__)

def get_db_connection():
    conn = psycopg2.connect(host='localhost',
                            database='flask_db',
                            user=os.environ['DB_USERNAME'],
                            password=os.environ['DB_PASSWORD'])
    return conn


@app.route('/')
def index():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT * FROM books;')
    books = cur.fetchall()
    cur.close()
    conn.close()
    return render_template('index.html', books=books)

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

ここでは、osモジュール、psycopg2ライブラリ、およびflaskパッケージからFlaskクラスとrender_template()をインポートします。その後、appというFlaskアプリケーションインスタンスを作成します。

get_db_connection()という関数を定義し、DB_USERNAMEDB_PASSWORD環境変数に保存されているユーザー名とパスワードを使用してflask_dbデータベースに接続します。この関数は、データベースへのアクセスに使用するconn接続オブジェクトを返します。

次に、app.route()デコレータを使用してメインの/ルートとindex()ビュー関数を作成します。 index()ビュー関数では、get_db_connection()関数を使用してデータベース接続を開き、カーソルを作成し、SELECT * FROM books; SQLステートメントを実行してデータベース内のすべての書籍を取得します。取得したデータをbooks変数に保存するためにfetchall()メソッドを使用します。最後に、カーソルと接続を閉じます。最後に、books変数でデータベースから取得した書籍のリストを渡してindex.htmlというテンプレートファイルをレンダリングするためにrender_template()関数を呼び出します。

データベースにある本をインデックスページに表示するには、まずすべての基本的な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>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

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

このベーステンプレートには、他のテンプレートで再利用するために必要なすべてのHTMLのボイラープレートが含まれています。title ブロックは各ページのタイトルを設定するために置換され、content ブロックは各ページの内容で置換されます。ナビゲーションバーには、インデックスページ用のリンクとして、url_for() ヘルパー関数を使用して index() ビュー関数にリンクするものと、アプリケーションに含めることを選択した場合の About ページ用のリンクが含まれています。

次に、index.html というテンプレートを開きます。これは app.py ファイルで参照されるテンプレートです:

  1. nano templates/index.html

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

flask_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Books {% endblock %}</h1>
    {% for book in books %}
        <div class='book'>
            <h3>#{{ book[0] }} - {{ book[1] }} BY {{ book[2] }}</h3>
            <i><p>({{ book[3] }} pages)</p></i>
            <p class='review'>{{ book[4] }}</p>
            <i><p>Added {{ book[5] }}</p></i>
        </div>
    {% endfor %}
{% endblock %}

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

このファイルでは、ベーステンプレートを拡張し、content ブロックの内容を置換します。<h1>見出しを使用してタイトルも表示します。

行の中でJinja for ループを使用して、booksリスト内の各書籍を処理します。書籍のID(最初のアイテム)をbook[0]を使用して表示します。その後、書籍のタイトル、著者、ページ数、レビュー、および書籍が追加された日付を表示します。

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

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

既に設定していない場合は、DB_USERNAMEおよびDB_PASSWORD環境変数を設定してください:

  1. export DB_USERNAME="sammy"
  2. export DB_PASSWORD="password"

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

  1. flask run

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

http://127.0.0.1:5000/

最初の起動時にデータベースに追加した書籍が表示されます。

データベース内の書籍をインデックスページに表示しました。次に、ユーザーが新しい書籍を追加できるようにします。次のステップで書籍を追加するための新しいルートを追加します。

ステップ5 — 新しい本の追加

このステップでは、新しい本やレビューをデータベースに追加するための新しいルートを作成します。

ユーザーが書籍のタイトル、著者、ページ数、および書籍のレビューを入力するウェブフォーム付きのページを追加します。

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

まず、app.pyファイルを開きます:

  1. nano app.py

ウェブフォームを処理するために、flaskパッケージからいくつかのものをインポートする必要があります:

  • 提出されたデータにアクセスするためのグローバルなrequestオブジェクト。
  • URLを生成するためのurl_for()関数。
  • 書籍をデータベースに追加した後、ユーザーをインデックスページにリダイレクトするためのredirect()関数。

これらのインポートをファイルの最初の行に追加します:

flask_app/app.py

from flask import Flask, render_template, request, url_for, redirect

# ...

次に、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リクエストのみが許可されています。ユーザーが最初に/createルートにGETリクエストを使用してリクエストすると、create.htmlという名前のテンプレートファイルがレンダリングされます。後で、新しい書籍を追加するためのウェブフォームにデータを入力して送信する場合のPOSTリクエストを処理するために、このルートを編集します。

新しいcreate.htmlテンプレートを開きます:

  1. nano templates/create.html

以下のコードを追加します:

flask_app/templates/create.html
{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Add a New Book {% endblock %}</h1>
    <form method="post">
        <p>
            <label for="title">Title</label>
            <input type="text" name="title"
                   placeholder="Book title">
            </input>
        </p>

        <p>
            <label for="author">Author</label>
            <input type="text" name="author"
                   placeholder="Book author">
            </input>
        </p>

        <p>
            <label for="pages_num">Number of pages</label>
            <input type="number" name="pages_num"
                   placeholder="Number of pages">
            </input>
        </p>
        <p>
        <label for="review">Review</label>
        <br>
        <textarea name="review"
                  placeholder="Review"
                  rows="15"
                  cols="60"
                  ></textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

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

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

titleという名前のテキストフィールドがあり、これを使用して/createルートでタイトルデータにアクセスします。

著者のためのテキストフィールド、ページ数のための数値フィールド、書籍レビューのためのテキストエリアがあります。

最後に、フォームの末尾に送信ボタンがあります。

開発サーバーが実行されている状態で、ブラウザを使用して/createルートに移動します:

http://127.0.0.1:5000/create

新しい書籍を追加するための入力フィールド、その著者のためのフィールド、および書籍のページ数のためのフィールド、書籍のレビューのためのテキストエリア、および送信ボタンがあるページが表示されます。

フォームに記入して送信すると、サーバーに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':
        title = request.form['title']
        author = request.form['author']
        pages_num = int(request.form['pages_num'])
        review = request.form['review']

        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute('INSERT INTO books (title, author, pages_num, review)'
                    'VALUES (%s, %s, %s, %s)',
                    (title, author, pages_num, review))
        conn.commit()
        cur.close()
        conn.close()
        return redirect(url_for('index'))

    return render_template('create.html')

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

POSTリクエストを次のように処理します:if request.method == 'POST'条件の中で。タイトル、著者、ページ数、およびユーザーが送信したレビューをrequest.formオブジェクトから取得します。

get_db_connection()関数を使用してデータベースを開き、カーソルを作成します。そして、booksテーブルにユーザーが送信したタイトル、著者、ページ数、およびレビューを挿入するINSERT INTO SQLステートメントを実行します。

トランザクションをコミットし、カーソルと接続を閉じます。

最後に、ユーザーをインデックスページにリダイレクトし、既存の書籍の下に新しく追加された書籍を表示します。

開発サーバーが実行されている状態で、ブラウザを使用して/createルートに移動します:

http://127.0.0.1:5000/create

フォームにデータを入力して送信します。

インデックスページにリダイレクトされ、新しい書籍レビューが表示されます。

次に、ナビゲーションバーに作成ページへのリンクを追加します。base.htmlを開きます:

  1. nano templates/base.html

ファイルを以下のように編集します:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

    </style>
</head>
<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>
</html>

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

ここでは、ナビゲーションバーに、作成ページを指す新しいリンクを追加します。

インデックスページを更新すると、ナビゲーションバーに新しいリンクが表示されます。

これで、新しい本のレビューを追加するためのWebフォームがあるページができました。Webフォームの詳細については、FlaskアプリケーションでWebフォームを使用する方法を参照してください。より高度で安全なWebフォームの管理方法については、Flask-WTFを使用してWebフォームを使用および検証する方法を参照してください。

結論

PostgreSQLデータベースと通信する小さな書評Webアプリケーションを構築しました。Flaskアプリケーションには、データベースへの新しいデータの追加、データの取得、およびページへの表示など、基本的なデータベース機能があります。

Flaskについてもっと読みたい場合は、Flaskシリーズの他のチュートリアルをチェックしてください。

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-a-postgresql-database-in-a-flask-application