ファイルのアップロードLink to this heading

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

ファイルのアップロードの基本Link to this heading

Consider a form containing a FileField:

forms.py
Python
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
Python
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 をフォームのコンストラクタに渡す必要があることに注意してください。

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

Code
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 を読んでください。

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

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

Code
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:

Code
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:

Code
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()

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

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.

forms.py
Python
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
Python
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()

アップロードハンドラLink to this heading

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

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

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

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

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

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

デフォルトでは、アップロードファイルが 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.

アップロードハンドラの動作の変更Link to this heading

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

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

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

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

Code
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:

Code
request.upload_handlers = [ProgressBarUploadHandler(request)]