ファイルアップロード

revision-up-to:17812 (1.4)

多くの Web サイトにとって、ファイルアップロードのサポートは不可欠ですね。 Django がアップロードされたファイルを扱うとき、ファイルデータは最終的に request.FILES に入ります (request オブジェクトの詳細は リクエスト/レスポンスオブジェクト のドキュメ ントを参照してください)。このドキュメントでは、ファイルがどのようにしてディ スクやメモリ上に保存されるかを説明し、そのデフォルトの動作をカスタマイズす る方法について説明します。

ファイルアップロードの基本

FileField を含む以下のような簡単なフォームを考えて みましょう:

from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file  = forms.FileField()

このフォームからの入力を扱うビューは、ファイルデータを request.FILES で受け取ります。 request.FILES は ファイルデータの入った辞書で、辞書のキーはフォームクラス中の FileField (または ImageFieldFileField のサブクラス) の名前です。従って、 上の例では、 request.FILES['file'] でファイルデータにアクセスできます。

もしリクエストメソッドが POST であり、尚且つ <form> において アトリビュート enctype="multipart/form-data" を含むリクエストが ポストされたのであれば、 request.FILES はデータのみを含んでいる事に注意して下さい。 そうでない場合 request.FILES は空です。

ほとんどの場合は、「 アップロードされたファイルをフォームに結びつける 」の節で説明した方法に従っ て、 request からデータを取り出してフォームに渡すだけでアップロードファ イルを処理できます:

from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
# アップロードファイルを処理する関数を import します
from somewhere import handle_uploaded_file

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES['file'])
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render_to_response('upload.html', {'form': form})

request.FILES を明示的にフォーム のコンストラクタに渡さねばならないので注意してください。 これはフォームにファイルデータを結びつけるために必要な手順です。

アップロードファイルの処理

class UploadedFile

パズルの最後のピースは request.FILES からの実際のデータファイルをハンドリングすることです。 この辞書の各エントリーはアップロードされたファイルをシンプルなラッパで 包んだ UploadedFile オブジェクトです。 通常、アップロードされた内容にアクセスする為にこれらのメソッドのいずれか を使う事になるでしょう:

read()

アップロードされたデータ全体をファイルから読み出します。このメソッ ドを使うときに十分注意してください。というのも、アップロードされた ファイルが巨大だと、メモリに読み込む際にシステムの容量を越してしま うかもしれないからです。そのようなときは、後述の chunks() を使う とよいでしょう。

multiple_chunks()

ファイルが大きくて、複数のチャンクに分けて読み出すべきである場合に True を返します。デフォルトの設定では、 2.5 Mbytes より大きなファ イルに対して True を返します。サイズの閾値は設定でき、後で説明 します。

chunks

ファイルのチャンクを返すジェネレータです。 multiple_chunks()True の場合には、 read() ではなくこのメソッドを使ってくださ い。

実際には、常に chunks() を使うのがよいでしょう。後述の例を参照 してください。

name

my_file.txt のような、アップロードされたファイルの名前です。

size

アップロードファイルのサイズ(単位バイト)です。

UploadedFile オブジェクトは他にもいくつかメソッドや属性を備えています。 詳しくは UploadedFile オブジェクト を参照してください。

まとめると、アップロードファイルの一般的な処理例は以下の通りです:

def handle_uploaded_file(f):
    destination = open('some/file/name.txt', 'wb+')
    for chunk in f.chunks():
        destination.write(chunk)
    destination.close()

上の例では、巨大なファイルを read() してシステムの容量を超えないように、 UploadedFile.chunks() を使っています。

アップロードされたファイルの保存先

アップロードされたファイルデータは、処理され保存される前に、システムのどこ かに一時的に記憶されていなければなりません。

デフォルトの設定では、アップロードされたファイルデータが 2.5 Mbytes より小 さければ、 Django はファイルデータ全体をメモリに保持します。そのため、ファ イルデータの保存処理はメモリからディスクへの書き込みだけで実現され、高速で す。

しかし、ファイルデータが大きすぎる場合、 Django はファイルデータをシステム のテンポラリディレクトリに一時ファイルとして保存します。従って、 *nix ライ クのプラットフォームでは、 Django は /tmp/tmpzfp6I6.upload のような ファイルを生成します。ファイルデータがとても大きければ、 Django がデータを ディスクにストリーム書き込みするにつれて、一時ファイルのサイズが増えてゆく のを観察できるでしょう。

2.5 Mbytes や /tmp といった仕様は、単に「妥当なデフォルト値」にすぎませ ん。アップロード時の挙動をカスタマイズしたり完全に置き換えたりしたければ、 この後の詳細説明に進んでください。

アップロードファイルハンドラの挙動を変更する

ファイルアップロードの挙動は、以下の 3 つの設定で制御できます:

FILE_UPLOAD_MAX_MEMORY_SIZE
アップロードされたファイルをメモリに保存する上限のサイズです。 設定値よりも大きなファイルがアップロードされると、ファイルデータは ディスクに書き込まれます。 デフォルト値は 2.5 Mbytes です。
FILE_UPLOAD_TEMP_DIR

FILE_UPLOAD_MAX_MEMORY_SIZE より大きなファイルがアップロー ドされたときにファイルデータを保存するディレクトリです。

デフォルト値はシステム標準の一時ファイルディレクトリです (UNIX ライクのシステムでは /tmp です)。

FILE_UPLOAD_PERMISSIONS

アップロードされたファイルに設定するファイルモードで、数字で表現 (例: 0644) します。ファイルモードの意味は os.chmod() のドキュメントを参照してください。

この値を設定しないか、 None にすると、アップロードされたファイ ルのモードはオペレーティングシステムに依存します。ほとんどのプラッ トフォームでは、一時ファイルのファイルモードは 0600 で、メモリ からファイルにデータを書き出すときにはシステム標準の umask を使います。

Warning

ファイルモードにあまりくわしくないのなら、先頭の 0 がとても 重要だということに注意してください。先頭の 0 は、値が 8 進数で あることを示しています。 644 のように指定すると、全くおかし な挙動になってしまうでしょう。

ファイルモードの先頭には常に 0 をつけてください.

FILE_UPLOAD_HANDLERS

アップロードファイルを処理する実際のハンドラです。 この設定を変更すると、ファイルアップロードの処理を完全にカスタマイ ズでき、 Django のアップロード処理全体の置き換えすらできます。 詳しくは後述の アップロードハンドラ を参照してください。

デフォルト値は以下の通りです:

