このページでは、Flaskを使ってフォームからのリクエストを受け取り、それをブラウザで表示させる、ということを行います。
そのなかで、テンプレートの継承、具体的なデータの取り出し方、を見ていきます。
参考ページ
Flaskへようこそ ―― Flask Documentation
Jinja ―― Jinja Documentation
使用した環境
WSL2 Ubuntu - 20.04
Python 3.10.0
Flask 2.1.3
テンプレートの継承
まずはモジュールを作成し、ルーティングを行います。
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
つぎに継承元と継承先のテンプレートを作成します。
継承元――親となるテンプレート。必要なHTMLを書きます。
継承先――子となるテンプレート。親の内容を引き継ぎ、重複している部分を省略できます。ここで記述するのは、親で示された「ブロック」のところです。
親テンプレートの名前は「base.html」とし、子テンプレートはルーティングで指定した「index.html」。
これらを作成します。
$ tree . . ├── app.py └── templates ├── base.html └── index.html 1 directory, 3 files
これから「base.html」を書きますが、親テンプレートでは、必要なHTMLに加え、「ここが〇〇ブロックですよ」といった目印をつけます。
ブロックの明示はつぎのような形で行います。
{% block ブロックの名前 %}
{% endblock %}
ブロック名は、わかりやすいものを付けます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
{% block head %}
<title>Document</title>
{% endblock %}
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
2つのブロックがあり、「block head」と「block content」です。
今度は子となる「index.html」で、この2つのブロック部分の内容を書いていきます。
冒頭に「extends」文を追加し、つぎのような書き方をします。
{% extends 継承元のファイル名 %}
{% block ブロックの名前 %}
<!-- ここに内容を書く -->
{% endblock %}
{% extends 'base.html' %}
{% block head %}
<title>Index Page</title>
{% endblock %}
{% block content %}
<h1>インデックスページ</h1>
<p>インデックスです</p>
{% endblock %}
blockとendblockは2つでセットなので、閉じ忘れに注意しましょう。
それではサーバーを起動して確認します。
$ python app.py * Serving Flask app 'app' (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) * Restarting with stat * Debugger is active!
継承できているようですが、念のため、ページのソースコードを表示し、それをコピペします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Index Page</title>
</head>
<body>
<h1>インデックスページ</h1>
<p>インデックスです</p>
</body>
</html
インデントや空行は目立ちますが、動作に問題はなく、ちゃんと継承できています。
空行になっているのは、開始と終了ブロックの宣言文です。
これが気になるという人は以下を参照してください。
Whitespace Control - Jinja Documentation
フォームの作成
フォームのページを「/form」に設定します。
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/form')
def form():
return render_template('form.html')
if __name__ == '__main__':
app.run(debug=True)
つぎは「form.html」を作成します。
HTMLでのフォーム作成と、FlaskでCSSファイルを適用させる方法は、それぞれ以前やったので、以下を参照してください。
{% extends 'base.html' %}
{% block head %}
<title>フォーム</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% endblock %}
{% block content %}
<h1>プロフィールの入力</h1>
<form action="#" method="post">
<div>
<label for="user-name">名前</label>
<input type="text" id="user-name" name="name" required>
</div>
<div>
<label for="user-age">年齢</label>
<input type="number" id="user-age" name="age" min="0" max="100" required>
</div>
<div>
<span>性別</span>
<input type="radio" id="gender_choice1" name="gender" value="male">
<label for="gender_choice1">男性</label>
<input type="radio" id="gender_choice2" name="gender" value="female">
<label for="gender_choice2">女性</label>
<input type="radio" id="gender_choice3" name="gender" value="another" checked>
<label for="gender_choice3">その他</label>
</div>
<div class="e-mail">
<label for="email">e-mail</label>
<input type="email" id="email" name="e-mail" required>
</div>
<div>
<label for="message">メッセージ</label><br>
<textarea name="msg" id="message" cols="50" rows="10" maxlength="200" placeholder="ひと言どうぞ!"></textarea>
</div>
<button>送信</button>
</form>
{% endblock %}
用意した入力欄は5つ。
あとでデータを取り出すときにname属性の値が必要になります。
- 名前(name="name")
- 年齢(name="age")
- 性別(name="gender")
- e-mail(name="e-mail")
- メッセージ(name="msg")
合わせてCSSファイルを「static」配下に置きます。
h1 {
text-align: center;
}
form {
margin: 0 auto;
width: 400px;
padding: 20px;
border: 1px solid;
}
#user_age {
width: 40px;
}
.e-mail {
margin-bottom: 7px;
}
ディレクトリの構成
$ tree . . ├── app.py ├── static │ └── style.css └── templates ├── base.html ├── form.html └── index.html 2 directories, 5 files
ここで動作確認します。
$ python app.py * Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) * Restarting with stat * Debugger is active!
「http://127.0.0.1:5000/form」にアクセス。
見た目はまずまずです。
ただし、遷移先は仮の措置としてこのページのトップにしています。
この「/form」では「GETメソッド」のみの対応であるため、送信ボタンを押したとしても「405 Method Not Allowed」が出ます。
つぎの項で送信先のページを作成し、あわせてPOSTに対応させるようにします。
データの取り出し方
フォームからのデータを取り出すにはrequestをインポートします。
このrequestに対し、
GETメソッドなら「args」、POSTメソッドなら「form」を使って、データを取り出します。
ちなみにrequestはFlaskで定義されている変数、argsやformはプロパティです。
GETメソッド
request.args[key]
request.args.get(key)
POSTメソッド
request.form[key]
request.form.get(key)
キーに使うものは、HTMLの<input>や<textarea>などのname属性の値です。
取り出し方には、それぞれ二通りの方法があります。
辞書と同様の取り出し方では、もしキーが存在しなかった場合、「400 Bad Request」エラーになります。
getの場合、キーがなかったときは「None」を返します。
次の項目では実際にPOSTでの取り出しをやってみます。
送信先ページの設定
ルーティング
「app.py」に追記します。
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/form')
def form():
return render_template('form.html')
@app.route('/print-values', methods=['GET', 'POST'])
def print_values():
if request.method == 'POST':
user_data = request.form
return render_template('values.html', user_data=user_data)
else:
return render_template('no-value.html')
if __name__ == '__main__':
app.run(debug=True)
@app.route('/print-values', methods=['GET', 'POST'])
フォームのメソッドをPOSTに設定しているので、引数のmethodsに「POST」を追加します。
※デフォルト値は['GET']のみです。
if request.method == 'POST':
requestを通してそのメソッドを判断。
user_data = request.form
今回のメソッドがPOSTのため「form」を使います。GETの場合なら「args」。
user_dataには「ImmutableMultiDict型」のデータが入ります。
if request.method == 'POST': user_data = request.form return render_template('values.html', user_data=user_data) else: return render_template('no-value.html')
POSTだったときは「values.html」と「user_data」を渡します。
ここでフォームの入力データをひとつずつ変数に入れ、render_templateに渡すこともできます。(例・name = request.form['name'])
しかし、キーと値の両方を使いたいので、ImmutableMultiDict型の「user_data」をそのまま渡し、テンプレート内で処理したいと思います。
GETだったときは「no-value.html」を渡します。
データの取り出し
フォームのメソッドがPOSTだった場合のテンプレート「values.html」を追加。
{% extends 'base.html' %}
{% block head %}
<title>データの出力</title>
{% endblock %}
{% block content %}
<h1>キーとバリュー</h1>
<div>
<h2>キー一覧</h2>
<ul>
{% for key in user_data.keys() %}
<li>{{ key }}</li>
{% endfor %}
</ul>
</div>
<div>
<h2>入力されたデータ</h2>
<p>名前: {{ user_data['name'] }}</p>
<p>年齢:{{ user_data['age'] }}</p>
<p>性別:{{ user_data['gender'] }}</p>
<p>メール:{{ user_data.get('e-mail') }}</p>
<p>メッセージ:{{ user_data.get('msg') }}</p>
</div>
{% endblock %}
「user_data」から値を取り出しました。
キーとなる<input>や<textarea>のname属性はつぎのようなものでしたね。
- 名前(name="name")
- 年齢(name="age")
- 性別(name="gender")
- e-mail(name="e-mail")
- メッセージ(name="msg")
これでフォームデータの送信先ページが作成できました。
「form.html」のデータの送信先を'#'から変更します。<form>タグの「action」の値をつぎのように変更してください。
<form action="{{ url_for('print_values') }}" method="post">
URLを直接書くのではなく、url_forの引数に関数名を渡し、URLを取り出します。
つぎはフォームのメソッドがGETだったときの「no-value.html」です。
{% extends 'base.html' %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
<h1>値がありません</h1>
<p>GETメソッドです</p>
{% endblock %}
{{ super() }}で親テンプレートの内容をそのまま使います。
つまり「base.html」の{ % block head %}の中身です。
このようになっていました。
<title>Document</title>
ぜんたいの最終的なディレクトリ構成です。
$ tree . . ├── app.py ├── static │ └── style.css └── templates ├── base.html ├── form.html ├── index.html ├── no-value.html └── values.html 2 directories, 7 files
最終確認
それでは実行し、追加したURLにアクセスしてみましょう。
http://127.0.0.1:5000/print-values
URLを手打ちして直接アクセスすると、GETメソッドとなるため、想定どおりの表示です。
http://127.0.0.1:5000/form
上の画像のようにプロフィールを埋めて送信すると↓
http://127.0.0.1:5000/print-values に遷移します。
今度の場合はPOSTだったため、valuse.htmlが呼ばれました。
入力されたデータも間違いありません。
まとめ
今回は自分でいちからフォームを作成しました。
フォームに関しては「WTForms」というライブラリもあります。
バリデーション(入力値のチェック)やCSRF対策がされていて、フォームを扱いやすくするライブラリです。