文件和其他數據類型不同,是一個二進制的形式,所以上傳有所區別,具體有以下幾種方式。
一 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.前端獲取文件對象方式
jQuery方式
4.如何上傳多個文件
從代碼document.getElementById('f1').files[0]或$('#f2')[0].files[0]
中可以看到一個<input type="file">
標簽能夠上傳多個文件,只需要在<input type="file">
里添加multiple
或multiple="multiple"
屬性。
5.jQuery對象與document之間的轉換
jQuery—>document: $('#i2') -> $('#i2')[0]
document—>jQuery: document.getElementById('i1') -> $(document.getElementById('i1'))
6."偽"Ajax:兼容性更好(上傳文件優先選擇"偽"Ajax,上傳數據優先選擇jQuery和原生Ajax)
在執行過程中出現了以下錯誤:
1.獲取不到文件
2.如果文件名稱中包含中文,產生亂碼
原因查找:
jquery導入失敗:
配置靜態文件路徑名寫錯:
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