目次
前提条件
┣ app.py
︙
┗ templates/
┗ index.html
ターミナル(コマンドプロンプト)のカレントディレクトリは、 flaskフォルダにあるものとします。
app.py
# -*- coding: utf-8 -*-
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Hello Jinja2</title>
</head>
<body>
<h1>Hello Jinja2</h1>
<p>Jinja2でHTMLファイルを読み込みことに成功しました。</p>
</body>
</html>
値をサーバーサイドに渡す
<form action="/" method="POST" enctype="multipart/form-data">
<input type="text" name="name">
<input type="submit" value="送信">
</form>
FlaskでPOSTを受けとるには、routeの第2引数にmethods=['POST']
を指定します。
# flask.requestをインポートする
from flask import request
# postのときの処理
@app.route('/', methods=['POST'])
def post():
name = request.form.get('name')
リクエストパラメーターは、2つの方法で取得します。
name = request.form.get('name')
name = request.form['name']
おすすめは、前者のget
を使う方です。
理由は、入力フォームが空白で値が送られなかった場合、raise exceptions.BadRequestKeyError(key)
というエラーが発生するからです。
ちなみにget
を使った方では、変数name
にはNone
が格納されます。
テキスト
<input type="text" id="hoge" name="HOGE" placeholder="Hoge">
テキストのPOSTをFlaskで受け取ったリクエストパラメータには、ユーザーからinput
タグに入力されたテキスト(value
属性)が格納されます。
# 『hoge』とPOSTされたとすると…
@app.route('/', methods=['POST'])
def post():
p = request.form.get('HOGE') # hoge
他にも、テキストデータをサーバーサイドに送れるものは、<input type="text">
以外にも、代表的なもので
input
タグのtype="password"
、type="tel"
、type="email"
、type="hidden"
textarea
タグ
などがあります。
サンプルプログラム
<!-- 中略 -->
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<form action="/" method="POST" enctype="multipart/form-data">
<div>
<label for="name">名前:</label>
<input type="text" id="name" name="name" placeholder="名前">
</div>
<div>
<input type="submit" value="送信">
</div>
</form>
</body>
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request
app = Flask(__name__)
# getのときの処理
@app.route('/', methods=['GET'])
def get():
return render_template('index.html', \
title = 'Form Sample(get)', \
message = '名前を入力して下さい。')
# postのときの処理
@app.route('/', methods=['POST'])
def post():
name = request.form['name']
return render_template('index.html', \
title = 'Form Sample(post)', \
message = 'こんにちは、{}さん'.format(name))
if __name__ == '__main__':
app.run()
ラジオボタン
input
要素のtype
属性にradio
を指定すると、選択肢の中から1つ選択するためのラジオボタンを作成できます。
<input type="radio" id="r1" name="radio" value="1">
<input type="radio" id="r2" name="radio" value="2"> <!-- CHECK -->
<input type="radio" id="r3" name="radio" value="3">
ラジオボタンのPOSTをFlaskで受け取ったリクエストパラメータには、value
属性の値が格納されます。
@app.route('/', methods=['POST'])
def post():
p = request.form.get('radio') # 2
サンプルプログラム
<!-- 中略 -->
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<form action="/" method="POST" enctype="multipart/form-data">
<div>
<label for="r1">鶏肉:</label>
<input type="radio" id="r1" name="radio" value="鶏">
</div>
<div>
<label for="r2">豚肉:</label>
<input type="radio" id="r2" name="radio" value="豚">
</div>
<div>
<label for="r3">牛肉:</label>
<input type="radio" id="r3" name="radio" value="牛">
</div>
<div>
<label for="r4">羊肉:</label>
<input type="radio" id="r4" name="radio" value="羊">
</div>
<div>
<input type="submit" value="送信">
</div>
</form>
</body>
# 中略
@app.route('/', methods=['GET'])
def get():
return render_template('index.html', \
title = 'Form Sample(get)', \
message = '何のお肉が好きですか?')
@app.route('/', methods=['POST'])
def post():
name = request.form.get('radio')
return render_template('index.html', \
title = 'Form Sample(post)', \
message = '{}の肉がお好きなんですね!'.format(name))
チェックボックス
input
要素のtype
属性にcheckbox
を指定すると、選択肢の中から1つ選択するためのチェックボックスを作成できます。(ラジオボタンとの違いは、複数チェックできることです。)
<input type="checkbox" id="ck1" name="check" value="1"> <!-- CHECK -->
<input type="checkbox" id="ck2" name="check" value="2">
<input type="checkbox" id="ck3" name="check" value="3">
チェックボックスのPOSTをFlaskで受け取ったリクエストパラメータには、value
属性の値が格納されます。
@app.route('/', methods=['POST'])
def post():
p = request.form.get('check') # 1
チェックボックスのように、場合によって複数回答があるようなフォームの場合、get
メソッドではなくgetlist
メソッドを使用することで、複数回答をリストにまとめて取得することが出来ます。
<input type="checkbox" id="ck1" name="check" value="1"> <!-- CHECK -->
<input type="checkbox" id="ck2" name="check" value="2"> <!-- CHECK -->
<input type="checkbox" id="ck3" name="check" value="3">
<input type="checkbox" id="ck4" name="check" value="4"> <!-- CHECK -->
@app.route('/', methods=['POST'])
def post():
p = request.form.getlist('check') # ['1', '2', '4']
サンプルプログラム
<!-- 中略 -->
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<form action="/" method="POST" enctype="multipart/form-data">
<div>
<label for="ck1">鶏肉:</label>
<input type="checkbox" id="ck1" name="checkbox" value="鶏">
</div>
<div>
<label for="ck2">豚肉:</label>
<input type="checkbox" id="ck2" name="checkbox" value="豚">
</div>
<div>
<label for="ck3">牛肉:</label>
<input type="checkbox" id="ck3" name="checkbox" value="牛">
</div>
<div>
<label for="ck4">羊肉:</label>
<input type="checkbox" id="ck4" name="checkbox" value="羊">
</div>
<div>
<input type="submit" value="送信">
</div>
</form>
</body>
# 中略
@app.route('/', methods=['GET'])
def get():
return render_template('index.html', \
title = 'Form Sample(get)', \
message = '何のお肉が好きですか?')
@app.route('/', methods=['POST'])
def post():
name = request.form.getlist('checkbox')
return render_template('index.html', \
title = 'Form Sample(post)', \
message = '{}の肉がお好きなんですね!'.format('と'.join(name)))
プルダウンメニュー
<select name="sel">
<option value="1">要素1</option>
<option value="2">要素2</option>
<option value="3">要素3</option><!-- CHECK -->
</select>
プルダウンメニューのPOSTをFlaskで受け取ったリクエストパラメータには、value
属性の値が格納されます。
@app.route('/', methods=['POST'])
def post():
p = request.form.get('sel') # 3
サンプルプログラム
<!-- 中略 -->
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<form action="/" method="POST" enctype="multipart/form-data">
<select name="sel">
<option value="null" disabled selected>選択して下さい</option>
<option value="鶏">鶏肉</option>
<option value="豚">豚肉</option>
<option value="牛">牛肉</option>
<option value="羊">羊肉</option>
</select>
<div>
<input type="submit" value="送信">
</div>
</form>
</body>
# 中略
@app.route('/', methods=['GET'])
def get():
return render_template('index.html', \
title = 'Form Sample(get)', \
message = '何のお肉が好きですか?')
@app.route('/', methods=['POST'])
def post():
name = request.form.get('sel')
return render_template('index.html', \
title = 'Form Sample(post)', \
message = '{}の肉がお好きなんですね!'.format(name))
ファイルをサーバーサイドに渡す
input
要素のtype
属性にfile
を指定すると、ユーザーにファイルを選択してアップロードさせるための入力フィールドを作成できます。
<input type="file" id="file" name="file" accept="ファイルの種類">
ファイルをアップロードするフォームでは、form
要素のenctype
属性にmultipart/form-data
を必ず指定しなければいけません。
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" id="file" name="file" accept="ファイルの種類">
</form>
ファイルのリクエストパラメーターは、request.form.get
ではなく、request.files.get
で取得します。
request.files.get
で得られるファイルの実体は、werkzeug.datastructures.FileStorage
です。
@app.route('/', methods=['POST'])
def post():
f = request.files.get('file') # <FileStorage: '送信時のファイル名' ('mimetype')>
f.filename
:送信時のファイル名f.name
:formのフィールド名(name属性)f.mimetype
:mimetypef.save('ディレクトリ')
:ファイルを保存する
ファイル形式を制限する
input
要素のaccept
属性でユーザーが送信できるファイル形式を制限することができます。
書式 | 説明 |
---|---|
audio/* |
オーディオファイル全般 |
video/* |
動画ファイル全般 |
image/* |
画像ファイル全般 |
.拡張子 |
ファイルの形式を直接指定 |
カンマ,区切りで複数の値を指定することもできます。
<input type="file" name="csv" accept=".csv"> <!-- CSVファイル -->
<input type="file" name="excel" accept=".xls,.xlsx"> <!-- Excelファイル -->
ファイルの容量を制限する
flaskでは、標準機能でアップロードされるファイル容量の上限を設定することができます。
# ファイル容量制限 : 1MB
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024
MAX_CONTENT_LENGTH
の単位はByteで、サンプルでは1MBを上限に設定しています。
保存したファイルをフォルダから削除する
Pythonの標準ライブラリのos.remove
メソッドで、保存したファイルを速やかに削除することができます。
CSVやEXCELファイルをサーバーにアップロードした場合、データの中に個人情報が含まれていることが多いと思います。
セキュリティの観点上、サーバー上にファイルが残らないようにpandas.DataFrame
などの変数にデータを格納したら、すぐに削除するようにしましょう。
import os
import pandas as pd #=> pip install pandas
# 中略
@app.route('/', methods=['POST'])
def post():
f = request.files.get('csv')
filepath = 'csv/' + secure_filename(f.filename)
f.save(filepath)
# CSVファイルをpandas.DataFrameとして読み込む
df = pd.read_csv(filepath, encoding='utf-8 or cp932', dtype='object')
# ファイルを削除する
os.remove(filepath)
ユーザーから送信されたファイル名を信用しない
ユーザーから送信されたファイルを保存するときは、セキュリティ上の問題を防ぐためにも、ファイル名を適当に変更する必要があります。
そこで、werkzeug.utils
のsecure_filename
メソッドを使用すると、自動で安全なファイル名を作成して保存するように設定することができます。
from werkzeug.utils import secure_filename
# 中略
@app.route('/', methods=['POST'])
def post():
f = request.files.get('file')
filename = secure_filename(f.filename)
サンプルプログラム
- ファイル形式を制限する
- ファイルの容量を制限する
- ユーザーから送信されたファイル名を信用しない
これらを踏まえたクライアントサイドからサーバーサイドにファイルを渡すサンプルプログラムを記します。
┣ app.py
︙
┣ static/
┃ ┗ image/
┗ templates/
┗ index.html
<!-- 中略 -->
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<!-- POSTのときの処理 -->
{%- if flag %}
<div><img src="{{ image_url }}" alt="{{ image_name }}"></div>
{%- endif %}
<form action="/" method="POST" enctype="multipart/form-data">
<div>
<input type="file" name="image" accept="image/*" required>
</div>
<div>
<input type="submit" value="送信">
</div>
</form>
</body>
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
# ファイル容量上限 : 1MB
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024
@app.route('/', methods=['GET'])
def get():
return render_template('index.html', \
title = 'Form Sample(get)', \
message = '画像を選択して下さい。', \
flag = False)
@app.route('/', methods=['POST'])
def post():
# ファイルのリクエストパラメータを取得
f = request.files.get('image')
# ファイル名を取得
filename = secure_filename(f.filename)
# ファイルを保存するディレクトリを指定
filepath = 'static/image/' + filename
# ファイルを保存する
f.save(filepath)
return render_template('index.html', \
title = 'Form Sample(post)', \
message = 'アップロードされた画像({})'.format(filename), \
flag = True, \
image_name = filename, \
image_url = filepath)
if __name__ == '__main__':
app.run()
連載目次:FlaskでWebアプリケーションを開発するためのロードマップ
入門編:10記事
入門編の10記事を順に読んでいけば、FlaskでWebアプリケーションを開発する必要最小限のことが学べます。
簡単なアプリケーションであれば、セキュリティ上の観点を考慮しなかった場合公開できるでしょう。
- Flaskを『ローカルで開発する環境構築』から『プログラムの実行まで』を一通り
FlaskでWebアプリケーションを作る『全体の流れ』を大まかに理解する- Flaskのrender_templateでHTML・CSS・JavaScriptファイルを読み込む
- サーバーサイドからクライアントサイドに変数(値・リスト・辞書型配列)を渡す
- クライアントサイドで変数を処理(アサイン、フィルター、エスケープ)する
- クライアントサイドで条件分岐(if)とループ(for in)を使って、コードを簡素化する
- クライアントサイド(form)からサーバーサイドにテキストや各種コントロール、ファイルを渡す
- テンプレート継承でHTMLファイルを役割ごとに分割する
データベースに接続するデプロイする
実践編
実践編の記事では、FlaskでWebアプリケーションを公開するために欠かせないセキュリティのことや実践的なテクニックを紹介しています。
ここまで読み込めば、あとはアイディア次第でいろんなWebアプリケーションを公開できるでしょう!
セッションCookieメソッドベース・ディスパッチベーシック認証・Digest認証ログイン機能- サーバーサイドからクライアントサイドにpandas.Series、pandas.DataFrameを渡す
- クライアントサイド(form)からPOSTされた1、2次元配列を受け取る
matplotlibを使うBootstrap4を使うフォームの非同期通信(Ajax, jQuery)