title: Django之Ajax #标题tags: #标签
date: 2022-01-04
categories: python # 分类

Ajax简介

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)。

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

AJAX除了异步的特点外,还有一个就是:浏览器页面局部刷新。

言而总之,Ajax的特性就是异步请求,局部刷新。

Ajax简单示例

下面来一个AJAX的简单示例:

页面输入两个整数,通过AJAX传输到后端计算出结果并返回。

  1. <!-- ajax.html文件内容 -->
  2. {% load static %}
  3. <!DOCTYPE html>
  4. <html lang="en">
  5. <head>
  6. <meta charset="UTF-8">
  7. <meta http-equiv="x-ua-compatible" content="IE=edge">
  8. <meta name="viewport" content="width=device-width, initial-scale=1">
  9. <title>AJAX局部刷新实例</title>
  10. </head>
  11. <body>
  12. <input type="text" id="i1">+
  13. <input type="text" id="i2">=
  14. <input type="text" id="i3">
  15. <input type="button" value="AJAX提交" id="b1">
  16. <script src="{% static 'jquery.js' %}"></script>
  17. <script>
  18. $("#b1").on("click", function () {
  19. $.ajax({
  20. url: "/ajax_add/", //别忘了加双引号
  21. type: "GET",
  22. data: {"i1": $("#i1").val(), "i2": $("#i2").val()}, //object类型,键值形式的,可以不给键加引号
  23. success: function (data) {
  24. $("#i3").val(data);
  25. }
  26. })
  27. })
  28. </script>
  29. </body>
  30. </html>

views.py文件内容:

from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.


def ajax_demo(request):
    return render(request,'ajax.html')



def ajax_add(request):
    i1=int(request.GET.get("i1"))
    i2=int(request.GET.get("i2"))
    ret=i1+i2
    return JsonResponse(ret,safe=False)

urls.py文件内容:

from django.contrib import admin
from django.urls import path
from ajax_demo import views

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('ajax_demo/',views.ajax_demo),
    path('ajax_add/',views.ajax_add),
]

settings.py的部分需要修改的内容:

STATIC_URL = '/static/'


STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static_file'),
]

另外,需要将jquery.js文件放到上面指定的目录下:

Django之Ajax - 图1

好,现在来启动项目看看:

Django之Ajax - 图2

Ajax常见应用场景

Ajax的常见应用场景有:搜索引擎根据用户输入的关键字,自动提示检索关键字。还有一个很重要的应用场景就是注册时候的用户名的查重。

其实下面的图片中就使用了AJAX技术!当文件框发生了输入变化时,使用AJAX技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后再把后端返回的结果展示出来。

它有如下特征:

  • 整个过程中页面没有刷新,只是刷新页面中的局部位置而已!
  • 当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应!

Django之Ajax - 图3

当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为lemontree7777777的用户是否存在,最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”。

Ajax的优点

  • AJAX使用JavaScript技术向服务器发送异步请求;
  • AJAX请求无须刷新整个页面;
  • 因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高;

Ajax的基本格式

button class="send_Ajax">send_Ajax</button>
<script>

       $(".send_Ajax").click(function(){

           $.ajax({
               url:"/handle_Ajax/",      // 请求路径
               type:"POST",            // 请求方法
               data: {                  // 发送请求时携带的数据
                uname: $('#uname').val(),
                pwd: $('#pwd').val(),/
                csrfmiddlewaretoken: "{{ csrf_token }}"
            },
               success:function(data){        // success负责处理请求路径返回的信息,data关键字用于接收请求返回的数据
                   console.log(data)
               },
           

           // 下面的不常用,用到了再说吧
               error: function (jqXHR, textStatus, err) {
                        console.log(arguments);
                    },

               complete: function (jqXHR, textStatus) {
                        console.log(textStatus);
                },

               statusCode: {
                    '403': function (jqXHR, textStatus, err) {
                          console.log(arguments);
                     },

                    '400': function (jqXHR, textStatus, err) {
                        console.log(arguments);
                    }
                }

           })

       })

</script>

ajax请求参数

