文件上傳及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