Django がファイルアップロードを扱うとき、ファイルデータは request.FILES
に格納されます (request
についての詳細は request and response objects をご覧ください)。ここでは、ファイルがどのようにディスクとメモリに保管され、またどうやってデフォルトの動作を変更するかを説明します。
警告
信頼できないユーザーからコンテンツアップロードを許可する場合、セキュリティリスクが存在します!緩和策の詳細は、ユーザーがアップロードしたコンテンツ のセキュリティガイドをご覧ください。
Consider a form containing a FileField
:
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:
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})
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()
1 つのフォームフィールドで複数のファイルをアップロードしたい場合、フィールドのウィジェットの multiple
HTML 属性をセットしてください:
from django import forms
class FileFieldForm(forms.Form):
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
そして、複数のファイルアップロードを扱うために、あなたの FormView
サブクラスの post
メソッドをオーバーライドします:
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)
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"]
MemoryFileUploadHandler
と TemporaryFileUploadHandler
はともに、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.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
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
2022年6月01日