# Django 6.0.7 documentation

# 文件上传

当 Django 处理文件上传时，文件数据最终会被放置在 `request.FILES` （关于 `request` 对象的更多信息，请参见 request 和 response 对象 的文档）。本文档解释了文件如何存储在磁盘和内存中，以及如何自定义默认行为。

Warning

如果接收不受信任的用户的上传会有安全隐患， 请阅读 用户上传内容 获取详情。

## 简单文件上传

考虑一个包含 `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()` 是为了确保即使是大文件又不会将我们系统的内存占满。

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

如果你在请求之外手动构造一个对象，你可以将一个类似于 `File` 的对象分配给 `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()

然后覆盖 `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)

Warning

这将允许你仅在表单级别处理多个文件。请注意，即使使用自定义小部件与与模型 `FileField` 相关的表单字段一起使用，也不能将多个文件放在单个模型实例的单个字段中。

## 上传 Handlers

当一个用户上传文件时，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))

Using `list.insert()`, as shown above, ensures that the progress bar handler is placed at the beginning of the list. Since upload handlers are executed in order, this placement guarantees that the progress bar handler runs before the default handlers, allowing it to track progress across the entire upload.

如果你想完全替换上传处理程序，你需要指定新列表：

    request.upload_handlers = [ProgressBarUploadHandler(request)]

Note

你只能在访问 `request.POST` 或 `request.FILES` 之前修改上传处理程序，开始上传处理后修改上传处理程序没有意义。如果你从读取 `request.POST` 或 `request.FILES` 之后尝试修改 `request.upload_handlers` ，Django 会报错。

因此，你要尽早在视图里修改上传处理程序。

Also, `request.POST` is accessed by `CsrfViewMiddleware` which is enabled by default. This means you will need to use `csrf_exempt()` on your view to allow you to change the upload handlers. You will then need to use `csrf_protect()` on the function that actually processes the request. Note that this means that the handlers may start receiving the file upload before the CSRF checks have been done. Example code:

    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
            ...