您的位置:首页 > 编程语言 > Python开发

Python自动化开发学习24-Django中(AJAX)

2018-05-18 21:01 756 查看

讲师的博客地址: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>

    补充

    再来补充点相关的小点

    处理函数识别AJAX请求

    如下面的例子,使用

    is_ajax()
    方法就可以识别出请求是否是AJAX请求了:

    def ajax_test(request):
    if request.is_ajax():
    message = "This is ajax"
    else:
    message = "Not ajax"
    return HttpResponse(message)

    上传文件的插件推荐

    这是一个可以拖拽上传文件的插件, dropzone.js 。
    官网:http://www.dropzonejs.com/

    下载文件

    转自国外:http://djangosnippets.org/snippets/365/

    import os, tempfile, zipfile
    from django.http import HttpResponse
    from django.core.servers.basehttp import FileWrapper
    
    def send_file(request):
    """
    Send a file through Django without loading the whole file into
    memory at once. The FileWrapper will turn the file object into an
    iterator for chunks of 8KB.
    """
    filename = __file__ # Select your file here.
    wrapper = FileWrapper(open(filename, 'rb'))
    response = HttpResponse(wrapper, content_type='text/plain')
    response['Content-Length'] = os.path.getsize(filename)
    return response
    
    def send_zipfile(request):
    """
    Create a ZIP file on disk and transmit it in chunks of 8KB,
    without loading the whole file into memory. A similar approach can
    be used for large dynamic PDF files.
    """
    temp = tempfile.TemporaryFile()
    archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)
    for index in range(10):
    filename = __file__ # Select your files here.
    archive.write(filename, 'file%d.txt' % index)
    archive.close()
    wrapper = FileWrapper(temp)
    response = HttpResponse(wrapper, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=test.zip'
    response['Content-Length'] = temp.tell()
    temp.seek(0)
    return response

    之后讲到项目,用的下载文件的方法,也是抄的这段代码。

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签:  python django