# Django 5.0.9.dev20240806133106 ドキュメント

# ファイルのアップロード

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()` を呼び出すだけで、対応する `FileField` の `upload_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})

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

    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` にリレーション先のモデルフィールドで使用されている場合でも、複数のファイルを単一のモデルインスタンス（単一のフィールド）に入れることはできません。

Changed in Django 3.2.19:

以前のバージョンでは `allow_multiple_selected` クラス属性がサポートされていなかったため、ユーザは `attrs` 引数で HTML 属性 `multiple` を指定してウィジェットを作成することが推奨されていました。しかし、これはフォームフィールドのバリデーションが最後に送信されたファイルだけに適用される原因となり、セキュリティ上不利な影響を与える可能性がありました。

## アップロードハンドラ

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

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

`MemoryFileUploadHandler` と `TemporaryFileUploadHandler` はともに、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.POST` や `request.FILES` にアクセスする 前に のみ修正できます。処理が始まってからアップロードハンドラを修正しても無意味だからです。もし `request.POST` や `request.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
            ...