/*  data: 当前ajax请求要携带的数据,是一个json的object对象,ajax方法就会默认地把它编码成某种格式
             (urlencoded:?a=1&b=2)发送给服务端;此外,ajax默认以get方式发送请求。*/

             function testData() {
               $.ajax("/test",{     //此时的data是一个json形式的对象
                  data:{
                    a:1,
                    b:2
                  }
               });                   //?a=1&b=2
/*processData:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;如果为false,
             那么对data:{a:1,b:2}会调用json对象的toString()方法,即{a:1,b:2}.toString()
             ,最后得到一个[object,Object]形式的结果。*/


/* contentType:默认值: "application/x-www-form-urlencoded"。发送信息至服务器时内容编码类型。
             用来指明当前请求的数据编码格式;urlencoded:?a=1&b=2;如果想以其他方式提交数据,
             比如contentType:"application/json",即向服务器发送一个json字符串:*/
               $.ajax("/ajax_get",{

                  data:JSON.stringify({
                       a:22,
                       b:33
                   }),
                   contentType:"application/json",
                   type:"POST",

               });                          //{a: 22, b: 33}

             /*注意:contentType:"application/json"一旦设定,data必须是json字符串,不能是json对象。
              对应的views.py文件中,需要使用:   json.loads(request.body.decode("utf8"))  来获取请求中携带的数据*/ 



/*traditional:一般是我们的data数据有数组时会用到 :data:{a:22,b:33,c:["x","y"]},
              traditional为false会对数据进行深层次迭代;  */

Ajax请求设置csrf_token

Ajax在提交post请求时,也需要携带csrf_token,否则会报权限错误的错误,现在来看看设置csrf_token的三种方式。

方式1

通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。

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

方式2
$.ajaxSetup({
    data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

方式3

过获取返回的cookie中的字符串 放置在请求头中发送。

注意:需要引入一个jquery.cookie.js插件。插件下载地址,下载并解压后,将其中的cookie.jquery.json文件放到你项目的js存放目录中即可。

<body>
<h1>你好,test!</h1>

{% csrf_token %}
用户名:<input type="text" id="username">
<button id="btn">提交</button>

</body>

<script src="{% static 'jquery.js' %}"></script>
<script src="{% static 'jquery.cookie.js' %}"></script>

<script>
  $('#btn').click(function () {

      $.ajax({
          url:'/test/',
          type:'post',
          headers:{
           /*  
           其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,
           我们发contenttype类型数据的时候,csrf_token就可以这样加
           */
              "X-CSRFToken": $.cookie('csrftoken'),
          },
          data:{
              uname:$('#username').val(),
          },
          success:function (res) {
              console.log(res)
          }

      })
  })



</script>

Ajax简单demo

我已经把demo上传至码云,可以移步gitee进行查阅,这里就不放源码了,直接看怎么使用。

login页面的ajax请求

登录认证通过的效果如下:

Django之Ajax - 图4

登录认证失败的效果如下:

Django之Ajax - 图5

上面就是login页面的功能,接下来说下home页面的。

home页面的ajax请求

<script src="{% static 'jquery.js' %}"></script>
<script>
    $.ajax({
        url: "{% url 'data' %}",
        type: "post",
        /*   下面两段暂时先不看
        data: JSON.stringify({k1: 'v1', k2: 'v2'}),
        contentType: 'application/json',
        */
        success: function (res) {
            // each循环后端返回的res数据
            $.each(res, function (k, v) {
                // console.log(k,v)
                var liStr = '<li>' + v.toString() + '</li>'
                $('ul').append(liStr)
            })
        }
    })

</script>

这个ajax请求主要是在加载home页面的时候,会去请求下data的这个url,然后data返回的数据如下:

def data(request):
    '''
    先不看这段代码,这段代码是来处理ajax发送post请求的
    body_data = request.body.decode('utf-8')
    import json
    ret = json.loads(body_data)
    print(body_data, type(body_data), ret, type(ret))
    '''
    l1 = [11, 22, 33, 44, 55, 66]
    # JsonResponse回复一个非字典数据的时候必须加“safe=False”,否则程序运行会报错
    return JsonResponse(l1, safe=False)

可以看到,ajax拿到data响应的数据后,通过循环取值的方式,将其渲染到了无序列表中,并展现在前端页面。

现在来看看加载ajax页面时,请求data这个url携带的数据,是什么样的:

Django之Ajax - 图6

可以看到在给data发送post请求时,携带的数据在请求体中,现在来看看data如何提取这些数据的,可能有些人看了下login页面的ajax请求处理后,会想,还能怎么提取,无非就是通过request.POST.get('key')来取值呗,错了,login页面发送的ajax请求之所以可以在request.post中拿到值,那是因为django内部会给处理Content-Type: application/x-www-form-urlencoded;的数据,你可以在浏览器上看下请求头,login页面提交post请求时的请求头是不是设置了Content-Type: application/x-www-form-urlencoded;,如下:

Django之Ajax - 图7

但是在给data发送数据时,我们指明了contentType: 'application/json',如下:

        data: JSON.stringify({k1: 'v1', k2: 'v2'}),
        contentType: 'application/json',

django不认contentType: 'application/json',所以也就不会帮我们把请求携带的数据写到request.POST中,因此,我们需要使用下面的代码来取(不要想为什么要发送django不认识的json类型给后端,没办法,我们在写接口的时候,经常会收到json类型的数据,这个时候就需要我们按照下面的方式进行处理):

    body_data = request.body.decode('utf-8')
    import json
    ret = json.loads(body_data)
    print(body_data, type(body_data), ret, type(ret))

Ajax上传文件

demo中的upload.html页面是上传文件的,现在我们摘出部分代码,来看看ajax是如何上传文件的,代码如下:

<script src="{% static 'jquery.js' %}"></script>
<script>
    $('#btn').click(function () {
        /* ajax 上传文件,需要借助FormData对象,否则会报错,FormData可以携带文件数据
         */
        var formdata = new FormData();
        formdata.append('uname', $('#uname').val());

        /* $('#file')[0]:表示将找到的jquery对象通过[0]转换为DOM对象
           然后通过DOM对象的files方法,找到上传的文件内容列表,
           然后通过索引取值[0],拿到第一个捕获到的上传的第一个文件(files方法拿到的是一个文件列表)*/
        formdata.append('head_pic', $('#file')[0].files[0]);
        formdata.append('csrfmiddlewaretoken', $('[name=csrfmiddlewaretoken]').val());

        $.ajax({
            url: "{% url 'upload' %}",
            type: 'post',

            // 上传文件时,下面两个固定死的要设置为false
            processData: false,    // 不对数据做任何处理,原汁原味的传到后端
            contentType: false,    // 不设置内容类型

            data: formdata,     // data只需要把上面加工好的formdata发送即可
            success: function (res) {
                console.log(res)
            }
        })

    })
</script>

不管是页面中的form表单上传文件,还是ajax上传文件,对于后端来说,处理的流程都是一样的,如下:

# ajax上传文件
def upload(request):
    print(request.POST)
    if request.method == 'GET':
        return render(request, 'upload.html')

    elif request.method == 'POST':
        print(request.POST)
        '''
        上传文件的时候,默认post请求里只有文件名,文件具体的数据放在了request.FILES中。
        打印结果:<QueryDict: {'csrfmiddlewaretoken': ['JNukzx9qO78648myA0iu5anxBXNtSaPRnd3qUhCMiYDaHWFIKpNhECY6mfYWYHHe'], 'head-pic': ['img19_1920x1200.jpg'], 'uname': ['lvjianzhao']}>
        '''
        print(request.FILES)
        '''
        前端的form表单如果需要上传文件,需要给form表单增加属性:enctype="multipart/form-data"
        就可以正常看到文件数据了,否则显示的为空。
        打印结果:<MultiValueDict: {'head-pic': [<InMemoryUploadedFile: img19_1920x1200.jpg (image/jpeg)>]}>
        '''
        file_obj = request.FILES.get('head_pic')  # file_obj,就相当于一个rb模式打开的文件句柄了
        print(file_obj)  # img19_1920x1200.jpg
        file_name = file_obj.name  # img19_1920x1200.jpg
        # file_path = 'C:\\Users\\Administrator\\Desktop\\' + file_name
        file_path = os.path.join(settings.BASE_DIR, 'images', file_name)
        # 用wb的方式把文件写入到指定文件中
        with open(file_path, 'wb') as f:
            # 方式一,按行读取file_obj,按行写入f
            # for i in file_obj:
            #     f.write(i)
            # 方式二,通过chunks方法,按固定字节读取,按固定字节写入(推荐)
            # chunks()默认一次返回大小为65536B,也就是64KB,最大为2.5M,是一个生成器
            # 源码中定义的大小:DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
            # 也可以通过关键字传参:chunk_size=,进行设置大小
            for chunck in file_obj.chunks():
                f.write(chunck)
        return HttpResponse('ok')

json模块序列化时间类型的数据

代码如下:

from datetime import date, datetime
import json

d = {'name': 'ljz', 'birthday': datetime.now()}

json_str = json.dumps(d)

print(json_str)

如果运行上面的脚本,则会报错:TypeError: Object of type datetime is not JSON serializable,为什么呢?因为json中没有对应date、datetime类型的数据,python和json中的数据类型对应关系如下:

Python JSON
dict object
list,tuple array
str string
int,float number
True true
False false
None null

可以看到,并没有时间日期类型的对应关系,所以如果要序列化的数据中包含时间日期类型的,需要重写json的方法,才可以正常序列化,重写后的代码如下:

from datetime import date, datetime
import json


# 对含有日期格式数据的json数据进行转换
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, field)


d = {'name': 'ljz', 'birthday': datetime.now()}

json_str = json.dumps(d, cls=JsonCustomEncoder)

print(json_str)