2019-08-10に投稿

Flask + Nuxt.js(spa) + axiosでCSVファイルをmultipart/form-dataによりアップロードする

Flask + Nuxt.js + axiosでCSVファイルをmultipart/form-dataによりアップロードする。なお、Nuxt.jsのモードはspaにしている。
FlaskとNuxt.jsの連携を確認したいので、バリデーションやエラーのハンドリングはほとんどしない。
まずFlaskでファイルのアップロードを確認し、その後Nuxt.jsでファイルをアップロードしていく。

FlaskでCSVファイルをmultipart/form-dataによるアップロード(POST)を受けつける

Flaskをインストールする

pipenvFlaskが動く環境を作る。

$ touch Pipfile
$ pipenv --python 3.7.4
$ pipenv install Flask==1.1.1

Flaskが動くことを次のコードで確認する。

app.py

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello():
    return 'hello'


if __name__ == "__main__":
    app.run(debug=True)

次のコマンドで起動することで、オートリロードを有効にする。
FLASK_ENV=development flask run

$ FLASK_ENV=development flask run
...(略)
Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

curl/にアクセスし、helloがレスポンスで返ってくることを確認する。

$ curl http://127.0.0.1:5000/
hello

FlaskでPOSTを受けつけるようにする

@app.routemethodsPOSTを受け付けるようにする。

app.py

@app.route("/api/upload", methods=["POST"])
def upload():
    return 'アップロード成功'

curlのオプション-X HTTPメソッドによりPOSTでレスポンスが返ってくることを確かめる。

$ curl -X POST http://127.0.0.1:5000/api/upload
アップロード成功

Flaskでmultipart/form-dataのファイルを受けつける

app.config['UPLOAD_FOLDER']にアップロード先のディレクトリを指定しておく。
multipart/form-dataによりアップロードされたファイルはrequest.filesに格納されている。
request.files[フィールド名]からファイルをFileStorageオブジェクトとして取得する。
FileStorageオブジェクトはsaveメソッドを持っており、このメソッドでファイルを保存することができる。
保存時はsecure_filename関数で安全なファイル名にする。

import os
from flask import Flask, request
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = './uploads'

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER


@app.route("/api/upload", methods=["POST"])
def upload():
    fileStorageObj = request.files['file']
    filename = secure_filename(fileStorageObj.filename)
    fileStorageObj.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return 'アップロード成功'


if __name__ == "__main__":
    app.run(debug=True)

テスト用のダミーCSVを用意する。

dummy.csv

name,age,pref
田原 唯菜,56,香川県
伊沢 圭一,57,鳥取県
鈴村 良夫,6,千葉県

このファイルをcurlでアップロードする。
multipart/form-data-F フィールド名=値で指定する。ファイルの場合は@ファイル名で指定する。

$ curl -X POST -F [email protected] http://127.0.0.1:5000/api/upload
アップロード成功

uploadsディレクトリにファイルが格納されていることが確認できる。

ファイルと一緒にテキストもリクエストする

ファイルと一緒にテキストもリクエストできることを確認しておく。

ファイルの取得は以下の形で行うが、

request.files['file']

ファイル以外の取得はform[フィールド名]で取得する。

request.form['text']

このフィールドをprintで表示できるようにし、curlで確認する。

$ curl -X POST -F [email protected] -F text="大切なCSVです"  http://127.0.0.1:5000/api/upload

レスポンスをJSONにする

Nuxt.jsaxiosで扱うためレスポンスをJSONにする。
JSON_AS_ASCIIを設定して、日本語が文字化けしないようにする。

app.config['JSON_AS_ASCII'] = False

戻り値はjsonify(オブジェクト)としてJSONで返せるようにする。

エラーの場合はabort(400, {'message': 'ファイルは必須です'})を呼び出し、
@app.errorhandler(400)でエラーをハンドリングする。

app.py

import os
from flask import Flask, request, jsonify, abort
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = './uploads'

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['JSON_AS_ASCII'] = False


