FlaskアプリケーションでWebフォームを使用する方法

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

はじめに

テキストフィールドやテキストエリアなどのウェブフォームは、ユーザーがデータをアプリケーションに送信し、アクションを実行するためにそれを使用したり、アプリケーションに大きなテキストを送信したりすることができます。例えば、ソーシャルメディアアプリケーションでは、ユーザーが自分のページに新しいコンテンツを追加できるボックスを提供することができます。別の例として、ログインページがあります。この場合、ユーザーにユーザー名を入力するためのテキストフィールドと、パスワードを入力するためのパスワードフィールドを提供します。サーバー(この場合はFlaskアプリケーション)は、ユーザーが送信したデータを使用し、データが有効であればユーザーをログインさせるか、Invalid credentials!のようなメッセージでユーザーに送信したデータが正しくないことを通知します。

Flaskは、Python言語でWebアプリケーションを作成するための便利なツールや機能を提供する軽量のPython Webフレームワークです。このチュートリアルでは、Webフォームの使用方法を示す小さなWebアプリケーションを構築します。このアプリケーションには、Pythonリストに保存されているメッセージを表示するためのページと、新しいメッセージを追加するためのページがあります。また、メッセージフラッシュを使用して、ユーザーが無効なデータを送信した際のエラーを通知します。

前提条件

ステップ1 — メッセージの表示

このステップでは、Pythonの辞書のリストに保存されているメッセージを表示するためのインデックスページを持つFlaskアプリケーションを作成します。

まず、新しいファイル app.py を編集用に開いてください:

  1. nano app.py

app.py ファイル内に以下のコードを追加して、単一のルートを持つFlaskサーバーを作成します:

flask_app/app.py
from flask import Flask, render_template

app = Flask(__name__)

messages = [{'title': 'Message One',
             'content': 'Message One Content'},
            {'title': 'Message Two',
             'content': 'Message Two Content'}
            ]

@app.route('/')
def index():
    return render_template('index.html', messages=messages)

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

このファイルでは、最初に Flask クラスと flask パッケージから render_template() 関数をインポートします。次に、Flask クラスを使用して、新しいアプリケーションインスタンスapp という名前で作成し、特別な __name__ 変数を渡します。これは、Flaskが舞台裏でいくつかのパスを設定するために必要です。テンプレートのレンダリングについては、チュートリアル Flaskアプリケーションでテンプレートを使用する方法 で説明されています。

次に、messages というグローバルなPythonリストを作成します。これにはPythonの辞書が含まれており、各辞書にはメッセージのタイトルを表す title とメッセージの内容を表す content の2つのキーがあります。これはデータストレージ方法の簡略化された例です。実際のシナリオでは、データを永続的に保存し、より効率的に操作できるデータベースを使用します。

Pythonリストを作成した後、@app.route()デコレータを使用して、index()という名前のビュー関数を作成します。この関数内で、render_template()関数を呼び出すことを返します。これにより、Flaskに対して、ルートがHTMLテンプレートを表示する必要があることが示されます。このテンプレートにはindex.htmlという名前を付け(後で作成します)、messagesという変数を渡します。この変数は、以前に宣言したmessagesリストを値として保持し、HTMLテンプレートで利用できるようにします。ビュー関数については、チュートリアルFlaskとPython 3を使って初めてのWebアプリケーションを作成する方法で説明されています。

次に、Flaskがテンプレートを検索するflask_appディレクトリに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>
        .message {
            padding: 10px;
            margin: 5px;
            background-color: #f3f3f3
        }
        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="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

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

この基本テンプレートには、他のテンプレートで再利用するために必要なすべてのHTMLの雛形が含まれています。titleブロックは各ページのタイトルを設定するために置き換えられ、contentブロックは各ページの内容で置き換えられます。ナビゲーションバーには2つのリンクがあり、1つはurl_for()ヘルパー関数を使用してindex()ビュー関数にリンクするインデックスページ用で、もう1つはアプリケーションに含める場合のAboutページ用です。

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

  1. nano templates/index.html

以下のコードを追加してください:

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

