Flaskを使用して、大容量データ(例: 画像ファイルやCSVファイル)をアップロード・ダウンロードする仕組みを構築する方法を紹介します。本記事では、ファイルのクレンジング、アップロード、ダウンロードの一連の流れを実装するコードとその解説を示します。
目次
ファイルクレンジング
アップロードされたファイルを安全に扱うため、ファイル名のクリーニングが必要です。このセクションでは、ファイル拡張子のチェックやファイル名をローマ字に変換する方法を解説します。
必要なライブラリと初期設定
まず、以下のコードを使用して、許可する拡張子を定義し、ファイル名をクレンジングする関数を作成します。
import os
from werkzeug.utils import secure_filename
import pykakasi
basedir = "/path/to/save/directory"
ALLOWED_EXTENSIONS = {'xlsx', 'xlsm', 'xls', 'csv'}
kakasi = pykakasi.kakasi()
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
japanese_filename = "".join([item['hepburn'] for item in kakasi.convert(file.filename)])
実装のポイント
- 拡張子確認:
allowed_file()
関数で、ファイル名に許可された拡張子が含まれているかをチェックします。拡張子の確認です。取り扱う拡張子を定義しています。まず、ファイルに.があるかどうか確認しています。and の後で、ファイル名を.の右で1回区切って、index1番目、つまり拡張子の方を小文字にして拡張子を得ます。それがALLOWED_EXTENSIONSにあればTrueを返します。 - ファイル名のローマ字化:
pykakasi
を利用して、漢字ファイル名をヘボン式ローマ字に変換します。file.filenameをkakasi.convertで漢字をいくつかの文字形式に直します。そのそれぞれをitemとすると、ローマ字(ヘボン式に直したものは)item[‘hepburn’]と表せます。これを空の文字列にjoinさせています。こうすることで漢字のファイル名をローマ字に直しています。
ファイルアップロード
ユーザーがファイルをアップロードできるHTMLフォームと、それを処理するバックエンドを作成します。
フロントエンド: upload.html
以下は、ファイルアップロードのフォームを作成するコードです。
{% extends "base.html" %}
{% block content %}
<form action="{{ url_for('app.upload')}}" id="upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" accept=".xls, .xlsx, .xlsm, .csv" multiple>
<input type="submit" value="アップロード">
</form>
{% endblock %}
送信フォームが表示されています。ファイルを選択してアップロードをクリックします。すると、POSTメソッドが使われて、action=”{{ url_for(‘app.upload’)}}”views.pyのupload()関数に送られます
バックエンド: views.py
アップロードされたファイルを保存し、データベースに記録します。
@bp.route('/upload', methods=['GET', 'POST'])
@login_required
def upload():
if request.method == 'POST':
if 'file' not in request.files:
flash('ファイルがありません')
file = request.files.get('file')
if file.filename == '':
flash('選択されたファイルがありません。')
if file and allowed_file(file.filename):
japanese_filename = "".join([item['hepburn'] for item in kakasi.convert(file.filename)])
file_name = secure_filename(japanese_filename)
file.save(os.path.join(basedir, file_name))
data = Data(
dataname = file_name,
datapath = os.path.join(basedir, file_name)
)
data.add_data()
return render_template('index.html')
return render_template('upload.html')
POSTで送られてきたのでif request.method == ‘POST’:のロジックが動きます。request.filesはFlaskのメソッドでファイルを取得する動作です。ファイルがあれば、file = request.files.get(‘file’)でアップロードしたファイルが 格納されます。
ファイルダウンロード
保存されたファイルをユーザーがダウンロードできる仕組みを構築します。
データ一覧表示: files.html
アップロード済みファイルをリストで表示し、ダウンロードリンクを付与します。
@bp.route('/files', methods=['GET'])
def files():
user_id = current_user.id
items = db.session.execute(db.select(Item).filter_by(user_id='user_id')).scalars()
return render_template('files.html', items=items)
ここでは
- 現在のユーザーのitemsをとってくる
- htmlにとってきたitemsを渡す
役割をしています。そしてこれをfiles.htmlが受ける形になっています。files.htmlではitemsの中身一つ一つをforループで取り出すようになっています。
ファイルIDを基にファイルを取得し、ユーザーにダウンロード可能な形式で返却します。
files.html
{% extends 'base.html' %}
{% block content %}
<div>
{% for item in items %}
<ul>
<li>
{{ item.name }} | <a href={{ url_for('download', id=item.id) }}>Download</a>
</li>
</ul>
{% endfor %}
</div>
<span>
<a href="{{ url_for('index') }}">Home</a>
</span>
{% endblock content %}
バックエンド: ダウンロード処理
@bp.route('/download/<int:id>', methods=['GET'])
def download(id):
item = db.session.execute(db.select(Item).filter_by(id=id)).scalar_one()
return send_file(BytesIO(item.data), as_attachment=True, attachment_filename=item.name)
a href のDownload のリンクを押すと、url_for()の働きによって def download()に飛ばすことができます。これで、このリンクを押すことで、views.pyのdownload()に飛ぶことができます。ちなみにここでは、idを引数に渡しています。
idを指定してitem一つをとってきます。これをsend_file()関数を使って返すと、ブラウザを通してダウンロードできます。
download関数のsend_file()の中の引数で、ダウンロードできるファイル形式を指定できます。