Flaskでファイルのアップロード・ダウンロード機能を実装する
このたび、ノンプログラマーのためのスキルアップ研究会(以下、ノンプロ研)のご支援のもとで「ラズパイとFlaskでつくる!Webアプリ開発入門」という技術同人誌を出させていただきました。
購入はこちらから!
そこでこの記事では、本の追加コンテンツみたいな感じで、簡単に作れるWebアプリなどを書いていきます。
本を買ってくださった方はぜひ試してみて下さい!
サンプルWebアプリの作成
サンプルWebアプリとして、csvファイルのアップロードとダウンロード、アップロードしたファイルを削除するWebアプリを作ります。
まずはディレクトリの構成と全コードをそれぞれ載せていきます。
ディレクトリの構成
まず、ディレクトリの構成を図示します。
ディレクトリ・ファイルの内容は以下の通りです。
File_UL_DL:このサンプルアプリのルートディレクトリです。
app.py:Webアプリの動作を書いていくファイルです。URLの定義やcsvファイルのアップロード関数などを書いていきます。
files:アップロードしたcsvファイルを格納するディレクトリです。
templates:以下のHTMLファイルを格納するディレクトリです。
index.html:ファイルのアップロードを行うボタンを配置し、アップロードしたファイルの一覧を表示する画面になります。
venv:仮想環境有効化用ディレクトリです。事前に作る必要はありません。
全コードの記載
続いて、コード全体を記載していきます。
まず、app.pyは下記の通りです。
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
import os
app = Flask(__name__)
# 操作画面
@app.route('/')
def index():
csv_files = [file for file in os.listdir('files') if file.endswith('.csv')]
return render_template('index.html', csv_files=csv_files)
# ファイルのアップロードを行う
@app.route('/upload', methods=["POST"])
def upload():
file = request.files.get('file')
file_name = 'uploaded_' + file.filename
file_path = os.path.join('files', file_name)
file.save(file_path)
return redirect(url_for('index'))
# ファイルのダウンロードを行う
@app.route('/download/<string:file>')
def download(file):
return send_from_directory('files', file, as_attachment=True)
# ファイルの削除を行う
@app.route('/delete/<string:file>')
def delete(file):
delete_file_path = os.path.join('files', file)
os.remove(delete_file_path)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
続いて、index.htmlは次の通りです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ファイルのアップロード・ダウンロード</title>
</head>
<body>
<h1>ファイルのアップロード・ダウンロード</h1>
<h2>ファイルのアップロード</h2>
<p>
<form method="POST" action="/upload" enctype="multipart/form-data">
<p><label for="file">アップロードするファイルの選択:</label>
<input type="file" name="file">
</p>
<button type="submit">アップロード</button>
</form>
</p>
<h2>アップロードしたファイルの一覧</h2>
<table>
<tr>
<th>アップロードしたファイル</th>
</tr>
{% for file in csv_files %}
<tr>
<td><a href="/download/{{ file }}">{{ file }}のダウンロード</a></td>
<td><a href="/delete/{{ file }}">{{ file }}の削除</a></td>
</tr>
{% else %}
<tr>
<td>データがありません</td>
</tr>
{% endfor %}
</table>
</body>
</html>
動かしてみる
作ったWebアプリを、ラズパイとngrokを使って動かしてみましょう。
さっそく、csvファイルをアップロードしてみましょう。
「ファイルを選択」ボタンを押してファイルを選びます。
アップロードにより、一覧にファイルが追加されました。
このサンプルアプリでは、「ファイル名のダウンロード」でファイルのダウンロード、「ファイル名の削除」でファイルの削除を行うようにしています。
コードの解説
※コードの説明につきましては、拙著を購読下さった前提で進めさせていただきます。
app.py
まず、必要なライブラリ・モジュールをインストールし、インスタンスを作成します。
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
import os
app = Flask(__name__)
send_from_directoryは、ファイルのダウンロードで使用します。また、ファイルの保存や削除などを行うためosモジュールをインストールします。
続いて、操作画面のルーティングとパスオペレーション関数を定義していきましょう。
# 操作画面
@app.route('/')
def index():
csv_files = [file for file in os.listdir('files') if file.endswith('.csv')]
return render_template('index.html', csv_files=csv_files)
詳細はindex.htmlのコードの解説で行いますが、filesディレクトリ内に存在するcsvファイルのリストをcsv_filesとしてrender_template関数に渡すことで、アップロードしたファイルをWebページ上で一覧表示させるようにします。
次は、ファイルのアップロードを行うURLとパスオペレーション関数を定義します。
# ファイルのアップロードを行う
@app.route('/upload', methods=["POST"])
def upload():
file = request.files.get('file')
file_name = 'uploaded_' + file.filename
file_path = os.path.join('files', file_name)
file.save(file_path)
return redirect(url_for('index'))
誤動作を防ぐため、HTTPメソッドはPOSTメソッドのみ許可します。request.files.get('file')により、アップロードのフォームから送信されてきた'file'という名前のデータをファイルデータとして取得します。
その後、ファイル名の先頭に'uploaded_'とつけてfilesディレクトリ内に保存し、操作画面に戻ります。
さらに、ファイルのダウンロードを行うURLとパスオペレーション関数を定義します。
# ファイルのダウンロードを行う
@app.route('/download/<string:file>')
def download(file):
return send_from_directory('files', file, as_attachment=True)
URLに<string:file>という表記が含まれていますが、これによりURL内で<>で括られた箇所を変数として使うことができます。「:」の左側で型を指定することができ、このコードではfileという文字列型(string)の変数を定義し、パスオペレーション関数で利用しています。
このパスオペレーション関数ではsend_from_directory関数でファイルのダウンロードを行いますが、引数はそれぞれ以下の役割を持っています。
'files'は、ファイルが保存されているディレクトリの名前です。相対パスで指定します。
fileは、ダウンロードするファイルの名前で、上記で定義したURL内の変数です。
as_attachment=Trueは、ブラウザにファイルを添付ファイルとして扱うよう指示します。これにより、ブラウザはファイルを表示するのではなく、ダウンロードするようになります。
最後に、ファイルを削除するURLとパスオペレーション関数を定義しましょう。
# ファイルの削除を行う
@app.route('/delete/<string:file>')
def delete(file):
delete_file_path = os.path.join('files', file)
os.remove(delete_file_path)
return redirect(url_for('index'))
上記同様URLからファイル名を取得し、osモジュールを使ってfilesディレクトリからファイルを削除する処理を行っています。
index.html
最初に、ファイルをアップロードするフォームの解説を行います。
<h2>ファイルのアップロード</h2>
<p>
<form method="POST" action="/upload" enctype="multipart/form-data">
<p><label for="file">アップロードするファイルの選択:</label>
<input type="file" name="file">
</p>
<button type="submit">アップロード</button>
</form>
</p>
<form>タグで、ファイルアップロードのためのフォームを定義しています。このタグ内において、enctype="multipart/form-data"という箇所がありますが、これによりファイルアップロードに必要なエンコーディングタイプを指定し、このフォームから送信されたデータをファイルデータとして扱うことができます。また、name="file"とすることで送信データがfileという名前で送信され、request.files.get('file')によりファイルデータを取得しています。
続いて、アップロードしたファイルの一覧を表示する箇所を解説します。
<h2>アップロードしたファイルの一覧</h2>
<table>
<tr>
<th>アップロードしたファイル</th>
</tr>
{% for file in csv_files %}
<tr>
<td><a href="/download/{{ file }}">{{ file }}のダウンロード</a></td>
<td><a href="/delete/{{ file }}">{{ file }}の削除</a></td>
</tr>
{% else %}
<tr>
<td>データがありません</td>
</tr>
{% endfor %}
</table>
<table>タグにより、アップロードしたファイルの一覧を表示するテーブルを作成しています。
Flaskで利用されているテンプレートエンジンJinja2では{% %}を使って繰り返しfor文を利用することができ、'/'のURLから渡されてきたcsvファイルのリストcsv_filesをfor文でのループ処理により個別で表示していく形でアップロードしたファイルの一覧を表示しています。
なお、{% %}ではif文を利用することもでき、このコードではリストcsv_filesが空のときに{% else %}で分岐させて「データがありません」と表示させるようにしてます。
また、表示されたファイル名はダウンロードを行うURLと削除を行うURLとリンク付けをしており、クリックするとそれぞれの処理が行われるようにしています。
最後に
このサンプルアプリではcsvファイルのアップロードとダウンロード、及び削除という基本的な機能しかつけていませんが、ファイルのアップロード時に名前を変更しているようにパスオペレーション関数内でコードを追記することでファイルの加工を行うこともできます。Pandasを使って、アップロードされたcsvファイル内のデータの集計を行い、その結果だけをファイルとして保存・ダウンロードするというようなこともできますので、このアプリをいろいろ応用して頂ければ幸いです。