("django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

このデフォルトの設定では、アップロードされたファイルをまずメモリに 保存しようと試み、その後ファイルへの保存を試みます。

UploadedFile オブジェクト

File から継承したものの他に、 UploadedFile オブジェクトは、以 下のメソッドや属性を定義しています。

UploadedFile.content_type

content-type ヘッダの内容 (text/plainapplication/pdf など) です。ユーザが指定した他のデータ同様、 この値がアップロードされたファイルのコンテンツタイプを正確に表してい ると信用してはなりません。アップロードされたファイルのコンテンツタイプ が content-type ヘッダと一致しているか確かめておく必要があります。 「値は一応信用するが、検証し」てください。

UploadedFile.charset

content-typetext/* のときに、ブラウザが指定した 文字セット (utf8 など) です。「値は一応信用するが、検証する」よう にしてください。

UploadedFile.temporary_file_path

アップロードファイルがディスクに一時保存されているときにのみ使える メソッドで、一時ファイルの完全なパスを返します。

Note

通常の Python のファイルオブジェクトと同じく、アップロードファイルをイ テレータとして扱うと、ファイルの内容を一行づつ読めます:

for line in uploadedfile:
    do_something_with(line)

しかし、標準の Python ファイルとは 違ってUploadedFile は 改行文字として \n (いわゆる Unix形式) しか認識しません。 \n 以 外の改行で終端されたアップロードファイルを処理する必要があるのなら、ビュ ー内で適切に処理する必要があります。

アップロードハンドラ

ユーザがファイルをアップロードすると、 Django はファイルデータを アップロードハンドラ (upload handler) と呼ばれるクラスに渡します。このク ラスはアップロードされたファイルデータを処理するためのクラスです。 デフォルトのアップロードハンドラは FILE_UPLOAD_HANDLERS で以下の ように定義されています:

("django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

MemoryFileUploadHandlerTemporaryFileUploadHandler は、Django の デフォルトのファイルアップロード処理、すなわち、小さいファイルはメモリに読 み込み、大きいファイルはディスクに読み込むという動作を実現します。

カスタムのハンドラを書けば、ファイルの処理方法をカスタマイズできます。例え ば、カスタムハンドラによってユーザレベルで容量制限を課したり、データをオン ザフライで圧縮したり、プログレスバーを描画したり、受け取ったデータをローカ ルに保存せず、直接別のストレージに送ったりできます。

アップロードハンドラをオンザフライで変更する

ビューごとに別々のアップロード処理を行いたい場合があります。その場合、 リクエストごとに request.upload_handlers を変更してアップロードハンドラ のチェインをオーバライドできます。デフォルトでは、 request.upload_handlers には FILE_UPLOAD_HANDLERS に指定した ハンドラのリストが入っていますが、これを他のリストに変更できます。

例えば、 ProgressBarUploadHandler というハンドラを書いて、 Ajax ウィジェットに何らかのアップロード状態のフィードバックを返させたいとしましょ う。以下のようにすれば、アップロードハンドラチェインに自作のハンドラを付加 できます:

request.upload_handlers.insert(0, ProgressBarUploadHandler())

この場合は、プログレスバーのハンドラを他のハンドラ よりも前に 実行したい ので、 (append() ではなく) list.insert() を使うことになるでしょう。 アップロードハンドラチェインは、順番に実行されることを思い出してください。

完全にアップロードハンドラチェインを置き換えてしまいたければ、単にリストを 代入してください:

request.upload_handlers = [ProgressBarUploadHandler()]

Note

アップロードハンドラチェインを変更できるのは、 request.POSTrequest.FILES にアクセスする よりも前 だけです。アップロードの処 理が始まってしまった後でハンドラを変更しても何の意味もありません。 request.POSTrequest.FILES を読み出した後で request.upload_handlers を変更しようとすると、 Django はエラーを送 出します。

従って、アップロードハンドラチェインはビュー処理の出来るだけ早い段階で 変更しておくことになるでしょう。 また request.POST は通常与えられる CsrfViewMiddleware によって許可されます。 これはビューの中でアップロードハンドラの変更をする為に csrf_exempt() を使う必要がある事を 意味します。 またそのとき csrf_protect() を リクエストの実際のプロセスの関数上で使う必要があります。 このときハンドラがCSRFチェックが行われる前にファイルアップロードを受け取 り始める可能性がある事に注意して下さい。 Example code:

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def upload_file_view(request):
    request.upload_handlers.insert(0, ProgressBarUploadHandler())
    return _upload_file_view(request)

@csrf_protect
def _upload_file_view(request):
    ... # Process request

カスタムのアップロードハンドラを書く

ファイルアップロードハンドラは、全て django.core.files.uploadhandler.FileUploadHandler のサブクラスでなけれ ばなりません。アップロードハンドラはどこに記述してもかまいません。

必須のメソッド

カスタムのファイルアップロードハンドラは以下のメソッドを 必ず 定義せねばなりません:

FileUploadHandler.receive_data_chunk(self, raw_data, start)

ファイルアップロード時に、データの「チャンク」を受け取ったときに呼 び出されます。

raw_data はアップロードされたデータの入ったバイト文字列です。

startraw_data チャンクのファイルデータ中の開始位置です。

このメソッドが返すデータは、次のアップロードハンドラの receive_data_chunk メソッドにフィードされます。この仕組みによって、 あるハンドラにデータを「フィルタ」させ、他のハンドラに入力できます。

receive_data_chunk で受け取ったデータを後続のハンドラに処理させたく ない場合には None を返してください。このメソッドは、アップロードさ れたデータを自分で処理して、他のハンドラにデータを保存させたくない場合に 便利です。

StopUploadSkipFile といった例外を送出すると、アップロード 処理は中断し、アップロードファイルの処理を行いません。

FileUploadHandler.file_complete(self, file_size)

ファイルのアップロードが終了したときに呼び出されます。

このハンドラは request.FILES に入れるための UploadedFile オブジ ェクトを返さねばなりません。後続のアップロードハンドラに UploadedFile を返させたい場合は None を返してください。

オプションのメソッド

カスタムのアップロードハンドラでは、以下のオプションのメソッドや属性を定義 できます:

FileUploadHandler.chunk_size

Django がアップロードファイルをメモリ上に読み込み、ハンドラに渡すときに 使うチャンクのサイズです。バイト単位です。この属性は、 FileUploadHandler.receive_data_chunk で読み込まれるチャンクのサイズ 指定でもあります。

パフォーマンスを最大化するには、チャンクのサイズを 4 の倍数とし、 2GB (231 バイト) 以下とすべきです。複数のハンドラがそれぞれ別々 のチャンクサイズを提供していた場合、 Django は最小のチャンクサイズを使い ます。

デフォルト値は 64*210 バイトまたは 64 KB です。

FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)

新たなファイルアップロードが開始されるときに呼び出されるコールバックです。 このメソッドは、アップロードハンドラがまだデータを受け取っていない段階で 呼び出されます。

field_name はファイルの <input> フィールドの名前です。

file_name は unicode のファイル名としてブラウザから提供された値です。

content_type'image/jpeg' のような MIME タイプで、ブラウザから 提供された値です。

content_length はファイルの長さで、ブラウザから提供された値です。 ブラウザがファイルサイズを提供しないこともあり、その場合は None です。

charset は (utf8 のような) 文字セットで、ブラウザから提供さ れた値です。 content_length と同様、指定されないこともあります。

他のハンドラにファイルを処理させたくない場合、このメソッドから StopFutureHandlers 例外を送出してもかまいません。

FileUploadHandler.upload_complete(self)
ファイル全体のアップロードが終了したときに呼び出されるコールバックです。
FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)

このメソッドを使うと、生の HTTP 入力の解析を完全にオーバライドできます。

input_dataread() をサポートするファイルライクオブジェクトです。

METArequest.META と同じです。

content_lengthinput_data に入っているデータの長さです。 input_data から content_length バイト以上を読み出そうとしてはなり ません。

boundary はリクエストの MIME バウンダリです。

encoding はリクエストのエンコーディングです。

アップロードの処理を他のハンドラに継続させたい場合には None を、 リクエストの直接処理に適したデータ構造を新たに生成して返したければ (POST, FILES) のタプルを返してください。