ファイルのアップロード

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

警告

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

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

Consider a form containing a 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'] でアクセスすることができます。

Note that request.FILES will only contain data if the request method was POST, at least one file field was actually posted, and the <form> that posted the request has the attribute enctype="multipart/form-data". Otherwise, request.FILES will be empty.

Most of the time, you'll pass the file data from request into the form as described in Binding uploaded files to a form. This would look something like:

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})

If you are constructing an object manually, you can assign the file object from request.FILES to the file field in the model:

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})

If you are constructing an object manually outside of a request, you can assign a File like object to the FileField:

from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile


class MyCommand(BaseCommand):
    def handle(self, *args, **options):
        content_file = ContentFile(b"Hello world!", name="hello-world.txt")
        instance = ModelWithFileField(file_field=content_file)
        instance.save()

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

If you want to upload multiple files using one form field, create a subclass of the field's widget and set the allow_multiple_selected attribute on it to True.

In order for such files to be all validated by your form (and have the value of the field include them all), you will also have to subclass FileField. See below for an example.

Multiple file field

Django is likely to have a proper multiple file field support at some point in the future.

forms.py
from django import forms


class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultipleFileField(forms.FileField):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault("widget", MultipleFileInput())
        super().__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        single_file_clean = super().clean
        if isinstance(data, (list, tuple)):
            result = [single_file_clean(d, initial) for d in data]
        else:
            result = single_file_clean(data, initial)
        return result


class FileFieldForm(forms.Form):
    file_field = MultipleFileField()

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

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


class FileFieldFormView(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)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        files = form.cleaned_data["file_field"]
        for f in files:
            ...  # Do something with each file.
        return super().form_valid()

警告

This will allow you to handle multiple files at the form level only. Be aware that you cannot use it to put multiple files on a single model instance (in a single field), for example, even if the custom widget is used with a form field related to a model FileField.

Changed in Django 3.2.19:

In previous versions, there was no support for the allow_multiple_selected class attribute, and users were advised to create the widget with the HTML attribute multiple set through the attrs argument. However, this caused validation of the form field to be applied only to the last file submitted, which could have adverse security implications.

アップロードハンドラ

サイトのユーザがファイルをアップロードした時、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 がデータストリームをディスクに書き込むにつれ、このファイルのサイズが大きくなってゆくのを観察することができるでしょう。

These specifics -- 2.5 megabytes; /tmp; etc. -- are "reasonable defaults" which can be customized as described in the next section.

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

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

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

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

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

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

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

If you want to replace the upload handlers completely, you can assign a new list:

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

If you are using a class-based view, you will need to use csrf_exempt() on its dispatch() method and csrf_protect() on the method that actually processes the request. Example code:

from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect


@method_decorator(csrf_exempt, name="dispatch")
class UploadFileView(View):
    def setup(self, request, *args, **kwargs):
        request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
        super().setup(request, *args, **kwargs)

    @method_decorator(csrf_protect)
    def post(self, request, *args, **kwargs):
        ...  # Process request