Django中的文件上傳和原生Ajax


概述

Django中的上傳有3種方案:

  1. form 表單常規上傳,但點擊提交后會自動刷新頁面
  2. Ajax 上傳,不刷新頁面,(分為原生ajax上傳和jQuery上傳),IE7以上不兼容
  3. iframe 上傳,兼容性沒問題,也不會刷新頁面

常規表單上傳

這種方式有個缺點,就是會自動刷新頁面

后台接收文件時,通過request.files.get('xxx')得到文件對象,並通過對文件循環,循環對象為文件對象.chunks()寫入新文件即可!后台接收文件都使用這種方式!

可以看下源碼:

class InMemoryUploadedFile(UploadedFile):
    """
    A file uploaded into memory (i.e. stream-to-memory).
    """
    def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
        super(InMemoryUploadedFile, self).__init__(file, name, content_type, size, charset, content_type_extra)
        self.field_name = field_name

    def open(self, mode=None):
        self.file.seek(0)
	
	#這里可以看到本身是一個迭代器!
    def chunks(self, chunk_size=None):
        self.file.seek(0)
        yield self.read()

    def multiple_chunks(self, chunk_size=None):
        # Since it's in memory, we'll never have multiple chunks.
        return False

他的父類:

lass UploadedFile(File):
    """
    A abstract uploaded file (``TemporaryUploadedFile`` and
    ``InMemoryUploadedFile`` are the built-in concrete subclasses).

    An ``UploadedFile`` object behaves somewhat like a file object and
    represents some file data that the user submitted with a form.
    """
    DEFAULT_CHUNK_SIZE = 64 * 2 ** 10

    def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
        super(UploadedFile, self).__init__(file, name)
        self.size = size
        self.content_type = content_type
        self.charset = charset
        self.content_type_extra = content_type_extra

    def __repr__(self):
        return force_str("<%s: %s (%s)>" % (
            self.__class__.__name__, self.name, self.content_type))

    def _get_name(self):
        return self._name

    def _set_name(self, name):
        # Sanitize the file name so that it can't be dangerous.
        if name is not None:
            # Just use the basename of the file -- anything else is dangerous.
            name = os.path.basename(name)

            # File names longer than 255 characters can cause problems on older OSes.
            if len(name) > 255:
                name, ext = os.path.splitext(name)
                ext = ext[:255]
                name = name[:255 - len(ext)] + ext

        self._name = name

    name = property(_get_name, _set_name)

然后我們來看下代碼吧:

HTML文件內容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
    <form action="/upload/" enctype="multipart/form-data" method="POST">
        <input type="text" name="user"/>
        <input type="file" name="img"/>
        <input type="submit" value="提交"/>

    </form>

</body>
</html>

views.py 后台處理:

def upload(request):
    if request.method == 'POST':
        user = request.POST.get('user')
        img = request.FILES.get('img')
        print(user, img.name)
        f = open(os.path.join('static', img.name), 'wb')
        for chunk in img.chunks():
            f.write(chunk)
        f.close()

    return render(request, 'upload.html')

原生Ajax

AJAX,Asynchronous JavaScript and XML (異步的JavaScript和XML),一種創建交互式網頁應用的網頁開發技術方案。

異步的JavaScript:
使用 【JavaScript語言】 以及 相關【瀏覽器提供類庫】 的功能向服務端發送請求,當服務端處理完請求之后,【自動執行某個JavaScript的回調函數】。
PS:以上請求和響應的整個過程是【偷偷】進行的,頁面上無任何感知。
XML
XML是一種標記語言,是Ajax在和后台交互時傳輸數據的格式之一
利用AJAX可以做:
1、注冊時,輸入用戶名自動檢測用戶是否已經存在。
2、登陸時,提示用戶名密碼錯誤
3、刪除數據行時,將行ID發送到后台,后台在數據庫中刪除,數據庫刪除成功后,在頁面DOM中將數據行也刪除。

Ajax主要就是使用 【XmlHttpRequest】對象來完成請求的操作,該對象在主流瀏覽器中均存在(除早起的IE),Ajax首次出現IE5.5中存在(ActiveX控件)。

XmlHttpRequest對象介紹

XmlHttpRequest對象的主要方法:

a. void open(String method,String url,Boolen async)
   用於創建請求
    
   參數:
       method: 請求方式(字符串類型),如:POST、GET、DELETE...
       url:    要請求的地址(字符串類型)
       async:  是否異步(布爾類型)
 
b. void send(String body)
    用於發送請求
 
    參數:
        body: 要發送的數據(字符串類型)
 
c. void setRequestHeader(String header,String value)
    用於設置請求頭
 
    參數:
        header: 請求頭的key(字符串類型)
        vlaue:  請求頭的value(字符串類型)
 
d. String getAllResponseHeaders()
    獲取所有響應頭
 
    返回值:
        響應頭數據(字符串類型)
 
e. String getResponseHeader(String header)
    獲取響應頭中指定header的值
 
    參數:
        header: 響應頭的key(字符串類型)
 
    返回值:
        響應頭中指定的header對應的值
 
f. void abort()
 
    終止請求

XmlHttpRequest對象的主要屬性:

a. Number readyState
   狀態值(整數)
 
   詳細:
      0-未初始化,尚未調用open()方法;
      1-啟動,調用了open()方法,未調用send()方法;
      2-發送,已經調用了send()方法,未接收到響應;
      3-接收,已經接收到部分響應數據;
      4-完成,已經接收到全部響應數據;
 
b. Function onreadystatechange
   當readyState的值改變時自動觸發執行其對應的函數(回調函數)
 
c. String responseText
   服務器返回的數據(字符串類型)
 
d. XmlDocument responseXML
   服務器返回的數據(Xml對象)
 
e. Number states
   狀態碼(整數),如:200、404...
 
f. String statesText
   狀態文本(字符串),如:OK、NotFound...

原生Ajax例子

我們來看個Django中Ajax的例子吧:

views.py文件:

#發送頁面
def ajax(request):
    import time
    time = time.time()
    return render(request,'ajax.html',{'time':time})
    
#接受數據,並返回response數據
def xhr_ajax(request):
    print(request.GET)
    print(request.POST)
    return HttpResponse('OK')

HTML文件 ajax.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax-原生</title>
</head>
<body>
{{ time }}
<input type="button" value="XMLHttpRequest按鈕" onclick="XhrAjax();"/>
<input type="button" value="XMLHttpRequest,FormData按鈕" onclick="XhrAjaxForm();"/>

<script>
    function XhrAjax() {
        var xhr = new XMLHttpRequest();
        // 如果xhr的值改變時就處罰函數
        xhr.onreadystatechange = function () {
        		// 如果數據接受完畢,打印responseText
            if (xhr.readyState == 4) {
                console.log(xhr.responseText);
            }

        };
        xhr.open('POST', '/xhr_ajax/');
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
        xhr.send('k1=v1;k2=v2');
    }
    
    
    // 使用FormData對象傳送
    
    function XhrAjaxForm() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                console.log(xhr.responseText);
            }

        };
        // GET請求
        //xhr.open('GET', '/xhr_ajax?p=123');
        //xhr.send();
        // POST請求
        xhr.open('POST', '/xhr_ajax/');
        // 設置請求頭
        // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
        var form = new FormData();
        form.append('user', 'alex');
        form.append('pwd', '123');
        xhr.send(form);

    }

</script>

</body>
</html>

例子中有兩種方式:

  1. 原生XMLHttpRequest傳送數據,send()發送的是字符串,而且必須使用setRequestHeader設置信息請求頭
  2. FormData對象介質傳送數據,通過append將(可以看為元組信息)信息傳入,然后send(),但send對象為form對象,此種方式不需要設置信息請求頭
  3. 特別需要注意的一點:如果使用GET方式,需要設置URL,字符串方式

Ajax上傳文件

為了上傳文件后不刷新頁面,我們引入ajax方式來上傳,請求時都是通過FormData對象封裝數據,這種方式又分為兩種:

  1. 原生Ajax方式上傳
  2. jQuery + Ajax方式上傳

views.py 配置:

def upload_ajax(request):
    if request.method == 'POST':
        user = request.POST.get('user')
        img = request.FILES.get('img')
        print(user, img.name)
        f = open(os.path.join('static', img.name), 'wb')
        for chunk in img.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse('ok')

    return render(request, 'upload_ajax.html')

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
	// 注意form上傳文件時,需要配置enctype 
<form action="/upload_ajax/" enctype="multipart/form-data" method="POST">
    <input type="text" name="user" id="user"/>
    <input type="file" name="img" id="img"/>

</form>
    <a style="cursor: pointer;display: inline-block;background-color: black;color: white" onclick="UploadFile1();">XMLHttpRequest上傳</a>
    <a style="cursor: pointer;display: inline-block;background-color: black;color: white" onclick="UploadFile2();">jQuery上傳</a>
    <script src="/static/jquery-1.12.4.js"></script>

