文件上传及FormData对象


文件和其他数据类型不同,是一个二进制的形式,所以上传有所区别,具体有以下几种方式。

一 Form上传文件

1.1 Form方式

file_upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>Form上传文件</h3>
    <form method="post" action="/file_upload/" enctype="multipart/form-data">
        {% csrf_token %}
        <p><input type="text" name="user" placeholder="用户名"></p>
        <p>请选择需要上传的文件:<input type="file" name="file"></p>
        <input type="submit" value="提交">
    </form>
</body>
</html>

views.py

def file_upload(request):
    '''用于Form上传文件'''
    if request.method == 'GET':
        return render(request,'file_upload.html')
    else:
        user = request.POST.get('user')   #非文件内容还是向之前那样获取
        # request.FILES 拿到的是一个句柄
        file_obj = request.FILES.get('file')
        print(file_obj)				#结果为:tree指令.png
        print(file_obj.name)		#结果为:tree指令.png
        print(file_obj.size)		#结果为:6813
        print(type(file_obj))		#结果为:<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
        print(type(file_obj.name))	#结果为:<class 'str'>
        #方式一
        # with open(file_obj.name, "wb") as f:
        #     for i in file_obj:
        #         f.write(i)
        #方式二:利用chunks(self, chunk_size=None)方法上传,可指定每次上传文件大小
        # with open(file_obj.name, "wb") as f:
        #     for chunk in file_obj.chunks():
        #         f.write(chunk)
        #方式三:指定上传位置
        import os
        with open(os.path.join('static',file_obj.name), "wb") as f:
            for chunk in file_obj.chunks():
                f.write(chunk)
        #return HttpResponse("上传成功...")
        return render(request,'file_upload.html')

注意点:Form上传文件时切记要加上:enctype="multipart/form-data"

1.2 Form表单方式

file_upload2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>Form表单上传文件</h3>
    <form method="post" action="/file_upload2/" enctype="multipart/form-data">
        {% csrf_token %}
        <p>用户名:{{ obj.user }}</p>
        <p>请选择需要上传的文件:{{ obj.file }}</p>
        <input type="submit" value="提交">
    </form>
</body>
</html>

views.py

def file_upload2(request):
    '''用于Form表单上传文件'''
    if request.method == 'GET':
        obj = fileUpload()
        return render(request,'file_upload2.html',{'obj':obj})
    else:
        obj = fileUpload(data=request.POST,files=request.FILES)
        if obj.is_valid():
            print(obj.cleaned_data)     #{'user': 'joe1991', 'file': <InMemoryUploadedFile: 编程规范.jpg (image/jpeg)>}
            file_obj = obj.cleaned_data.get('file')
            import os
            with open(os.path.join('static', file_obj.name), "wb") as f:
                for chunk in file_obj.chunks():
                    f.write(chunk)
        return render(request, 'file_upload2.html',{'obj':obj})
在执行过程中,一直报这个错误:__init__() got an unexpected keyword argument 'file',重启web服务端搞定???
Form表单提交会导致页面刷新,但是在有些情况下,我们不希望页面被刷新,这种时候我们都是使用Ajax的方式进行请求

二 Ajax上传文件

知识补充:

通常我们提交(使用submit/button)时,会把form中的所有元素的name与value组成一个queryString,提交到后台。这用jQuery的方法来说,就是serialize

通过form的jQuery对象.serialize()可以对form表单进行序列化,从而将form表单中的所有参数传递到服务端。

但是上述方式,只能传递一般的参数,上传文件的文件流是无法被序列化并传递的。不过如今主流浏览器都开始支持一个叫做FormData的对象,有了这个FormData,我们就可以轻松地使用Ajax方式进行文件上传了。

与之前学习的Ajax分类一致,Ajax上传文件也有三种方式:

  • 原生Ajax上传
  • jQuery Ajax上传
  • “伪”Ajax上传

其中通过原生与jQuery上传时,需要利用一个FormData对象,它既可以处理字典、列表(字符串),也可以处理二进制等

FormData是什么?我们来看看Mozilla上的介绍。
XMLHttpRequest Level 2添加了一个新的接口FormData,利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步地提交这个"表单"。比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件。

所有主流浏览器的较新版本都已经支持这个对象了,比如Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。

参见:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/FormData

代码如下:

