文件上传

当Django处理一个文件上传时,文件数据最终放在 request.FILES (更多关于 request 对象参见文档 request and response objects )本文档解释文件如何存储在磁盘和内存中,以及如何自定义默认行为。

警告

如果您接受来自不受信任用户的上载内容,则存在安全风险!参见安全指南主题 用户上载的内容 有关缓解措施的详细信息。

基本文件上载

考虑一个包含 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 ,至少有一个文件字段实际已发布,并且 <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)

循环过度 UploadedFile.chunks() 而不是使用 read() 确保大文件不会占用系统内存。

有一些其他方法和属性可用于 UploadedFile 对象;参见 UploadedFile 作为完整的参考。

使用模型处理上载的文件

如果要在 Model 用一个 FileField ,使用 ModelForm 使这个过程更容易。文件对象将保存到 upload_to 对应的参数 FileField 调用时 form.save() ::

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

如果在请求之外手动构造对象,则可以将 File 的LIKE对象 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()

上载多个文件

如果要使用一个表单域上传多个文件,请创建该域的微件的子类并设置 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()

然后重写 post 你的方法 FormView 处理多个文件上载的子类:

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

警告

这将允许您仅在表单级别处理多个文件。请注意,您不能使用它在单个模型实例上放置多个文件(例如,在单个字段中),即使自定义小部件与与模型相关的表单域一起使用 FileField

上传处理程序

当用户上载文件时,django将文件数据传递给 上传处理程序 --一个小类,在文件数据上传时处理它。上载处理程序最初在 FILE_UPLOAD_HANDLERS 设置,默认为:

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

一起 MemoryFileUploadHandlerTemporaryFileUploadHandler 提供Django的默认文件上载行为,即将小文件读取到内存中,将大文件读取到磁盘上。

您可以编写自定义处理程序来定制django处理文件的方式。例如,您可以使用自定义处理程序强制执行用户级配额、动态压缩数据、呈现进度条,甚至直接将数据发送到另一个存储位置,而不在本地存储数据。见 正在写入自定义上载处理程序 有关如何自定义或完全替换上载行为的详细信息。

上传数据的存储位置

在保存上载的文件之前,数据需要存储在某个地方。

默认情况下,如果上载的文件小于2.5兆字节,Django将在内存中保存上载的全部内容。这意味着保存文件只涉及从内存中读取和写入磁盘,因此速度非常快。

但是,如果上载的文件太大,Django会将上载的文件写入存储在系统临时目录中的临时文件。在类似Unix的平台上,这意味着您可以期望Django生成一个名为 /tmp/tmpzfp6I6.upload . 如果上载足够大,您可以看到当Django将数据流到磁盘上时该文件的大小增加。

这些细节——2.5兆字节; /tmp ;等等——是“合理的默认值”,可以按照下一节的描述进行定制。

更改上载处理程序行为

有一些设置控制Django的文件上传行为。见 File Upload Settings 有关详细信息。

动态修改上载处理程序

有时特定视图需要不同的上载行为。在这些情况下,可以通过修改 request.upload_handlers . 默认情况下,此列表将包含由 FILE_UPLOAD_HANDLERS ,但可以像修改任何其他列表一样修改该列表。

例如,假设你写了 ProgressBarUploadHandler 这提供了关于上传到某种Ajax小部件的进度的反馈。您可以将此处理程序添加到上载处理程序中,如下所示:

request.upload_handlers.insert(0, ProgressBarUploadHandler(request))

你可能想用 list.insert() 在这种情况下(而不是 append() )因为进度条处理程序需要运行 之前 任何其他处理程序。请记住,上载处理程序是按顺序处理的。

如果要完全替换上载处理程序,可以指定一个新列表:

request.upload_handlers = [ProgressBarUploadHandler(request)]

备注

只能修改上载处理程序 之前 访问 request.POSTrequest.FILES --在上载处理已经开始后更改上载处理程序是没有意义的。如果你试图修改 request.upload_handlers 在阅读之后 request.POSTrequest.FILES 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() 在ITS上 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