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


