要件
- Django側で生成したCSVファイルを、ブラウザ側でそのまま保存する動きを作りたい
- CSVファイル名はパラメータに変化し、日本語も含まれる
結論
# ファイル名はURLエンコードする
parsed_filename = urllib.parse.quote(filename)
response = HttpResponse(file_data, content_type="text/csv")
response["Content-Disposition"] = f"attachment; filename={parsed_filename}"
return response
this.$axios.post('/download/csv', params, {responseType: 'blob'}).then((res) => {
if (res) {
const blob = new Blob(
[res.data],
{
type: res.data.type
}
)
const contentDisposition: string = res.headers['content-disposition'];
const match = contentDisposition.match(/attachment; filename=(.*)/)
//ブラウザにダウンロードせるときに、ファイル名をURLデコード
saveAs(blob, decodeURI(match[1]));
}
})
経緯や学んだこと
Djangoだけで作られている場合を想定している情報が多いようで、こういったコーディングはよく見かけた。
response = HttpResponse(file_data, content_type="text/csv")
response["Content-Disposition"] = f"attachment; filename={filename}"
return response
これだと、Vue側で受け取るときにContent-Dispositionが文字化けしてファイル名が取得できなかった。
ファイル名の文字化けに対策が必要なんだな・・・
とググっているとこういう情報に行き着く。
# ファイル名はURLエンコードする
parsed_filename = urllib.parse.quote(filename)
response = HttpResponse(file_data, content_type="text/csv")
# 更にファイル名の文字コードをUTF-8に指定
response["Content-Disposition"] = f"attachment; filename={parsed_filename}; filename*=UTF-8''{parsed_filename};"
return response
ここでハマった
ファイル名をURLエンコードする情報は確かにたくさん在って、他の人はこれで対処できたのかもしれない。
(おそらく、Vueでなく、直接ブラウザがファイルのダウンロードを受け取る仕組みであればこれでも動いたのだろう)
ただ、今回私のところではまだ文字化けしていた。
というか、URLエンコードされた文字列のままのファイル名でダウンロードされた。
ここで、たどり着いた情報のファイル名の指定の意味をよく考えた。
これ、本来は
filename={parsed_filename}; filename*=UTF-8''{parsed_filename};
じゃなくて
filename*=UTF-8''{filename};
なのでは?
*=UTF-8''
で指定した場合、後ろに続く文字列はUTF-8だと明示するということなのではと。(裏付けなし)
だとして、変換された文字列はURLエンコードの文字列なわけだから、UTF-8とかは関係ないなと。
解決
ファイル名がURLエンコードされていても、ブラウザだったらそのまま普通のファイル名に変換してくれるのだろう。
だが、Vueはそんなことしてくれないわけだ。
→ ファイル名URLデコードすれば良いんだな。
→ 結論のコード
感想
今回、
- よく分からないレスポンスの意味を、一旦鵜呑み
- 文字コード指定できる書き方あるんだ!
で変にDjango側で解決しようと右往左往してしまった。
コードの意味を解釈して、諦めるところはすっぱり諦める判断が重要だなと。
ちなみに*=UTF-8''
の裏付けについてはなんか読みにくいRFCとか、リンク先が消えているとかで5分で諦めた。