ファイルのアップロード

Django がファイルアップロードを扱うとき、ファイルデータは request.FILES に格納されます (request についての詳細は request and response objects をご覧ください)。ここでは、ファイルがどのようにディスクとメモリに保管され、またどうやってデフォルトの動作を変更するかを説明します。

警告

信頼できないユーザーからコンテンツアップロードを許可する場合、セキュリティリスクが存在します!緩和策の詳細は、User-uploaded content のセキュリティガイドをご覧ください。

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

FileField を含むシンプルなフォームを考えます:

forms.py
from django import forms

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

このフォームをハンドリングするビューは、ファイルのデータを request.FILES というディクショナリの中に受け取ります。このディクショナリには、キーと、それぞれのキーに対応するフォームの FileField (または ImageField または他の FileField のサブクラス) が格納されています。そのため、上のフォームから送信されたデータには、request.FILES['file'] でアクセスすることができます。

ただし、request.FILES にデータが格納されるのは、リクエストメソッドが POST で、かつリクエストをポストした <form> の属性に enctype="multipart/form-data" が設定されていた場合に限られることに注意してください。そうでない場合には、request.FILES は空のディクショナリになります。

ほとんどの場合、Binding uploaded files to a form で説明されているように、request からフォームへデータを引き渡すだけでしょう。その場合、下記のようになります:

views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm

# Imaginary function to handle an uploaded file.
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(request, 'upload.html', {'form': form})

ここで、request.FILES をフォームのコンストラクタに渡す必要があることに注意してください。

アップロードされたファイルをハンドルする一般的な方法は、次のようになります。

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

read() を使う代わりに UploadedFile.chunks() でループすることで、大きなサイズのファイルがアップロードされた時にメモリが専有されることを防げます。

UploadedFile オブジェクトには、他にもいくつかのメソッドと属性があります。完全なリファレンスについては、UploadedFile を読んでください。

モデルを使用したアップロードファイルのハンドリング

FileField を持つ Model 上のファイルを保存するときは、ModelForm を使えば、このプロセスはとても簡単になります。この場合、ファイルオブジェクトは、form.save() を呼び出すだけで、対応する FileFieldupload_to 引数で指定した場所に保存されます。

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField

def upload_file(request):
    if request.method == 'POST':
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # file is saved
            form.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = ModelFormWithFileField()
    return render(request, 'upload.html', {'form': form})

手動でオブジェクトを作成する場合には、request.FILES 内のファイルオブジェクトを、モデルの file_field 引数に指定するだけで大丈夫です。

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
from .models import ModelWithFileField

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

複数のファイルをアップロードする

1 つのフォームフィールドで複数のファイルをアップロードしたい場合、フィールドのウィジェットの multiple HTML 属性をセットしてください:

forms.py
from django import forms

class FileFieldForm(forms.Form):
    file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

そして、複数のファイルアップロードを扱うために、あなたの FormView サブクラスの post メソッドをオーバーライドします:

views.py
from django.views.generic.edit import FormView
from .forms import FileFieldForm

class FileFieldView(FormView):
    form_class = FileFieldForm
    template_name = 'upload.html'  # Replace with your template.
    success_url = '...'  # Replace with your URL or reverse().

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        files = request.FILES.getlist('file_field')
        if form.is_valid():
            for f in files:
                ...  # Do something with each file.
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

アップロードハンドラ

サイトのユーザがファイルをアップロードした時、Django はそのファイルのデータを アップロードハンドラ (upload handler) という、ファイルがアップロードされた時にデータをハンドリングするための小さなクラスへ渡します。アップロードハンドラは、初めに設定の FILE_UPLOAD_HANDLERS で定義されています。デフォルトでは次のようになっています。

["django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler"]

MemoryFileUploadHandlerTemporaryFileUploadHandler はともに、Django でファイルがアップロードされた時のデフォルトの動作――小さなファイルはメモリ上に保存し、大きなファイルはディスクに保存する――を提供しています。