ajax_upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>原生Ajax上传文件</h3>
    <input type="file" id="f1">
    <button onclick="upload1()">原生Ajax上传</button>
    <div id="container1"></div>

    <h3>jQuery Ajax上传文件</h3>
    <input type="file" id="f2">
    <button onclick="upload2()">jQuery Ajax上传</button>
    <div id="container2"></div>

    <h3>“伪”Ajax上传文件</h3>
    <form id="f3" method="POST" action="/ajax_upload/" target="ifr" enctype="multipart/form-data">
        <iframe id="ifr" name="ifr" style="display: none"></iframe>
        <input type="file" name="file" >
        <input type="text" name="user" placeholder="用户名">
        <button onclick="upload3()">“伪”Ajax上传</button>
    </form>
    <div id="container3"></div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        // 原生Ajax上传文件执行函数
        function upload1() {
            var formData = new FormData();      //创建FormData对象
            formData.append('k1','v1');
            formData.append('file',document.getElementById('f1').files[0]);   //获取文件对象

            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    var file_path = xhr.responseText;
                    console.log(file_path)
                    var tag = document.createElement('img');
                    tag.src = "/"+ file_path;          //处理返回的路径
                    document.getElementById('container1').appendChild(tag);
                }
            };
            xhr.open('POST','/ajax_upload/');
            xhr.send(formData);
        }

        // jQuery Ajax上传文件执行函数
        function upload2() {
            var formdata = new FormData
            formdata.append('k2','v2');
            formdata.append('file',$('#f2')[0].files[0])
            $.ajax({
                url:'/ajax_upload/',
                type:'POST',
                data:formdata,
                contentType:false,
                processData:false,
                success:function (arg) {
                    var tag = document.createElement('img');
                    tag.src = "/"+ arg;
                    $('#container2').append(tag);
                }
            })
        }

        // “伪”Ajax上传文件执行函数
        function upload3(){
            document.getElementById('ifr').onload = loadIframe;
            document.getElementById('f3').submit();
        }
        function loadIframe(){
            var content = document.getElementById('ifr').contentWindow.document.body.innerText;
            var tag = document.createElement('img');
            tag.src = "/"+ content;
            $('#container3').append(tag);
        }
    </script>
</body>
</html>

views.py

def ajax_upload(request):
    '''用于ajax文件上传'''
    if request.method == 'GET':
        return render(request,'ajax_upload.html')
    else:
        print(request.POST,request.FILES)   #<QueryDict: {'k1': ['v1']}> <MultiValueDict: {'file': [<InMemoryUploadedFile: 1.png (image/png)>]}> 数据未做处理
        file_obj = request.FILES.get('file')
        print(file_obj,file_obj.name)      #1.png  1.png
        import os
        with open(os.path.join('static', file_obj.name), "wb") as f:
            for chunk in file_obj.chunks():
                f.write(chunk)
        file_path = os.path.join('static', file_obj.name)
        return HttpResponse(file_path)

注意点:

1.原生Ajax采用POST请求时,不再需要设置请求头:

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');

2.采用jQuery Ajax上传文件时,必须要加上:

contentType:false  #因为data值是FormData对象,不需要对数据做处理
processDate:false    #不做预处理

3.前端获取文件对象方式

image

jQuery方式

image

4.如何上传多个文件

从代码document.getElementById('f1').files[0]或$('#f2')[0].files[0]中可以看到一个<input type="file">标签能够上传多个文件,只需要在<input type="file">里添加multiplemultiple="multiple"属性。

5.jQuery对象与document之间的转换

jQuery—>document: $('#i2') -> $('#i2')[0]
document—>jQuery: document.getElementById('i1') -> $(document.getElementById('i1'))

6."伪"Ajax:兼容性更好(上传文件优先选择"伪"Ajax,上传数据优先选择jQuery和原生Ajax)

在执行过程中出现了以下错误:

1.获取不到文件

image

2.如果文件名称中包含中文,产生乱码

image

原因查找:

jquery导入失败:

image

配置静态文件路径名写错:

STATICFIEELS_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

当将静态文件路径名修改后,无异常。但是第二点还需要继续观察学习。

三 FormData对象详细

在第二部分我们已经简单使用了FormData对象,接下来做一下深入的学习。

3.1 FormData的获取与修改

创建一个FormData对象:

var formdata = new FormData();

W3c草案提供了三种方案来获取或修改FormData。

方案一:创建一个空的FormData对象,然后再用append方法逐个添加键值对

var formdata = new FormData();
formdata.append("k1","v1");
formdata.append"url", "http://www.baidu.com/");

方案二:取得form元素对象,将它作为参数传入FormData对象中

var formobj =  document.getElementById("form");
var formdata = new FormData(formobj);

方案三:利用form元素对象的getFormData方法生成它

var formobj =  document.getElementById("form");
var formdata = formobj.getFormData();

3.2 FormData 方法

参见:https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/FormData

四 补充--使用<form>表单初始化FormData对象方式上传文件(了解)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>表单初始化FormData对象上传文件</h3>
    <form id="uploadForm" enctype="multipart/form-data">
        <input id="file" type="file" name="file"/>
        <button onclick="upload4()">表单初始化FormData对象上传</button>
    </form>
    <div id="container4"></div>
	
	<script src="/static/jquery-3.2.1.js"></script>
    <script>
	function upload4() {
            $.ajax({
                url:'/ajax_upload/',
                type:'POST',
                data:new FormData($('#uploadForm')[0]),
                contentType:false,
                processData:false,
                cache: false,
                success:function (arg) {
                    alert('ok');
                }
            })
        }
    </script>
</body>
</html>

注意点:

1.<form>标签必须添加enctype="multipart/form-data"属性

2.contentType设置为false,不设置contentType值,因为是由<form>表单构造的FormData对象,且已经声明了属性enctype="multipart/form-data",所以这里设置为false

3.processData设置为false。因为data值是FormData对象,不需要对数据做处理

4.cache设置为false,上传文件不需要缓存

5.后端与之前一样,通过:request.FILES.get('file')获取到文件对象

 

部分参考:http://blog.csdn.net/inuyasha1121/article/details/51915742


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM