1. 程式人生 > >Python自動化開發學習24-Django中(AJAX)

Python自動化開發學習24-Django中(AJAX)

python django

講師的博客地址:http://www.cnblogs.com/wupeiqi/articles/5703697.html 。號稱是AJAX全套

原生Ajax

Ajax主要就是使用 XmlHttpRequest 對象來完成請求的操作,該對象在主流瀏覽器中均存在(除了早期的IE)。創建 XMLHttpRequest 對象的語法:

xmlhttp=new XMLHttpRequest();

老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 對象:

xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");

XmlHttpRequest對象的主要方法

創建對象之後,就可以通過對象來調用下面的這些方法了:

  • void open(String method,String url,Boolen async) :創建請求
    • method :請求的方式,如:POST、GET、DELETE 等等
    • url :請求的地址
    • async :是否異步。一般都是異步的,只是他也支持同步的使用方式
  • void send(String body) :發送請求
    • body :要發送的數據
  • void setRequestHeader(String header,String value) :設置請求頭
    • header :請求頭的key
    • value :請求頭的value
  • String getAllResponseHeaders() :獲取所有響應頭,返回值就是響應頭數據
  • String getResponseHeader(String header) :獲取響應頭中指定header的值
    • header :響應頭的key,返回值就是響應頭的value
  • void abort() :終止請求

使用原生的方法發請求

發送GET請求
使用上面的方法,先發送一個空的GET請求:

<!-- ajax.html 文件 -->
<body>
<input type="text" placeholder="隨便寫點值,看看頁面是否有刷新">
<input type="button" value="Ajax" />
<script>
    document.getElementsByTagName(‘input‘)[0].onclick = function () {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open(‘GET‘, ‘/ajax/‘);
        xmlhttp.send();
    };
</script>
</body>

為了能夠真正有服務響應這個請求,還得寫一個處理函數:

# views.py 文件

def ajax(request):
    return render(request, ‘ajax.html‘)

打開控制臺,在網絡裏點擊按鈕觸發事件後會看到我們發送的請求。點擊這個請求可以看到標頭。還有正文,正文裏的響應正文返回的就是整個頁面的html。並且整個過程裏頁面也是不會刷新的。

發送POST請求
上面發送的是GET請求,如果要發送POST請求,不只是要改一下method參數,還必須設置一下請求頭:

xmlhttp.setRequestHeader(‘Content-Type‘, ‘application/x-www-form-urlencoded; charset-UTF-8‘);

另外還會有csrf的問題,csrf_token可以放到表單裏,另外也可以設置到請求頭中。

xmlhttp.setRequestHeader(‘X-CsrfToken‘, "{{ csrf_token }}");

之前一直用{% csrf_token %},這是生成一個html,這裏使用的是{{ csrf_token }},直接就是token的字符串。
另外,客戶端的Cookie裏也會有一個csrf的值,看下來和{{ csrf_token }}的值是不同的,但是獲取過來再同樣放到請求頭裏也是可以通過csrf驗證的:

xmlhttp.setRequestHeader(‘X-CsrfToken‘, getCookie(‘csrftoken‘));
// 這裏需要一個getCookie方法,有很多的實現方式,比如下面用正則匹配的
function getCookie(name) {
    var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
    if(arr=document.cookie.match(reg)){
        return arr[2];
    } else {
        return null;
    }
}

XmlHttpRequest對象的主要屬性

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

補充一個知識點:關於狀態碼和狀態文本,使用HttpResponse返回的時候也是可以設置的:

return HttpResponse(json.dumps(ret), status=404, reason="Not Found")

並且一般這個狀態碼返回的往往都是200,因為即使後臺有錯誤,我們捕獲或者驗證處理了,之後還是會正常返回數據的。如果需要用到這種狀態碼,就像上面一樣返回的時候帶上status參數設置狀態碼。或者我們不要這種通用的狀態碼,而是在我們自己寫的ret的字典裏,也搞一套規則表示應用返回的狀態信息。兩種用法都有人用,而且貌似自己搞一套的更多。

兼容性的問題

解決兼容性的問題,只需要解決這一句代碼就好了 var xmlhttp = new XMLHttpRequest(); 如果有這個對象,那麽就使用這個對象,如果沒有這個對象,就用另外一個對象。下面的function就是提供了一個返回正確的對象的方法:

<script>
    function GetXmlhttp(){
        var xmlhttp = null;
        if(XMLHttpRequest){
            xmlhttp = new XMLHttpRequest();
        }else{
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        return xmlhttp;
    }
    // 使用的時候就不是創建對象了,而是通過上面的方法獲取到對象
    // var xmlhttp = GetXmlhttp();
</script>

下面3句的效果是一樣的,是講兼容性的時候順帶提到的。即下面的3種方式引用都是可以的。

> XMLHttpRequest
< function XMLHttpRequest() { [native code] }: 
> window.XMLHttpRequest
< function XMLHttpRequest() { [native code] }: 
> window[‘XMLHttpRequest‘]
< function XMLHttpRequest() { [native code] }: 

jQuery 的 Ajax

原生方法幫助我們了解原理,使用的話還是jQuery會方便的多。

回調函數的參數-獲取原生的XMLHttpRequest對象

之前使用回調函數success的時候,只用到了一個參數。這個回調函數最多是有3個參數的:

  1. 如果只傳遞一個參數,表示只請求服務器響應的文本信息,這樣可以根據需求在服務器設置一個json格式的文本信息,客戶端就可以直接獲取到服務端的數據。
  2. 如果傳遞兩個參數,則在第一個參數的基礎上,增加了一個狀態參數。比如:會返回字符串"success",貌似也沒什麽用。
  3. 如果傳遞三個參數,則第三個參數就是完整的ajax相應的狀態信息。就是XMLHttpRequest對象,拿來就能按原生的方法來操作。
    下面上傳文件的小節裏會有例子

偽Ajax請求

由於HTML標簽的iframe標簽具有局部加載內容的特性,所以可以使用其來偽造Ajax請求。

iframe標簽

在標簽內部加上一個src的屬性,就會在這個元素的內部創建包含另外一個文檔的內聯框架(就是嵌套一個網頁):

<body>
<iframe src="http://blog.51cto.com/steed"></iframe>
<h2>註意帶上前面的http://</h2>
<h2>舉例:http://blog.51cto.com/steed</h2>
<label for="url">URL:</label><input type="text" id="url">
<input type="button" value="發送iframe請求" onclick="iframeRequest();">
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
    function iframeRequest() {
        var url = $(‘#url‘).val();
        $(‘iframe‘).attr(‘src‘, url);
    }
</script>
</body>

上面還附帶了一個方法,跟上input框的url,刷新iframe標簽裏嵌套的頁面。但是頁面整體是不刷新的,只有嵌套框架的內部會變化。上面的例子要說明的問題是:iframe標簽也可以實現不刷新頁面發送請求並且拿到返回的數據(偷偷的發請求)。
接下來,在上面的基礎上,現在在form裏寫一個iframe標簽,並且通過target屬性和iframe的name相關聯。原本頁面上的form請求,現在都在iframe裏實現了不刷新頁面的提交和數據返回:

<form action="/ajax/" method="POST" target="ifm">
    {% csrf_token %}
    <iframe name="ifm"></iframe>
    <input type="text" name="username" />
    <input type="submit" />
</form>

下面是對應的處理函數:

# views.py 文件

import time
def ajax(request):
    if request.method == ‘GET‘:
        return render(request, ‘ajax.html‘)
    elif request.method == ‘POST‘:
        ret = {‘code‘: True, ‘data‘: request.POST.get(‘username‘)}
        time.sleep(3)
        return HttpResponse(json.dumps(ret))

獲取返回值

上面雖然返回了值,但是是在頁面上顯示的,如何拿到這些數據。iframe內部是一個完整的Document對象,這裏需要使用一個特殊的方法才能取到裏面的值。另外,iframe內部是在提交並且返回數據只會才會有我們需要的值的,提交之後立刻獲取也是獲取不到的,需要等到數據返回後才能獲取到。上面的處理函數裏加了一個sleep,效果更佳明顯。
獲取數據的時機
iframe的數據加載完成後,會觸發一個onload事件,給onload事件綁定一個函數,此時再去獲取,就能獲取到標簽內部最新的值。不過這樣還不完美,第一次加載頁面的時候也會觸發onload事件,要解決這個問題,需要為submit綁定事件,通過submit事件來給iframe標簽綁定onload事件:

<script>
    document.getElementsByTagName(‘form‘)[0].onsubmit = function () {
        document.getElementsByTagName(‘iframe‘)[0].onload = function () {
            alert(123)
        };
    }
</script>

獲取數據的方法
既然iframe內部是一個DOM對象,使用 .contentWindow.document 就是內層的DOM對象。之後就是之前學習的知識了:

<script>
    document.getElementsByTagName(‘form‘)[0].onsubmit = function () {
        document.getElementsByTagName(‘iframe‘)[0].onload = function () {
            var text = this.contentWindow.document.getElementsByTagName(‘body‘)[0].innerText;
            alert(text);
        };
    }
</script>

如果是用jQuery的話,就使用jQuery的方法:

<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
    $(‘form‘).submit(function () {
        $(‘iframe‘).load(function () {
            var text = $(this).contents().find(‘body‘).text();
            alert(text);
        })
    })
</script>

上傳文件

之前通過Ajax發送的都是普通數據。發送普通數據的時候,推薦還是用jQuery,退而求其次是用原生的,用偽Ajax並不方便。
下面看看不普通的數據,就是上傳文件。

好看的上傳按鈕

下面的input是系統自帶的上傳按鈕:

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .upload{display: inline-block; padding: 10px; background-color: blue; color: white;}
    </style>
</head>
<body>
<input type="file" id="file" name="file" />
<a class="upload">上傳</a>
</body>

上傳按鈕在不用的瀏覽器裏看到的樣子也是不同的,並且樣式並不能完全的按自己的需要來定制。如果要做一個好看的上傳按鈕,需要特殊處理一下。

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div.file{position: relative; width: 100px; height: 50px; line-height: 50px;}
        div.file>#file{position: absolute; width:100%; height:100%; z-index: 20; opacity: 0;}
        div.file>.upload{position: absolute; width:100%; height:100%; z-index: 10;
            display: inline-block; background-color: blue; color: white; text-align: center;}
    </style>
</head>
<body>
<div class="file">
    <input type="file" id="file" name="file" />
    <a class="upload">上傳</a>
</div>
</body>

上面的思路就是,把默認的input和我們的標簽重疊。把默認的input放在上面,但是透明度設置為0就是看不見。把我們自定制的標簽放在下面,但是上層由於全透明看不見,看到的效果就是我們自定制標簽的效果。但是點擊的效果還是點擊了默認的input按鈕,因為它才是真正在上層的元素,只是看不見而已。
要自定制好看的上傳按鈕,基本都是基於這個方式來實現的。

FormData 對象

這裏需要先引入一個FormData對象。FormData對象用以將數據編譯成鍵值對,以便用XMLHttpRequest來發送數據。這個對象有一個append()方法,為當前的FormData對象添加鍵值對:

void append(String name, String value);
void append(String name, Blob value, optional String filename);

第一種用法就是傳入2個字符串,前面是Key,後面是字符串。
第二種用法是,第二個參數傳入一個Blob對象,即一個不可變、原始數據的類文件對象。簡單理解成文件對象就好了,在上面的例子中,通過 var file_obj = document.getElementById(‘file‘).files[0]; 就能獲取到這個文件對象。
最後還有一個可選的(optional)第三個參數,指定文件的文件名。

原生Ajax上傳

頁面的代碼如下,主要看js的部分:

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div.file{position: relative; width: 100px; height: 50px; line-height: 50px;}
        div.file>#file{position: absolute; width:100%; height:100%; z-index: 20; opacity: 0;}
        div.file>.upload{position: absolute; width:100%; height:100%; z-index: 10;
            display: inline-block; background-color: blue; color: white; text-align: center;}
    </style>
</head>
<body>
<div class="file">
    <input type="file" id="file" name="file" />
    <a class="upload">上傳</a>
</div>
<input type="button" value="提交" id="btn" />
<script>
    document.getElementById(‘btn‘).onclick = function () {
        var file_obj = document.getElementById(‘file‘).files[0];
        var form_data = new FormData();
        form_data.append(‘username‘, ‘root‘);
        form_data.append(‘file‘, file_obj);
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open(‘POST‘, ‘/upload/‘);
        xmlhttp.setRequestHeader(‘X-CsrfToken‘, "{{ csrf_token }}");
        // form_data.append(‘csrfmiddlewaretoken‘, "{{ csrf_token }}");  // 也可以加在form裏
        xmlhttp.onreadystatechange = function () {
            if (xmlhttp.readyState === 4){
                var obj = JSON.parse(xmlhttp.responseText);
                console.log(obj);
            }
        };
        xmlhttp.send(form_data)
    }
</script>
</body>

對應後端的處理函數:

# views.py 文件

def upload(request):
    if request.method == ‘GET‘:
        return render(request, ‘upload.html‘)
    elif request.method == ‘POST‘:
        username = request.POST.get(‘username‘)
        file_obj = request.FILES.get(‘file‘)
        print(type(file_obj), file_obj)  # 從這裏看到已經收到文件了
        print(file_obj.__dict__)  # 看看有哪些屬性,比如:文件名、大小、文件類型
        ret = {‘code‘: True, ‘data‘: username}
        # 保存文件
        with open(file_obj.name, ‘wb‘) as file:
            for item in file_obj.chunks():
                file.write(item)
        return HttpResponse(json.dumps(ret))

jQuery 的 Ajax 上傳

只有js的部分和上面不同:

<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
    $(‘#btn‘).click(function () {
        var file_obj = $(‘:file‘)[0].files[0];
        var form_data = new FormData();
        form_data.append(‘username‘, ‘root‘);
        form_data.append(‘file‘, file_obj);
        $.ajax({
            url: ‘/upload/‘,
            type: ‘POST‘,
            data: form_data,
            headers: {‘X-CsrfToken‘: "{{ csrf_token }}"},
            processData: false,  // 上傳文件必須要有這句,告訴jQuery不要對data進行加工
            contentType: false,  // 上傳文件必須要有這句,告訴jQuery不要設置contentType
            success: function (data, textStatus, jqXHR) {
                console.log(data);
                console.log(textStatus);
                console.log(jqXHR);
            }
        })
    })
</script>

偽Ajax上傳文件

上面使用Ajax上傳文件,都必須依賴 FormDate 對象。但是這個對象不是所有瀏覽器都支持的,沒錯,還是老版的IE。要考慮兼容性問題,就需要用到這裏的偽Ajax。
使用偽造Ajax上傳文件和上傳一般的內容幾乎沒什麽差別,加一個 type="file" 的input框。
註意:form標簽要上傳文件,需要加上 enctype="multipart/form-data" 這個屬性

<form action="/upload/" method="POST" target="ifm" enctype="multipart/form-data">
    {% csrf_token %}
    <iframe name="ifm"></iframe>
    <input type="text" name="username" />
    <input type="file" name="file">
    <input type="submit" />
</form>
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
    $(‘form‘).submit(function () {
        $(‘iframe‘).load(function () {
            var text = $(this).contents().find(‘body‘).text();
            var obj = JSON.parse(text);
            console.log(obj);
        })
    })
</script>

上面還多了一個iframe的大框,設置 style="display: none;" 即可。剩下的上傳按鈕的美化方法應該是一樣的。
這個方法的兼容性是最高的,所以偽Ajax在上傳文件的應用場景中是推薦使用的方法。

生成預覽

如果上傳的文件是張圖片,可以生成預覽。把上傳的圖片存放到一個存放靜態文件的目錄裏,setting.py裏也設置好靜態文件的目錄。比如:

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, ‘static‘),
)

這裏預覽的圖片是從服務器上獲取的。也就是先把圖片上傳的服務器,然後服務器端返回文件在服務器上的路徑給預覽,預覽再通過路徑拿到服務器端的文件,最後顯示在頁面上。
處理函數修改一下,默認是保存到根目錄的,現在保存到專門的目錄裏:

# views.py 文件

import os

def upload(request):
    if request.method == ‘GET‘:
        return render(request, ‘upload.html‘)
    elif request.method == ‘POST‘:
        username = request.POST.get(‘username‘)
        file_obj = request.FILES.get(‘file‘)
        # print(type(file_obj), file_obj)  # 從這裏看到已經收到文件了
        # print(file_obj.__dict__)  # 看看有哪些屬性,比如:文件名、大小、文件類型
        img_path = os.path.join(‘static/imgs/‘, file_obj.name)  # 生成文件保存的路徑
        ret = {‘code‘: True, ‘data‘: username, ‘img‘: img_path}  # 返回的信息要有文件的路徑
        # 保存文件
        with open(img_path, ‘wb‘) as file:
            for item in file_obj.chunks():
                file.write(item)
        return HttpResponse(json.dumps(ret))

主要的變化就是生成了文件保存的路徑,另外將路徑返回給客戶端。
下面是html的完整代碼:

