Django上传文件的正确访问器

时间:2011-03-21 23:17:44

标签: python django django-forms http-post

假设Django形式如下......

class MyForm(forms.Form):
    attachment = forms.FileField()

我看过很多教程都使用request.FILES['attachment']访问上传的Django文件,但我的印象是,只要有可能,您应该通过POST访问form.cleaned_data['attachment']个数据。

是否有理由使用request.FILES[]?这些对象是否应包含完全相同的数据?

1 个答案:

答案 0 :(得分:7)

在HTML中创建表单时,它具有特定的编码(或将数据发送到服务器的方法)。默认情况下,编码为application/x-www-form-urlencoded,它实质上是在单个字符串中发送表单详细信息。但是,如果要将文件上传到服务器,则需要将编码设置为multipart/form-data(这就是您在关于该主题的所有教程中都会注意到的enctype="..."行)。这将以多个部分发送数据,每个表单字段一个。有关两种编码如何显示的示例see here

当Django遇到multipart/form-data编码时,它会将收到的数据拆分为两个词典:request.FILES词典包含上传的任何文件,而request.POST包含任何其他表单域。如果您感兴趣,则处理由MultiPartParser文件中的django/http/__init__.py类完成。

为了说明如何将这些数据呈现给您的代码,我们创建一个简单的应用程序。首先,让我们创建一个由字符字段和文件字段组成的简单表单:

from django import forms

class TestForm(forms.Form):
    name = forms.CharField()
    file = forms.FileField()

接下来,我们将创建一个简单的视图来创建表单,将任何提交的数据绑定到它,并通过模板呈现它:

from django.shortcuts import render_to_response
from django.template import RequestContext

from forms import TestForm

def show_form(request):
    if request.method == 'POST':
        form = TestForm(request.POST, request.FILES)
    else:
        form = TestForm()

    context = {
        'form': form
    }
    return render_to_response('show_form.html', context, RequestContext(request))

最后,我们将使用模板显示表单以及有关请求和表单的一些信息:

<html>
    <head>
        <title>Django forms - file test</title>
    </head>

    <body>
        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <input type="submit" />
        </form>

        <h2>Request details</h2>

        <p>
        Request method: {{ request.method }}
        <br />
        POST data: {{ request.POST|default:"No data" }}
        <br />
        FILES data: {{ request.FILES|default:"No data" }}
        </p>

        <h2>Form details</h2>

        <p>
        Cleaned data: {{ form.cleaned_data|default:"No data" }}
        </p>

    </body>
</html>

请注意,您需要在设置中启用django.core.context_processors.request上下文处理器,以查看有关请求的详细信息。

如果我们然后启动服务器并将浏览器指向视图,我们会看到我们期望看到的内容 - 空表单,请求模式是GET,并且没有POST,FILES或表单数据。< / p>

接下来,在字符字段中输入名称,但不要选择要上载的文件。提交后,我们会收到有关所需文件字段的预期错误。我们感兴趣的是有关请求的信息:


请求详细信息

请求方法:POST
POST数据:&lt; QueryDict:{u'csrfmiddlewaretoken':[u'b032358a4dbd71bc3a776c2ef41b09d9'],u'name':[u'Blair'],u'file':[u'']}&gt;
文件数据:无数据

表格详情

清理数据:无数据


由于浏览器没有发送文件信息,Django已将所有表单详细信息放入POST字典中,并将FILES字典留空。由于表单无效,因此没有与之关联的数据。

现在让我们尝试一下没有名字,但要上传一个文件:


请求详细信息

请求方法:POST
POST数据:&lt; QueryDict:{u'csrfmiddlewaretoken':[u'b032358a4dbd71bc3a776c2ef41b09d9'],u'name':[u'']}&gt;
FILES数据:&lt; MultiValueDict:{u'file':[&lt; InMemoryUploadedFile:image.pdf(application / pdf)&gt;]}&gt;

表格详情

清理数据:无数据


现在,已提交的数据已在POST和FILES词典之间分开。该文件可以通过request.FILES['file']访问,但不能通过表单清理数据访问,因为表单由于缺少名称而无效。由于我上传的文件很小,它存储在内存中;超过一定大小(默认为2.5MB)的文件将存储在临时目录中,但您的代码可以处理它们。

最后,让我们尝试两个字段的值:


请求详细信息

请求方法:POST
POST数据:&lt; QueryDict:{u'csrfmiddlewaretoken':[u'b032358a4dbd71bc3a776c2ef41b09d9'],u'name':[u'Blair']}&gt;
FILES数据:&lt; MultiValueDict:{u'file':[&lt; InMemoryUploadedFile:image.pdf(application / pdf)&gt;]}&gt;

表格详情

清理数据:{'name':u'Blair','file':&lt; InMemoryUploadedFile:image.pdf(application / pdf)&gt;}


由于数据有效并绑定到表单,因此也可以通过表单的cleaned_data访问该文件。

通过request.FILES访问它有一个潜在的好处:如果表单无效,您仍然可以在要求用户更正数据之前将文件保存在某处。这可以防止必须再次上传文件(如果您处理大文件,这在时间和带宽方面可能非常昂贵)。如果您只想处理小文件,它不会有太大区别,但使用request.FILES可能是更好的做法。这也是Django file upload documentation的作用。