{% block content %}
    <h1>{% block title %} Messages {% endblock %}</h1>
    {% for message in messages %}
        <div class='message'>
            <h3>{{ message['title'] }}</h3>
            <p>{{ message['content'] }}</p>
        </div>
    {% endfor %}
{% endblock %}

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

このコードでは、base.htmlテンプレートを拡張し、contentブロックの内容を置き換えています。<h1>見出しを使用してタイトルとしても機能させています。

この行{% for message in messages %}では、Jinjaのforループを使用して、messagesリスト内の各メッセージを順番に処理しています。メッセージのタイトルと内容を含むために<div>タグを使用しています。タイトルは<h3>見出しで、内容は<p>タグで表示しています。

仮想環境が有効になっているflask_appディレクトリ内で、Flaskにアプリケーション(この場合はapp.py)についてFLASK_APP環境変数を使用して伝えます:

  1. export FLASK_APP=app

それでは、FLASK_ENV環境変数をdevelopmentに設定して、開発モードでアプリケーションを実行し、デバッガにアクセスできるようにします。Flaskデバッガの詳細については、Flaskアプリケーションでエラーを処理する方法を参照してください。これを行うには、以下のコマンドを使用します(Windowsではexportの代わりにsetを使用してください):

  1. export FLASK_ENV=development

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

  1. flask run

開発サーバーが稼働している状態で、ブラウザを使用して以下のURLにアクセスします:

http://127.0.0.1:5000/

インデックスページにmessagesリストのメッセージが表示されます:

これでウェブアプリケーションをセットアップし、メッセージを表示できました。次に、ユーザーが新しいメッセージをインデックスページに追加できるようにする方法が必要です。これはウェブフォームを通じて行います。次のステップでウェブフォームを設定します。

ステップ2 — フォームの設定

このステップでは、アプリケーションにユーザーが新しいメッセージをメッセージリストに追加できるページを作成します。

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

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

  1. nano app.py

ファイルの末尾に以下のルートを追加します:

flask_app/app.py
# ...

@app.route('/create/', methods=('GET', 'POST'))
def create():
    return render_template('create.html')

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

この/createルートは、methodsパラメータにタプル('GET', 'POST')を持ち、GETPOSTリクエストの両方を受け入れます。GETPOSTHTTPメソッドです。デフォルトでは、データを取得するために使用されるGETリクエストのみが受け入れられます。例えば、サーバーにインデックスページやAboutページを要求する場合です。POSTリクエストは、特定のルートにデータを送信するために使用され、サーバー上のデータが変更されることがよくあります。

この例では、GETリクエストを使用してcreateページを要求します。Createページには、入力フィールドと送信ボタンを持つウェブフォームがあります。ユーザーがウェブフォームに記入して送信ボタンをクリックすると、POSTリクエストが/createルートに送信されます。そこでリクエストを処理し、ユーザーが空のフォームを送信していないことを確認するために送信されたデータを検証し、messagesリストに追加します。

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 Message {% endblock %}</h1>
    <form method="post">
        <label for="title">Title</label>
        <br>
        <input type="text" name="title"
               placeholder="Message title"
               value="{{ request.form['title'] }}"></input>
        <br>

        <label for="content">Message Content</label>
        <br>
        <textarea name="content"
                  placeholder="Message content"
                  rows="15"
                  cols="60"
                  >{{ request.form['content'] }}</textarea>
        <br>
        <button type="submit">Submit</button>
    </form>
{% endblock %}

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

このコードでは、base.htmlテンプレートを拡張し、contentブロックをページのタイトルとして機能する<h1>見出しで置き換えています。<form>タグでは、method属性をpostに設定し、フォームデータがPOSTリクエストとしてサーバーに送信されるようにしています。

フォーム内には、titleという名前のテキスト入力フィールドがあります。これは、アプリケーション内でタイトルのフォームデータにアクセスする際に使用する名前です。<input>タグには、{{ request.form['title'] }}というvalueを設定しています。これにより、何か問題が発生した場合でもユーザーが入力したデータが失われないようになっています。例えば、ユーザーが必須のcontentテキストエリアを埋め忘れた場合、リクエストがサーバーに送信され、エラーメッセージがレスポンスとして返されますが、タイトルのデータはrequestグローバルオブジェクトに保存され、request.form['title']を通じてアクセスできます。

タイトル入力フィールドの後に、前述の理由と同じく{{ request.form['content'] }}の値を持つcontentという名前のテキストエリアを追加しています。

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

開発サーバーが実行中の状態で、ブラウザを使用して/createルートに移動します。

http://127.0.0.1:5000/create

「新しいメッセージを追加」ページが表示され、メッセージのタイトル用の入力フィールド、メッセージの内容用のテキストエリア、そして送信ボタンが表示されます。

このフォームは、create() ビュー関数に POST リクエストを送信します。ただし、この関数にはまだ POST リクエストを処理するコードがないため、フォームに記入して送信しても何も起こりません。次のステップでは、フォームが送信されたときに受信する POST リクエストを処理します。送信されたデータが有効かどうか(空でないか)を確認し、メッセージのタイトルと内容を messages リストに追加します。

ステップ 3 — フォームリクエストの処理

このステップでは、アプリケーション側でフォームリクエストを処理します。前のステップで作成したフォームを介してユーザーが送信するフォームデータにアクセスし、それをメッセージのリストに追加します。また、メッセージフラッシュを使用して、無効なデータを送信したユーザーに通知します。フラッシュメッセージは一度だけ表示され、次のリクエストで消えます(例えば別のページに移動した場合)。

app.py ファイルを編集用に開きます:

  1. nano app.py

まず、Flask フレームワークから以下をインポートします:

  • 前のステップで構築した HTML フォームを介して送信される着信リクエストデータにアクセスするためのグローバル request オブジェクト。
  • url_for()関数を使ってURLを生成します。
  • flash()関数を使ってリクエストが処理された際にメッセージを表示します(ユーザーに全てが順調であることを通知したり、送信データが無効な場合に問題を通知します)。
  • redirect()関数を使ってクライアントを別の場所にリダイレクトします。

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

flask_app/app.py
from flask import Flask, render_template, request, url_for, flash, redirect

# ...

flash()関数は、クライアントのブラウザセッションにフラッシュメッセージを保存します。これにはシークレットキーの設定が必要です。このシークレットキーはセッションを保護するために使用され、Flaskが新しいメッセージページからインデックスページへと移動する際に、リクエスト間で情報を記憶することを可能にします。ユーザーはセッションに保存された情報にアクセスできますが、シークレットキーを持っていない限り変更することはできません。そのため、シークレットキーへのアクセスを誰にも許可しないようにしてください。詳細についてはFlaskのセッションに関するドキュメントを参照してください。

秘密鍵は長いランダムな文字列であるべきです。osモジュールのos.urandom()メソッドを使って秘密鍵を生成することができます。このメソッドは、暗号化に適したランダムなバイト列の文字列を返します。これを使ってランダムな文字列を取得するには、新しいターミナルを開き、以下のコマンドを使ってPythonの対話型シェルを開いてください:

  1. python

Pythonの対話型シェルで、標準ライブラリからosモジュールをインポートし、以下のようにos.urandom()メソッドを呼び出します:

  1. import os
  2. os.urandom(24).hex()

次のような文字列が得られます:

Output
'df0331cefc6c2b9a5d0208a726a5d1c0fd37324feba25506'

この文字列を秘密鍵として使用できます。

秘密鍵を設定するには、app.configオブジェクトを通じてアプリケーションにSECRET_KEY設定を追加します。app定義の直後、messages変数を定義する前に直接追加します:

flask_app/app.py

# ...
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key'


messages = [{'title': 'Message One',
             'content': 'Message One Content'},
            {'title': 'Message Two',
             'content': 'Message Two Content'}
            ]
# ...

次に、create()ビュー関数を以下のように正確に修正します:

flask_app/app.py
# ...