<div id="preview"></div>
<form action="/upload/" method="POST" target="ifm" enctype="multipart/form-data">
    {% csrf_token %}
    <iframe name="ifm" style="display: none;"></iframe>
    <input type="text" name="username" />
    <input type="file" name="file">
    <input type="submit" />
</form>
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
    $(‘form‘).submit(function () {
        $(‘iframe‘).load(function () {
            var text = $(this).contents().find(‘body‘).text();
            var obj = JSON.parse(text);
            // console.log(obj);
            var imgTag = document.createElement(‘img‘);
            imgTag.src = "/" + obj.img;  // 這裏註意前面需要添加個‘/‘
            $(‘#preview‘).empty();  // 可能有上次的預覽,要先清空
            $(‘#preview‘).append(imgTag);
        })
    })
</script>

上面預設了一個圖片預覽的div。Ajax請求返回後,獲取到其中的圖片路徑(這個路徑應該是直接拼接到“127.0.0.1:8000”後面就能訪問到圖片的)。創建一個img標簽,設置好src,然後加到預覽的div裏。加進去之前,要把div清空一下,因為有上一次預覽加進去的img標簽

一步提交

上面的上傳文件都是分兩步上傳的,首先是input[type=‘file‘]標簽選擇文件,然後是submit或者button按鈕綁定事件來進行提交。
這裏也可以不要後面的submit或者button按鈕,為input[type=‘file‘]綁定一個onchange事件觸發一個form表單的submit事件,或者是替換到原來button按鈕的onclick事件。前一種的實現只要加上下面的這個事件綁定:

<script>
    $(‘:file‘).change(function () {
        $(‘form‘).submit();
    });
<script>

Python自動化開發學習24-Django中(AJAX)