目标
在很多Web应用中,都有上传文件的使用场景。本篇文件将讲述如何使用一个Form表单上传文件,该Form表单将包含一个select input和一个file input。
先看一下Django官网对上传文件对介绍Django官网传送门。Django官网对介绍对使用Django后台处理相关业务对介绍还是相当对详尽。但是对于初学者来说,官网资料缺乏对所匹配的前端实现的描述。 这样初学者在实现相关功能的时候,往往会遇到一些意想不到的问题。 比如我在实现上传文件功能的时候, 就遇到了form.is_valid()返回false的问题。
form is not invalid ?
前端HTML5文件的Form描述是
<form>
<div class="row justify-content-center align-items-center">
<label class="col-sm-8 col-form-label text-align:center">初次使用请先下载商品模板</label>
<div class="col-sm-4">
<button class="btn green" type="button" id="downloadSKUTemplate">下载
<i class="fa fa-download"></i>
</button>
</div>
</div>
<div class="justify-content-center align-items-center">
<label for="storesel" class="col-sm-8 col-form-label">导入门店: </label>
\ <div class = "col-sm-4">
<select class="bs-select green form-control pull-right" id="storesel" data-style="btn-info" name="store">
<option value="NULL" selected="selected">--请选择门店--</option>
<option value="CO">Colorado</option>
<option value="ID">Idaho</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NM">New Mexico</option>
<option value="ND">North Dakota</option>
<option value="UT">Utah</option>
<option value="WY">Wyoming</option>
</select>
</div>
</div>
<div class="form-group">
<div class="fileinput fileinput-new" data-provides="fileinput">
<span class="btn btn-default btn-file">
<span class="fileinput-new">Select file</span>
<span class="fileinput-exists">Change</span>
<input id="skuimport" type="file" name="skufile"></span>
<span class="fileinput-filename"></span>
<a href="#" class="close fileinput-exists" data-dismiss="fileinput" style="float: none">×</a>
</div>
</div>
</form>
前端JS文件的实现是
function sendXHRequest(formData, uri) {
console.info("+++ sendXHRequest +++");
// Get an XMLHttpRequest instance
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('loadstart', onloadstartHandler, false);
xhr.upload.addEventListener('load', onloadHandler, false);
xhr.addEventListener('readystatechange', onreadystatechangeHandler, false);
// Set up request
xhr.open('POST', uri, true);
for(var value of formData.values()){
console.log(value);
}
xhr.setRequestHeader("X-CSRFToken",csrftoken);
//xhr.setRequestHeader("Content-Type", "multipart/form-data");
// xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=AaB03x");
// Fire!
xhr.send(formData);
console.info(" --- sendXHRequest ---");
}
function initFullFormAjaxUpload() {
var form = document.getElementById('testform');
// FormData receives the whole form
console.info("+++ initFullFormAjaxUpload +++");
var formData = new FormData();
//FormData only has the file
var fileInput = document.getElementById('skuimport');
var file = fileInput.files[0];
formData.append('file', file);
formData.append('store',$('#import #storesel').val());
//Code common to both variants
sendXHRequest(formData, 'skuimport/');
console.info("--- initFullFormAjaxUpload ---");
}
$('#import #btn-confirm').click(function(event)
{
event.preventDefault();
alert("import form button confirm clicked");
initFullFormAjaxUpload();
alert("import form button confirm done");
$('#import').modal('hide');
});
Django后台view的实现是
class skuimport(generic.View):
def get(self, request):
pass
def post(self,request):
print "skuimport POST +++"
if request.method == "POST":
print "sku skuimport"
for key in request.POST:
print key
value = request.POST.getlist(key)
print value
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
print "form is valid"
handle_uploaded_file(request.FILES['skufile'],"skufile.jpg")
else:
print "form is not valid"
return render(request,'p_test.html',{'form': form} )
Django后台的Form的定义是
class UploadFileForm(forms.Form):
skufile = forms.FileField()
store = forms.CharField()
按照我的理解,上述实现基本上是按照官网描述的方法实现的, 但是结果却出人意料。后台的输出如下:
skuimport POST +++
sku skuimport
store
[u'NULL']
form is not valid
怎么调试?
后台没有任何错误输出, 就只能看到is_valid()方法返回false。后来找到了这一篇博文,找到了比较合适的调试方法。
我们先新建一个html文件,命名为p_test.html。然后在form.is_valid()返回失败的时候,使用form渲染p_test.html文件返回
return render(request,'p_test.html',{'form': form} )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} :test: {{ field }}
</div>
{% endfor %}
</body>
</html>
在js里面定义回调函数
// Handle the response from the server
function onreadystatechangeHandler(evt) {
var status, text, readyState;
try {
readyState = evt.target.readyState;
text = evt.target.responseText;
status = evt.target.status;
}
catch(e) {
return;
}
if (readyState == 4 && status == '200' && evt.target.responseText) {
alert(evt.target.responseText);
$('#import').modal('hide');
}
}
这样我们在上传文件之后,浏览器会弹出如下图所示信息:
解决办法
情况1:
在发送XHRRequest请求的时候, 该函数formData.append('file', file);中的第一个参数‘file’必须和Django后端Form类里面定义的变量名保持一致。 在本例子中即是:
class UploadFileForm(forms.Form):
skufile = forms.FileField()
store = forms.CharField()
如果是使用XHRRequest发送FormData,那么在使用append方法的时候, 就必须使用“skufile”和“store”作为第一个参数名:
formData.append('file', file);
formData.append('store',$('#import #storesel').val());
情况2:
如果在Form中使用action/submit直接上传,那么每一个input控件的name必须和Django后台Form类中的参数名保持一致。 这样才能避免因为名字不一样而导致的错误
下期预告
下一篇文章将讲述:如何解决怎么在上传文件的时候前端显示进度