前回記事
今回は作ったサイトへファイルを簡単にアップロードする方法です。
ブログサイトとか作る場合には絶対必要な項目!
Djangoにファイルをアップロードする為の方法に「データベースに登録する」「モデルを利用する」という方法が検索すると多く出て来ます。
ファイルの名前も自動で付けられたりとフレームワークらしく大変便利な機能だそうですが、モデル作ったりマイグレーションしたりと面倒!
データベースとかそんな余計な機能いらんねん!モデル?そんなん使わんし!
保存したい場所はプロジェクトルートじゃないんだよ!
あ〜イライライライラ。。。。
とりあえずアップロードしたファイルをpythonで好きなディレクトリに保存したいだけなんじゃ!
という場面があると思います(ありました)。
ということで、モデルもデータベースも関係無く、とりあえずformからアップロードしたファイルを任意の場所に保存するだけの方法をご紹介します。
*ここからはDjangoでurls、views、templatesが理解出来ている状態の方を対象にしています。
templateでHTMLに書くformの必用事項はこれだけ。
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p><input type="file" name="upfile"/></p>
<p><input type="submit" name="btn_upload" value="UPLOAD"/></p></form>
まず、POST方式で、ファイルをアップロードする場合はenctype="multipart/form-data"の一文が必用だそうです。
csrf_tokenはDjangoでお決まりのPOST時に必用なhiddenコードです。これが無いとPOSTできません。(Djangoに備わっているCSRF攻撃対策です)
ファイルのアップロード画面を作るのは<input type="file" name="upfile"/>これだけ。
これでファイル選択ボタンが生成されます。なんて便利な!
ーーー コレ↓ ーーーー
ーーーーーーーーーーー
最後に、アップロードファイルをサーバーにリクエストするsubmitボタンを配置して完了です。
つまり、Djangoで必用なのは{% csrf_token %}だけで、その他は一般的なHTMLコードです。
<参考>
HTMLタグ/フォームタグ/ファイルの送信欄を作る - TAG index
で、Django側でどうやって受け取るの?という話ですが、
requestを受け取ったら、
fname = '/home/djang/testfiles.jpg' #固定の任意の名前.jpgファイルを想定
if request.method == 'POST': # 1
updata = request.FILES.get('upfile') # 2
f = open(fname,'wb+') # 3
for chunk in updata.chunks(): # 4
f.write(chunk) # 5
f.close() # 6
以上です。
これで「/home/djang/testfiles.jpg」にアップロードしたファイルが保存されます。
何をしているかというと、
1ではPOSTリクエストの場合のみ処理をさせています。(データを同じアドレスに送ってるから)
2ではformからのinputメソッドにあるname='upfile'のデータを受け取っています。
通常のPOSTデータのvalue値の受取はrequest.POST.get('upfile')ですが、データファイルの受取はFILESを使うそうです。
参考したサイトではrequest.FILES['upfile']というコードがよく例示してありますが、その場合、nameに'upfile'が無いとエラーが発生してしまいます。(辞書形式のファイルなので)
.get('upfile')とすることでエラーなく(値が存在しない場合はNone)結果を受け取ることが出来ますのであえてgetを使っています。
3ではファイルにバイナリモードを指定しています。
4では.chunks()を使っています。チャンクデータと呼ぶそうであるサイズで分割されたバイナリデータを生成するようです。
僕もよく解っていませんが、実際にupfile自体をwrite()しようとすると
a bytes-like object is required, not 'InMemoryUploadedFile'
というエラーを吐いて怒られます(ました)。
これは、requestによって渡される値はメモリーに格納したことを示すだけのデータで実態がないデータということでしょう(たぶん)。
そこでchunks()を使ってメモリーから実際のデータを分割して読み出す作業をしている。
という認識ですが、もし違ってたら教えてください(汗)
5でclose()処理をしていますが、参考サイトにはどこにもこの処理が書かれていませんでしたので不要かもしれません、が気持ちが悪いのでエラーも出ないので一応書いています。
なお、ファイルの名前は
fname = updata.name
とすることで、元のファイル名をそのまま受け取る事も可能なのですが、
../../myapps/setting.py
などのように、悪意のあるファイル名でもそのまま使うようなコードを組んでしまったら(上例の場合、上位ディレクトリのsetting.pyファイルを書き換えると仮定)システムに多大なる損害が及ぶことがあるので、データに限らずファイルに任意の名前を使って保存させることは避けるべきです。
<参考>パス・トラバーサル攻撃
https://jvndb.jvn.jp/ja/cwe/CWE-22.html
今回は以上です。