@app.route('/create/', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        if not title:
            flash('Title is required!')
        elif not content:
            flash('Content is required!')
        else:
            messages.append({'title': title, 'content': content})
            return redirect(url_for('index'))

    return render_template('create.html')

if文で、リクエストがPOSTリクエストである場合にのみ、後続のコードが実行されるように、request.method == 'POST'と比較して確認します。

その後、request.formオブジェクトから送信されたタイトルとコンテンツを抽出します。このオブジェクトはリクエスト内のフォームデータにアクセスできます。タイトルが提供されていない場合、条件if not titleが満たされます。その場合、flash()関数を使用してユーザーにタイトルが必要であることを通知するメッセージを表示します。これにより、メッセージがflashed messagesリストに追加されます。後ほど、これらのメッセージをbase.htmlテンプレートの一部としてページに表示します。同様に、コンテンツが提供されていない場合、条件elif not contentが満たされます。その場合、'Content is required!'メッセージをflashed messagesリストに追加します。

タイトルとメッセージのコンテンツが適切に送信された場合、messages.append({'title': title, 'content': content})を使用して、ユーザーが提供したタイトルとコンテンツを含む新しい辞書をmessagesリストに追加します。その後、redirect()関数を使用してユーザーをインデックスページにリダイレクトします。インデックスページへのリンクにはurl_for()関数を使用します。

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

次に、ウェブブラウザを使用して/createルートに移動します。

http://127.0.0.1:5000/create

好きなタイトルとコンテンツをフォームに入力してください。フォームを送信すると、新しいメッセージがインデックスページにリストされます。

最後に、flashed messagesを表示し、「新しいメッセージ」ページへのリンクをナビゲーションバーに追加して、この新しいページに簡単にアクセスできるようにします。ベーステンプレートファイルを開きます。

  1. nano templates/base.html

ファイルを編集し、<nav>タグ内のFlaskAppリンクの後に新しい<a>タグを追加してください。次に、contentブロックの直上に新しいforループを追加し、ナビゲーションバーの下にフラッシュされたメッセージを表示します。これらのメッセージは、Flaskが提供する特別なget_flashed_messages()関数で利用可能です。そして、各メッセージにalertというクラス属性を追加し、<style>タグ内にいくつかのCSSプロパティを設定します:

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

        .alert {
            padding: 20px;
            margin: 5px;
            color: #970020;
            background-color: #ffd5de;
        }

    </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">
        {% for message in get_flashed_messages() %}
            <div class="alert">{{ message }}</div>
        {% endfor %}
        {% block content %} {% endblock %}
    </div>
</body>
</html>

ファイルを保存して閉じ、その後ブラウザでhttps://127.0.0.1:5000を再読み込みしてください。ナビゲーションバーには、/createルートにリンクする「Create」アイテムが表示されます。

フラッシュメッセージの動作を確認するために、「Create」ページに移動し、2つのフィールドを埋めずにSubmitボタンをクリックしてください。以下のようなメッセージが表示されます:

インデックスページに戻ると、ナビゲーションバーの下に表示されていたフラッシュメッセージは消えています。これらはベーステンプレートの一部として表示されているにもかかわらずです。もしこれらがフラッシュメッセージでなければ、インデックスページにも表示されるでしょう。なぜなら、インデックスページもベーステンプレートを継承しているからです。

フォームにタイトルだけで内容がない状態で送信してみてください。「内容は必須です!」というメッセージが表示されます。ナビゲーションバーのFlaskAppリンクをクリックしてインデックスページに戻り、その後「戻る」ボタンをクリックして作成ページに戻ると、メッセージ内容がまだ残っていることがわかります。これは「戻る」ボタンをクリックした場合にのみ機能し、前回のリクエストを保存するためです。ナビゲーションバーのCreateリンクをクリックすると新しいリクエストが送信され、フォームがクリアされるため、フラッシュメッセージは消えます。

ユーザーの入力を受け取り、それを検証し、データソースに追加する方法を学びました。

注意:
messagesリストに追加したメッセージは、サーバーが停止すると消えてしまいます。これはPythonのリストがメモリ内にのみ保存されるためです。メッセージを永続的に保存するには、SQLiteのようなデータベースを使用する必要があります。PythonでSQLiteを使用する方法については、Python 3でsqlite3モジュールを使用する方法を参照してください。

結論

インデックスページに表示されるメッセージリストにユーザーがメッセージを追加できるFlaskアプリケーションを作成しました。Webフォームを作成し、ユーザーがフォームを介して送信するデータを処理し、メッセージリストに追加しました。また、無効なデータが送信された場合にユーザーに通知するためにフラッシュメッセージを使用しました。

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

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