Django Rest Framework 文件上传处理

问题

如何用 Django REST 上传一个文件字段?

首先,既然是 RESTful,那只能用 AJAX。

但是,如果涉及到文件的上传,如何进行 AJAX 的上传呢?这是个问题。

按照以往的经验,我们可以通过构造一个 form 的 markup,然后填写好内容之后,通过 jquery.form.js 这个插件进行异步上传。

但是这样绕得的确感觉有点远了,我们需要另外构造一个 markup,然后里面的 file 输入框还要与前端产生耦合。

于是很认真地在 stackoverflow 上面搜索这个问题。

一些额外的背景知识

首先,如果我们要上传一个文件,通过表单的话,如果通过自然的方式,这将与 form 标签的 enctype 属性有关:

<form method="post" enctype=multipart/form">
    <input type="text" name="username" />
    <input type="file" name="avatar" />
</form>

一般会这样,然后我们提交之后,浏览器会根据表单的内容构造一个 POST 请求的内容 (payload):

------WebKitFormBoundaryWwE7y8P3JK82rxsk
Content-Disposition: form-data; name="username"

admin
------WebKitFormBoundaryWwE7y8P3JK82rxsk
Content-Disposition: form-data; name="avatar"; filename="avatar.png"
Content-Type: image/png

------WebKitFormBoundaryWwE7y8P3JK82rxsk--

可以看到,普通的字段,以及文件,是通过这种方式上传的,如果是文件,那么分割的部分将包含了 Content-Type 的部分头信息。

我们在这个问题里,有一个很关键的问题,就是如何通过 jQuery 构造出来一个这样的 AJAX 请求。

后端的规矩

作为开始,我们先要搞清楚 REST Framework 用何种方式捕捉我们提交的文件字段:

官方文档很简单,只跟我们说了如何定义字段:http://www.django-rest-framework.org/api-guide/fields/#file-upload-fields

而实际上,如果 Model 定义的字段类型是 FileField 或者是 ImageField,ViewSet 的字段类型已经自动被定义为这种类型了。

然并卵,这样还没有说清楚。

但是,我们可以做一个大胆的假设,假设我们只要通过 multipart/form 的方式 post 过去,然后其中字段的名称跟 <input type="file" /> 的名称匹配上,后台就可以帮你处理好写入的问题。

实践证明,我的假设是对的(通过观察 HTML 版的 REST 调试工具,然后用 Chorme 观察 payload 就可以证明)。

那么,问题回来了,我们如何通过 jquery 构造出来这个 payload?

HTML 的新特性 FormData 对象

首先这是我在 stackoverflow 上面问到的主要相关问题:

http://stackoverflow.com/q/166221/2544762

http://stackoverflow.com/q/2320069/2544762

简单总结一下,有教用插件的,iframe 的,这一类都不是我们想要的结果。

最靠谱的还是用原生的 $.ajax 方法,在于 ajax 方法的 data 参数,这次我们不要传进去一个普通的 javascript 字典,而是传进去一个 FormData 对象。

注意,ajax 参数里面还要显式指定 processData 为 false,这样 formData 的格式才会被承认。

还有,必须指定 contentType 为 false,具体见这个问题:http://stackoverflow.com/a/8244082/2544762

关于 FormData 对象的说明是这篇文档:https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects

举个例子就可以说明问题:

<input id="upload_avatar" type=file />

<script>
$('#upload_avatar').change(function() {
    var formData = new FormData();
    formData.append('avatar', this.files[0]);
    formData.append('username', 'admin');
    $.ajax({
        url: '/api/users/5/',
        method: 'put',
        data: formData,
        processData: false,
    }).done(function(data) {
        // actions after finish
    });
});
</script>

这样一来,基本的功能就已经完全可以解决了。

后记

另外,其实 FormData 的这个用法配合 jQuery 的 ajax 方法还可以做很多其他更有意义的事情。

例如,我们可能会对一些图片之类的做客户端压缩,在 canvas 导出 base64 编码的图片文件之后,我们希望通过 ajax 将这个文件上传上去,这个时候 formData 就可以大派用场。

通过 atob 将 base64 内容转成二进制,然后构造 Blob 对象:http://stackoverflow.com/q/16245767/2544762

然后通过 formData.append('filename', blob) 这样的方式构造一个文件上传字段。

通过这种方式,能够对传统的文件上传前后端配合方式做极大的改进,从体验上和效率上都得到很大的提高。


【转载请附】愿以此功德,回向 >>

原文链接:https://www.huangwenchao.com.cn/2015/09/django-rest-framework-upload.html【Django Rest Framework 文件上传处理】

《Django Rest Framework 文件上传处理》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注