@app.route("/api/upload", methods=["POST"])
def upload():
    print(request.form['text'])
    if 'file' not in request.files:
        return abort(400, {'message': 'ファイルは必須です'})
    fileStorageObj = request.files['file']
    filename = secure_filename(fileStorageObj.filename)
    fileStorageObj.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return jsonify({'message': 'アップロード成功'})


@app.errorhandler(400)
def custom400(error):
    return jsonify({'message': error.description['message']})

if __name__ == "__main__":
    app.run(debug=True)

ファイル、テキストを指定した場合はアップロード成功と返ってくる。

$ curl -X POST -F [email protected] -F text="大切なCSVです" http://127.0.0.1:5000/api/upload
{
  "message": "アップロード成功"
}

また、ファイルをあえて指定しない場合はファイルは必須ですとエラーハンドリング通り返ってくる。

$ curl -X POST -F text="大切なCSVです" http://127.0.0.1:5000/api/upload
{
  "message": "ファイルは必須です"
}

ここまででFlaskアプリケーションの実装は終わり。

Nuxt.jsでCSVファイルをmultipart/form-dataによりアップロード(POST)する

つぎはNuxt.jsの実装を進めていく。

npx create-nuxt-app プロジェクト名でプロジェクトを作成する。
Axiosをインストールし、modeSingle Page Appにする。他の設定項目はデフォルトのままにした。

$ npx create-nuxt-app ui
? Choose features to install Axios
? Choose rendering mode Single Page App

nuxt.config.jsaxiosのオプションproxytrueとし、proxyapiにアクセスがきたらFlaskに渡すよう設定する。

nuxt.config.js

  axios: {
    proxy: true
  },
  proxy: {
    '/api/': 'http://127.0.0.1:5000/',
  },

npm run devで起動し、proxyの設定を確認する。

$ npm run dev
...略
Listening on: http://localhost:3000/

Nuxt.jsは3000番ポートで起動するので、http://localhost:3000/api/uploadに向けてcurlでPOSTする。
今まで通りレスポンスが返ってきている。

$ curl -l -X POST -F [email protected]  -F text="大切なCSVです"  http://localhost:3000/api/upload
{
  "message": "アップロード成功"
}

あとはコンポーネントを作れば良い。
template部分は、テキストはv-modelで入力をうけつける。
ファイルはtype="file"としたうえで、@changeイベントでファイルが選択されたときのハンドリングをする。 event.target.files[0]からファイルを取得し、dataに保持しておく。
アップロードするボタンが押されたら、multipart/form-dataでPOSTするためにnew FormData()に対してtextfileappendする。
this.$axios.post(URL, formData, options)でPOSTする。
第一引数にURLを指定し、第二引数にformDataを指定する。第三引数のoptionsでHTTPヘッダーにmultipart/form-dataを指定する。
index.vue

<template>
  <section class="container">
    <form>
      <input v-model="text" />
      <input type="file" @change="onChange" />
      <button type="button" @click="onSubmit">アップロードする</button>
    </form>
  </section>
</template>

<script>
export default {
  data() {
    return {
      text: '',
      file: null,
    }
  },
  methods: {
    onChange(event) {
      this.file = event.target.files[0]
    },
    async onSubmit() {
      const formData = new FormData()
      formData.append('text', this.text) 
      formData.append('file', this.file)
      const response = await this.$axios.post('/api/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }).catch(error => {
        return error.response
      })
      console.info(response.data.message)
    }
  }
}
</script>

画面からテキストを入力し、画像を選択して「アップロードする」するボタンを押すとuplodasディレクトリにファイルがアップロードされ、ブラウザのコンソールにメッセージが表示される。

info アップロード成功

また、画像を選択しなかった場合、400エラーとなり、エラーメッセージが取得できる。

POST http://localhost:3000/api/upload 400 (BAD REQUEST)
info ファイルは必須です

参考

https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/?highlight=upload
http://tm-webtools.com/Tools/TestData
https://stackoverflow.com/questions/21294889/how-to-get-access-to-error-message-from-abort-command-when-using-custom-error-ha
https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.ImmutableMultiDict
https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/#werkzeug.datastructures.FileStorage

Originally published at nansystem.com

nancy

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント