AJAX

AJAX即“Asynchronous JavaScript and XML”(异步的JavaScript与XML技术),指的是一套综合了多项技术的浏览器端网页开发技术.能够在不更新整个页面的情况下和后端交换数据,然后来更新页面的一部分.避免了每次一有数据交互,就要重新载入整个页面的情况.还可以看看知乎上关于AJAX的解释.在学习AJAX技术之前,先复习一下AJAX传输数据的标准,即JSON数据.

JSON回顾

JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation),是轻量级的文本数据传输方式,在很多语言中都得到广泛的支持.
对于我们来说,前端页面在浏览器中运行的JS程序,后端是Python程序,在JS和Python通过JSON通信的时候,必须要清楚双方通过通信可以传输哪些类型的数据以及之间的对应关系,才能够作出正确的处理.
JSON

Python的操作方法,引入JSON模块的loads和dumps方法来操作:

a = [1, 'cony', None, True, False, 6.03,{"jenny":"cony"},]
b = json.dumps(a)
c = json.loads(b)

print(a, type(a), sep="\n")
print(b, type(b), sep="\n")

# [1, 'cony', None, True, False, 6.03, {'jenny': 'cony'}]
# [1, "cony", null, true, false, 6.03, {"jenny": "cony"}]

通过结果可以看到Python类型与JSON的对应关系.JSON字符串的引号必须是双引号.

JS的操作方法无需引入模块,直接用JSON.stringify和parse方法生成和解析JSON数据:

obj1 = [1, "cony", null, true, false, 6.03, {"jenny": "cony"}]
obj2 = JSON.stringify(obj1)
obj3 = JSON.parse(obj2)

JSON无法传递JS的函数和Python中的函数和对象, 但是对于前端和后端的数据交互足够了.

AJAX入门例子

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程).
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
在之前使用的发送请求的方式有:在浏览器的地址栏发送GET请求;通过视图函数重定向来让浏览器发送请求;HTML上的a标签发送GET请求;表单提交可以选择发送GET或POST请求.
AJAX实际上是一种发送请求和接受请求的方式,可以发送GET也可以发送POST请求.

jQuery发送AJAX的具体API在jQuery中文站里可以看到.

简单的示例,两个数的计算:

<div class="container">
<input type="number" id="i1">+
<input type="number" id="i2">=
<input type="number" id="i3">
<input type="button" value="AJAX提交" id="b1">
</div>

这里可以注意到,页面中并没有form表单.而且一旦刷新页面,实际上输入框中的数字全部都会消失.
按钮本身没有任何功能,因为类型只是普通按钮,而且没有用JS绑定事件.
我们要做的就是通过jQuery给这个按钮绑定事件,让其给后端发AJAX请求.
用原生JS写AJAX请求非常麻烦,所以直接学习用jQuery发送AJAX请求.

<script>
    $("#b1").on("click", function() {
        var val1 = $("#i1").val();
        var val2 = $("#i2").val();
        $.ajax({
            url:"/ajax_add/",
            type:"GET",
            data:{"i1":val1,"i2":val2},
            success: function (data) {
                $("#i3").val(data)
        }
        })
    })

首先给button绑定一个click事件,对应一个匿名函数,取得当时val1和val2的值,然后用jQuery的ajax方法传送一个字典.字典内的键是固定的,url是地址,表示方法与其他路径一样,type表示请求的种类,可以为GET或者POST或者其他.然后是一个data字典,用来传送实际需要发送的键和值,然后还有一个固定的success键,用于对应返回的内容.一般在success的值内也写一个函数,函数的第一个参数就是返回值,之后将该值赋给最后一个框的值显示出来,就完成了一次ajax请求.
在发送的时候要注意,例子中直接发送了值,是因为val取到的是字符串,如果data中的值不是字符串,则要先调用JSON.stringify来包装成JSON字符串,并且在后端用JSON模块进行处理,前端对拿到的数据也用JSON.parse来处理,保证通信的一致性.

ajax请求是异步的,也就是说一个页面可以有多个ajax请求同时发送到各个函数进行处理,各个ajax请求之间不会互相等待,而是各自等待各自的消息返回然后进行处理.

ajax的常见应用场景就是注册的时候即时验证登录框,一般有如下几种做法:每输入一个字符,input框失去焦点,提交form表单之前等几个阶段来用ajax做验证.
写了一个简单的模拟用户注册然后展示用户名是否可以使用的页面:

