当 Django 处理文件上传时,文件数据最终会被放置在 request.FILES
(关于 request
对象的更多信息,请参见 request 和 response 对象 的文档)。本文档解释了文件如何存储在磁盘和内存中,以及如何自定义默认行为。
警告
如果接收不受信任的用户的上传会有安全隐患, 请阅读 用户上传内容 获取详情。
考虑一个包含 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']
的形式被访问。
注意 request.FILES
只有当请求方法是 POST
,至少有一个文件字段被实际发布,并且发布请求的 <form>
有 enctype="multipart/form-data"
属性时,才会包含数据。否则 request.FILES
将为空。
大多数情况下,你需要像 将上传的文件绑定到表单中 里描述的那样将文件数据从 request
传递给表单。示例如下:
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)
使用 UploadedFile.chunks()
而不是 read()
是为了确保即使是大文件又不会将我们系统的内存占满。
There are a few other methods and attributes available on UploadedFile
objects; see UploadedFile
for a complete reference.
如果想要在 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
的文件对象到模型里的文件对象:
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.
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
方法来控制多个文件上传:
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
.
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",
]
MemoryFileUploadHandler
和 TemporaryFileUploadHandler
提供 Django 默认文件上传行为,小文件读入内存,大文件存在磁盘上。
你可以编写自定义的 handlers 来自定义 Django 如何处理文件。比如,你可以使用自定义的 handlers 来强制处理用户层面的配额,动态压缩数据,渲染进度条,甚至可以将数据发送到其他存储地址而不是本地。查看 编写自定义上传处理程序 来了解你如何自定义或者完全替换上传行为。
在保存上传的文件之前,数据需要保存到某处。
默认情况下,如果上传的文件小于2.5兆,Django 将把文件的所有内容保存到内存里。这意味着保存文件只涉及从内存中读取和写入磁盘,因此这很快。
但如果上传的文件很大,Django 会把文件写入系统临时目录的临时文件里存储。在类 Unix 平台里这意味着 Django 会生成一个类似名为 /tmp/tmpzfp6I6.upload
的文件。如果上传的文件非常大,你可以查看这个文件的大小增长,因为 Django 将数据流式传输到磁盘上。
2.5 megabytes; /tmp
; 等这些细节只是合理的默认值,在下一节要介绍它们如何被自定义。
这里有一些控制 Django 文件上传行为的配置,请查看 File Upload Settings 。
有时候某些视图需要不同的上传行为。在这些例子里,你可以基于每个请求覆盖上传处理程序。默认情况下,这个列表将包含由 FILE_UPLOAD_HANDLERS
设置的上传处理程序,但你可以像修改其他列表一样修改这个列表。
比如,假设你正在编写 ProgressBarUploadHandler
,来提供在上传过程中的反馈给 Ajax widget。你需要添加这个处理程序到你的上传处理模块:
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
你或许想在这里使用 list.insert()
(而不是 append()
),因为进度条处理程序需要在其他处理程序之前使用。记住,上传处理程序是按顺序处理的。
如果你想完全替换上传处理程序,你需要指定新列表:
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
如果你使用的是基于类的视图,你需要在其 csrf_exempt()
方法上使用 dispatch()
,并在实际处理请求的方法上使用 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
5月 31, 2023