見出し画像

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ファイル内のデータの集計を行い、その結果だけをファイルとして保存・ダウンロードするというようなこともできますので、このアプリをいろいろ応用して頂ければ幸いです。

この記事が気に入ったらサポートをしてみませんか?