<div class="container">
    <form action="/ajax_add/" method="post">
        <h1 class="text-center">用户注册</h1>
        <label for="username">用户名</label>
        <input type="text" id="username" name = "un">
        <p id="error1"></p>
        <label for="password">密码</label>
        <input type="password" id="password" name="pwd">
        <p id="error2"></p>
        {% csrf_token %}
        <input type="submit" value="提交" id="b1">
    </form>
</div>

JS脚本:

<script>
    form_tag = false;
    $("#username").on("blur", function () {
        usern = $("#username");
        warn = $("#error1");
        var username = usern.val();
        $.ajax({
            url: "/ajax_add/",
            type: "GET",
            data: {"un": username},
            success: function (data) {
                console.log(data);
                if (data != "用户名可以使用") {
                    warn.text(data);
                    warn.css("color", "red");
                }
                else {
                    warn.text(data);
                    warn.css("color", "red");
                    form_tag = true;
                }
            }
        })
    });
    $("#b1").on("click", function () {
        if (form_tag === true) {
            return
        }
        else {
            return false
        }
    })
</script>
给提交按钮绑定事件,判断全局变量,如果全局变量为true就不阻挡按钮的默认事件,否则阻拦默认事件.

后端程序:

def ajax_add(request):
    if request.method == "GET":
        username = request.GET.get('un')
        print(username)
        if username:
            ret = models.users.objects.filter(name=username.strip().lower())
            print(ret.count())
            if ret.count():
                return HttpResponse("用户名已经被注册")
            else:
                return HttpResponse("用户名可以使用")
        else:
            return HttpResponse("用户名不能为空")
    else:
        username = request.POST.get('un', None)
        if username:
            models.users.objects.create(name=username.strip().lower()).save()
        return HttpResponse("增加新用户成功")

AJAX发送POST请求

CSRF_TOKEN的本质就是在HTTP请求里包含CSRF_TOKEN键值对,这个键值对既可以写在请求头里,也可以写在请求体里,所以就有了各种不同的方法.
写在请求体的方法最简单,就是页面在生成的时候会有csrf_token,用jQuery拿到值以后,在ajax请求的data里加上这一个键值对即可.
由于Django生成的csrf_token的name属性是固定的,所以可以编写如下的代码:

$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  data: {
    "username": "Q1mi",
    "password": 123456,
    "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
  },
  success: function (data) {
    console.log(data);
  }
})

使用请求头发送,则data部分比较干净,仅包含用户自己的数据:

# 需要使用jquery.cookie.js插件
$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrf_token,并设置ajax请求头
  data: {"username": "Q1mi", "password": 123456},
  success: function (data) {
    console.log(data);
  }
})

AJAX发送文件

通过AJAX发送文件,必须使用JS的Formdata对象,然后进行一些特殊设置.

$("#b3").click(function () {
  var formData = new FormData();
  formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
  formData.append("f1", $("#f1")[0].files[0]);
  $.ajax({
    url: "/upload/",
    type: "POST",
    processData: false,  // 告诉jQuery不要去处理发送的数据
    contentType: false,  // 告诉jQuery不要去设置Content-Type请求头
    data: formData,
    success:function (data) {
      console.log(data)
    }
  })
})

Django的序列化

目前使用Ajax发送了比较简单的数据,主要是字符串和字符串格式的数值.在之前的项目中,除了字符串之外,最经常给模板传送的就是QuerySet对象,如果需要向Ajax返回QuerySet对象,很显然必须将其序列化成JSON字符串才行,如果手工操作,需要取出全部字典放到列表中再转换,比较麻烦,Django中提供了直接将QuerySet序列化成可以发送的对象的模块:

def books_json(request):
    book_list = models.Book.objects.all()[0:10]
    from django.core import serializers
    ret = serializers.serialize("json", book_list)
    return HttpResponse(ret)

前端在接收到这个字符串的时候,用JSON.parse解析,之后便可以交给JS代码来处理.需要注意的是,serialize只能接受标准的QuerySet对象作为参数,不能接受value_list等处理过的QuerySet对象.

SweetAlert插件

之前的通信方式局限在Django内部,即模板和后端通过tag模板语言进行通信.但是在实际开发的过程里,JS才是前端页面的掌控者,因此无论使用AJAX与否,今后编写页面的时候,都要通过JS来控制行为,尽量减少单独使用模板语言来控制行为,也就是让JS来掌握前端的所有控制权.
那么就可以使用JS的诸多插件,比如jQuery就是成熟的插件,还有很多插件可以简化JS控制行为的方式,比如SweetAlert插件,这是一个基于Bootstrap 的项目.其实质很简单,就是包了一个确定和取消的按钮及弹出框,来控制是否让AJAX发送请求.