📤

FastAPIで、HTMLのform内容の送信ができないときの解決策

2022/03/01に公開約2,100字2件のコメント

FastAPIに対してHTMLのform内容を送信したとき、422 Unprocessable Entity エラーが発生し、ブラウザには以下のような文章が表示されることがある。
{"detail":[{"loc":["query","変数名"],"msg":"field required","type":"value_error.missing"}]}


これが発生する例を紹介する。

  • HTMLではtextの入力と、ファイルのアップロードができる
  • /createに対してPOSTメソッドで入力内容を送信する
    フォームのスクリーンショット
<div style="text-align: center">
    <form action="/create" method="post" id="upload-form" enctype="multipart/form-data">
        <label for="name-form">アプリケーションの名前</label> <br/>
        <input name="app_name" type="text" id="name-form"><br/>
        <br/>
        <label for="file-form">Excelファイル</label> <br/>
        <input name="file" type="file" id="file-form"><br/>
        <br/>
        <input type="submit" value="アップロード"/>
    </form>
</div>
  • /createではapp_nameというstr型変数と、ファイルを受け取る
main.py
@app.post("/create")
async def create_upload_file(app_name: str, file: UploadFile = File(...)):
    return {"filename": file.filename,
            "app_name": app_name}

この構成のままsubmitすると
{"detail":[{"loc":["query","app_name"],"msg":"field required","type":"value_error.missing"}]}
という表示が出て、正常に処理が行われない。
その原因と、解決する方法を紹介する。

原因

関数の引数にパスパラメータにないものがあるとき、それは自動的にクエリパラメータと見なされるが、formがmethod="post"のときはactionのURLにクエリパラメータがつかないから。

上記の例では、引数にapp_name: strがあるものの、@app.post("/create/{ app_name }")のようにパスパラメータとして指定されていないため、app_nameはクエリパラメータから取得される。
しかしaction="/create"だと送信先のURLがhttp://127.0.0.1:8000/createになり、クエリパラメータなしの状態でPOSTされるため、app_nameが取得できずエラーが発生する。

formのactionを書き換える

以上の問題をformのactionを書き換えることで解決する。
まずHTMLに以下のようなjavascriptを追加する。

function setAction(){
    const path = "/create";
    const key = "app_name";
    const val = document.getElementById('name-form').value;
    document.getElementById('upload-form').action = path+'?'+ key +'='+ val;
}

そしてsubmitボタンのonClick関数として設定する。

<input type="submit" value="アップロード" onclick="setAction()"/>

すると、formのactionの値がsubmitボタンを押した瞬間
action="/create?app_name=(フォームに入力した値)"
に書き換わり、クエリパラメータがついた状態でPOSTできるようになる。

これによって、422 Unprocessable Entity エラーが回避できる。

Discussion

Formクラスがありますよ。

from fastapi import FastAPI, Form, UploadFile

app = FastAPI()

@app.post("/create")
async def create_upload_file(app_name: str = Form(...), file: UploadFile = File(...)):
    return {"filename": file.filename,
            "app_name": app_name}

ほんとだ!ありがとうございます

ログインするとコメントできます