Django - 如何上传文件?

目标

在很多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">&times;</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类中的参数名保持一致。 这样才能避免因为名字不一样而导致的错误

下期预告

下一篇文章将讲述:如何解决怎么在上传文件的时候前端显示进度

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容