文件和其他數據類型不同,是一個二進制的形式,所以上傳有所區別,具體有以下幾種方式。
一 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