カスタマイズハンドラを書けば、Django がファイルをハンドリングする方法をカスタマイズすることができます。たとえば、カスタムハンドラを使うことで、ユーザレベルでのクオータを設定したり、データをその場で圧縮したり、プログレスバーを表示したり、あるいは、送られたデータをローカルに保存せずに、別のストレージに転送することさえ可能です。ハンドラをカスタマイズしたり、アップロード時の動作を完全に置き換えたりする方法については、詳しくは アップロードハンドラをカスタマイズする を読んでください。

アップロードされたデータの保存場所

アップロードされたファイルを保存する時点で、そのデータはコンピュータ上のどこかに保存されているはずです。

デフォルトでは、アップロードファイルが 2.5 MB 未満ならば、Django はデータ全体をメモリ上に保持します。つまり、この場合にファイルを保存するというのは、メモリ上からデータを読み込んでディスクにファイルを書き込むだけなので、処理は短時間しかかかりません。

しかし、アップロードファイルのサイズが大きい場合、Django はアップロードファイルをシステムの一時ディレクトリ内に、一時ファイルとして保存します。Unix-like なプラットフォームなら、Django は /tmp/tmpzfp6I6.upload のようなファイルを作成すると考えて良いです。アップロードされたファイルが十分大きければ、Django がデータストリームをディスクに書き込むにつれ、このファイルのサイズが大きくなってゆくのを観察することができるでしょう。

これらの特定の設定――2.5 MB だとか /tmp ディレクトリなど――は、単に「常識的なデフォルト」として設定されているだけなので、次のセクションで説明するように、自分でカスタマイズすることができます。

アップロードハンドラの動作の変更

Django のファイルアップロードの動作を制御するための設定がいくつかあります。詳しくは、 File Upload Settings を参照してください。

その場でアップロードハンドラを修正する

時として特定のビューが異なるアップロード動作を必要とすることがあります。このような場合には、request.upload_handlers を修正することで、1リクエストごとに、アップロードハンドラをオーバーライドすることが可能です。デフォルトでは、このリストには FILE_UPLOAD_HANDLERS の設定で指定したアップロードハンドラが入っていますが、このリストは自由に修正することができます。

たとえば、アップロードの進行状況を計算して、AJAX のウィジェットなどにフィードバックを返す ProgressBarUploadHandler というハンドラを作ったとしましょう。このハンドラを、次のようにアップロードハンドラのリストに追加します。

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

この場合には、list.append() メソッドの代わりに list.insert() を使ったほうが良いでしょう。なぜなら、プログレスバーハンドラーは、他のハンドラの 前に 実行する必要があるでしょうから。アップロードハンドラは、リストの前から順に処理されることに注意してください。

アップロードハンドラ全体を置き換えたい場合は、新しいリストを代入するだけでも構いません。

request.upload_handlers = [ProgressBarUploadHandler(request)]

注釈

アップロードハンドラは、request.POSTrequest.FILES にアクセスする 前に のみ修正できます。処理が始まってからアップロードハンドラを修正しても無意味だからです。もし request.POSTrequest.FILES を読み込んだ後に request.upload_handlers を修正しようとしたならば、Django はエラーを投げます。

したがって、アップロードハンドラの修正は、常にビューのできるだけ早い段階で行うようにするべきです。

さらに、request.POST は、デフォルトで有効になっている CsrfViewMiddleware によってアクセスされます。つまり、アップロードハンドラを変更できるようにするためには、ビュー上で csrf_exempt() を使う必要があります。それから、実際にそのリクエストを処理する関数上で、csrf_protect() を使う必要があります。ハンドラは、CSRF チェックが終わる前にファイルアップロードの受け取りを開始する可能性があることに注意してください。以下はコードの例です:

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

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

@csrf_protect
def _upload_file_view(request):
    ... # Process request
Back to Top