<script>

    // 原生上傳
    function UploadFile1() {
        var form = new FormData();
        form.append('user', document.getElementById('user').value);
        
        // 得到上傳圖片中的第一張,使用files[下標]
        var fileObj = document.getElementById('img').files[0];
        form.append('img', fileObj);

        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4){
                var data = xhr.responseText;
                console.log(data);
            }

        };
        xhr.open('POST','/upload_ajax/',true);
        xhr.send(form);

    }

    // ajax + jQuery上傳
    function UploadFile2() {
        var fileobj = $("#img")[0].files[0];
        var form = new FormData();
        form.append('img',fileobj);
        form.append('user','alex');
        $.ajax({
            type:'POST',
            url:'/upload_ajax/',
            data:form,
            processData:false,  // 告訴jquery不轉換數據
            contentType:false,  // 告訴jquery不設置內容格式
            success:function (arg) {
                console.log(arg);
                
            }
        })

    }

</script>

</body>
</html>

從上面的代碼來看,第二張方式要簡單很多,不需要send、open,之需要按常規ajax寫,然后聲明不轉換數據,不設置內容格式即可.

需要注意的是,上面的這兩種方式,兼容性不是很好,對IE6之前的瀏覽器不支持,所以我們使用iframe來做.

iframe 上傳

需求分析

  1. 異步上傳
  2. 上傳完后頁面顯示上傳后的圖片

因為iframe有內嵌頁面的功能,所以可以使用iframe將上傳后的圖片顯示,分以下步驟:

  1. 提交圖片前將圖片提交給iframe
  2. iframe代替本身頁面發送文件給后台
  3. 后台返回數據提交給iframe
  4. 頁面通過回調函數,取到后台返回的數據
  5. 將數據拆分,取到后台的上傳后的文件目錄
  6. 頁面創建一個img的標簽,添加到本頁面顯示圖片

總結一下:頁面向后台提交數據時,中間由iframe提交文件並接收后台返回的數據

views.py 后台:

def upload_iframe(request):
    if request.method == 'POST':
        ret = {'data':None,'status':False,'error':None}
        try:
            user = request.POST.get('user')
            img = request.FILES.get('img')
            file_path = os.path.join('static', img.name)
            f = open(file_path, 'wb')
            for chunk in img.chunks():
                f.write(chunk)
            f.close()
            ret['status'] = True
            ret['data'] = file_path
        except Exception as e:
            ret['error'] = str(e)

        return HttpResponse(json.dumps(ret))

    return render(request, 'upload_iframe.html')

HTML頁面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload_iframe</title>
    <style>
        /* 設置顯示上傳后圖片的樣式*/
        .img{
            width: 250px;
            height: 250px;
        }
    </style>
</head>
<body>
<iframe id="my_iframe" name="my_iframe" style="display: none" src=""></iframe>
<form id="fo" method="POST" action="upload_iframe.html" enctype="multipart/form-data">
{#    <input type="text" id="user" name="user"/>#}
    // id 和 name 的屬性必須都有
    <input type="file" id="img" name="img" onchange="UploadFile3();"/>
{#    <input type="submit"/>#}

</form>
<div id="container"></div>
<script src="/static/jquery-1.12.4.js"></script>
<script>
    function UploadFile3() {
        // 方式重復顯示圖片,刪除之前顯示的圖片
        $('#container').find('img').remove();
        // onload事件會在頁面或者圖像加載完成后立即觸發
        document.getElementById('my_iframe').onload = callback;
        // target 屬性指定在何處打開表單中的 action-URL
        document.getElementById('fo').target = 'my_iframe'
        // 提交
        document.getElementById('fo').submit();
    }

    function callback() {
        // 使用contents(),可以將iframe中內嵌的頁面HTML內容取出,取出的數據為字符串格式
        var text = $('#my_iframe').contents().find('body').text();
        // 字符串通過json轉化為字典
        var json_data = JSON.parse(text);
        console.log(json_data);
        if (json_data.status) {
            // 創建一個img的元素
            var tag = document.createElement('img');
            // 字符串拼接
            tag.src = '/' + json_data.data;
            tag.className = 'img';
            // 添加到目標元素標簽內
            $('#container').append(tag);
        } else {
            alert(json_data.error)
        }

    }
</script>


</body>
</html>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM