ファイルのアップロード

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

警告

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

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

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 で、少なくとも1つのファイルフィールドが実際に投稿され、かつリクエストをポストした <form> の属性に enctype="multipart/form-data" が設定されていた場合に限られることに注意してください。そうでない場合には、request.FILES は空の辞書になります。

ほとんどの場合、アップロードされたファイルをフォームにバインドする で説明されているように、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})

リクエストの外で手動でオブジェクトを構築する場合は、 FileFieldFile のようなオブジェクトを代入できます:

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

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

1つのフォームフィールドを使用して複数のファイルをアップロードしたい場合は、フォームのウィジットのサブクラスを作成し、allow_multiple_selected クラス属性を True に設定してください。

このようなファイルをすべてフォームで検証する (そしてフィールドの値にすべてを含める) ためには、 FileField をサブクラス化する必要があります。例を以下に示します。

複数ファイルのフィールド

Django は将来、適切な複数ファイルフィールドをサポートするようになる予定です。

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 サブクラスの form_valid() メソッドをオーバーライドします。

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 form_valid(self, form):
        files = form.cleaned_data["file_field"]
        for f in files:
            ...  # Do something with each file.
        return super().form_valid(form)

警告

これにより、フォームレベルでのみ複数のファイルを扱うことができるようになります。例えば、カスタムウィジェットがモデル FileField にリレーション先のモデルフィールドで使用されている場合でも、複数のファイルを単一のモデルインスタンス(単一のフィールド)に入れることはできません。

アップロードハンドラ

サイトのユーザがファイルをアップロードした時、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 のファイルアップロードの動作を制御するための設定がいくつかあります。詳しくは、 ファイルアップロードの設定 を参照してください。

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

時として特定のビューが異なるアップロード動作を必要とすることがあります。このような場合には、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
    ...

クラスベースのビューを使用している場合は、その dispatch() メソッドで csrf_exempt() を使用し、実際にリクエストを処理するメソッドで csrf_protect() を使用する必要があります。下記はコード例です:

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
        ...
Back to Top