【Flask】formからサーバーサイドにテキストや各種コントロール、ファイルを渡す

前提条件

CHECK.1
ディレクトリ
ディレクトリ
flask/
 ┣ app.py
 ︙
 ┗ templates/
   ┗ index.html

ターミナル(コマンドプロンプト)のカレントディレクトリは、 flaskフォルダにあるものとします。

CHECK.2
app.py
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()
CHECK.3
index.html
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>
CHECK.4
動作確認
ターミナル
$ python app.py

http://127.0.0.1:5000/

END

値をサーバーサイドに渡す

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']を指定します。

Python
# 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が格納されます。

テキスト

HTML
<input type="text" id="hoge" name="HOGE" placeholder="Hoge">

テキストのPOSTをFlaskで受け取ったリクエストパラメータには、ユーザーからinputタグに入力されたテキスト(value属性)が格納されます。

Python
# 『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タグ

などがあります。

サンプルプログラム

index.html
<!-- 中略 -->
<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>
app.py
# -*- 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つ選択するためのラジオボタンを作成できます。

HTML
<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属性の値が格納されます。

Python
@app.route('/', methods=['POST'])
def post():
	p = request.form.get('radio') # 2

サンプルプログラム

index.html
<!-- 中略 -->
<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.py
# 中略
@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つ選択するためのチェックボックスを作成できます。(ラジオボタンとの違いは、複数チェックできることです。)

HTML
<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属性の値が格納されます。

Python
@app.route('/', methods=['POST'])
def post():
	p = request.form.get('check') # 1

チェックボックスのように、場合によって複数回答があるようなフォームの場合getメソッドではなくgetlistメソッドを使用することで、複数回答をリストにまとめて取得することが出来ます。

HTML
<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 -->
Python
@app.route('/', methods=['POST'])
def post():
	p = request.form.getlist('check') # ['1', '2', '4']

サンプルプログラム

index.html
<!-- 中略 -->
<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.py
# 中略
@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)))

プルダウンメニュー

HTML
<select name="sel">
	<option value="1">要素1</option>
	<option value="2">要素2</option>
	<option value="3">要素3</option><!-- CHECK -->
</select>

プルダウンメニューのPOSTをFlaskで受け取ったリクエストパラメータには、value属性の値が格納されます。

Python
@app.route('/', methods=['POST'])
def post():
	p = request.form.get('sel') # 3

サンプルプログラム

index.html
<!-- 中略 -->
<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.py
# 中略
@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を指定すると、ユーザーにファイルを選択してアップロードさせるための入力フィールドを作成できます。

HTML
<input type="file" id="file" name="file" accept="ファイルの種類">

ファイルをアップロードするフォームでは、form要素のenctype属性にmultipart/form-dataを必ず指定しなければいけません。

HTML
<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です。

Python
@app.route('/', methods=['POST'])
def post():
	f = request.files.get('file') # <FileStorage: '送信時のファイル名' ('mimetype')>
  • f.filename:送信時のファイル名
  • f.name:formのフィールド名(name属性)
  • f.mimetype:mimetype
  • f.save('ディレクトリ'):ファイルを保存する

ファイル形式を制限する

input要素のaccept属性でユーザーが送信できるファイル形式を制限することができます。

書式 説明
audio/* オーディオファイル全般
video/* 動画ファイル全般
image/* 画像ファイル全般
.拡張子 ファイルの形式を直接指定

カンマ,区切りで複数の値を指定することもできます。

HTML
<input type="file" name="csv" accept=".csv"> <!-- CSVファイル -->
<input type="file" name="excel" accept=".xls,.xlsx"> <!-- Excelファイル -->

ファイルの容量を制限する

flaskでは、標準機能でアップロードされるファイル容量の上限を設定することができます。

Python
# ファイル容量制限 : 1MB
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024

MAX_CONTENT_LENGTHの単位はByteで、サンプルでは1MBを上限に設定しています。

保存したファイルをフォルダから削除する

Pythonの標準ライブラリのos.removeメソッドで、保存したファイルを速やかに削除することができます。

CSVやEXCELファイルをサーバーにアップロードした場合、データの中に個人情報が含まれていることが多いと思います。

セキュリティの観点上、サーバー上にファイルが残らないようにpandas.DataFrameなどの変数にデータを格納したら、すぐに削除するようにしましょう。

Python
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.utilssecure_filenameメソッドを使用すると、自動で安全なファイル名を作成して保存するように設定することができます。

Python
from werkzeug.utils import secure_filename

# 中略

@app.route('/', methods=['POST'])
def post():
	f = request.files.get('file')
	filename = secure_filename(f.filename)

サンプルプログラム

  • ファイル形式を制限する
  • ファイルの容量を制限する
  • ユーザーから送信されたファイル名を信用しない

これらを踏まえたクライアントサイドからサーバーサイドにファイルを渡すサンプルプログラムを記します。

ディレクトリ
flask/
 ┣ app.py
 ︙
 ┣ static/
 ┃  ┗ image/
 ┗ templates/
   ┗ index.html
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>
Python
# -*- 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でWebアプリケーションを公開するために欠かせないセキュリティのことや実践的なテクニックを紹介しています。

ここまで読み込めば、あとはアイディア次第でいろんなWebアプリケーションを公開できるでしょう!

  1. セッション
  2. Cookie
  3. メソッドベース・ディスパッチ
  4. ベーシック認証・Digest認証
  5. ログイン機能
  6. サーバーサイドからクライアントサイドにpandas.Series、pandas.DataFrameを渡す
  7. クライアントサイド(form)からPOSTされた1、2次元配列を受け取る
  8. matplotlibを使う
  9. Bootstrap4を使う
  10. フォームの非同期通信(Ajax, jQuery)