Django之Ajax

1.Ajax简介

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

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

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

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

a.同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;

b.异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

AJAX除了异步 的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程

示例

通过ajax访问登陆页面,将获取到的用户名跟密码提交到后台进行校验,校验后跳转到home页面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#<form action="" method="post">#}
{#    用户名:#}
{#    <input type="text" id="username">#}
{#    密码:#}
{#    <input type="password" id='password'>#}
{#    <button id="sub">提交</button>#}
{#    <span class="error"></span>#}
{#</form>#}
<h1>用户登录</h1>
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>

</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $('#sub').click(function () {
        $.ajax({
            url:'{% url 'login' %}',
            type:'post',
            data:{username:$('#username').val(),password:$('#password').val()},
            success:function (res) {
                if(res==='1'){
                    $('.error').text('登陆成功');
                    location.href='/home/'
                }else{
                    $('.error').text('用户名或密码错误')
                }
            }
        })
    })
</script>
</html>

urls.py路由分发

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login,name='login'),
    url(r'^home/', views.home),
]

views.py视图函数

def login(request):
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        print(request.POST)
        uname = request.POST.get('username')
        pwd = request.POST.get('password')
        if uname == 'alex' and pwd == 'alex123':
            return HttpResponse('1')
        else:
            return HttpResponse('0')

def home(request):
    return HttpResponse('home')

启动项目后查看运行效果,发现页面不刷新,实现了局部刷新

Ajax常见应用场景

搜索引擎根据用户输入的关键字,自动提示检索关键字.注册时候的用户名查重就使用了Ajax技术,当文件框发生了输入变化,使用Ajax技术向服务区发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后把后端返回的结果展示出来

a.整个过程中页面没有刷新,只是刷新了页面中的局部位置而已

b.当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应

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

整个过程页面没有刷新,只是局部刷新了,在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作

Ajax的优缺点

优点:

1.Ajax使用javascript技术向服务器发送异步请求

2.Ajax请求无需刷新整个页面

3.服务器响应内容不再是整个页面,而是整个页面中的部分内容,所以Ajax的性能高

缺点:

1.Ajax并不适合于所有场景,很多时候还是要使用同步交互

2.Ajax虽然提高了用户体验.但是无形中向服务器发送的请求次数增多了,导致服务器的压力增大

3.Ajax是在浏览器中使用JavaScript技术完成的,所以还需要处理浏览器兼容问题

2.Ajax请求设置csrf_token

​ 详述CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。攻击者通过HTTP请求江数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。

所以解决csrf攻击的最直接的办法就是生成一个随机的csrftoken值,保存在用户的页面上,每次请求都带着这个值过来完成校验。

那么django中csrf认证怎么玩的呢?

官方文档中说到,检验token时,只比较secret是否和cookie中的secret值一样,而不是比较整个token。

部分源码

def _compare_salted_tokens(request_csrf_token, csrf_token):
    # Assume both arguments are sanitized -- that is, strings of
    # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
    return constant_time_compare(
        _unsalt_cipher_token(request_csrf_token),
        _unsalt_cipher_token(csrf_token),
    )

def _unsalt_cipher_token(token):
    """
    Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
    CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
    the second half to produce the original secret.
    """
    salt = token[:CSRF_SECRET_LENGTH]
    token = token[CSRF_SECRET_LENGTH:]
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
    secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    return secret

​ token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。

django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。

同样也不难解释,为什么ajax请求时,需要从cookie中拿取token添加到请求头中。

常见的网络请求安全解决办法

    Cookies Hashing:每一个表单请求中都加入随机的Cookie,由于网站中存在XSS漏洞而被偷窃的危险。 
    HTTP refer:可以对服务器获得的请求来路进行欺骗以使得他们看起来合法,这种方法不能够有效防止攻击。 
    验证码:用户提交的每一个表单中使用一个随机验证码,让用户在文本框中填写图片上的随机字符串,并且在提交表单后对其进行检测。 
    令牌Token:一次性令牌在完成他们的工作后将被销毁,比较安全。
    ...等等吧,还有很多其他的。

设置csrf_token

方式1

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

#form表单类型
<form action="" method="post">
    {% csrf_token %}
    用户名:<input type="text" id="username">
    密码:<input type="password" id='password'>
    <button id="sub">提交</button>
    <span class="error"></span>
</form>

#非form表单类型
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>





$('#sub').click(function(){
    $.ajax({
        url: '{% url "login" %}',
        type: 'post',
        data: {username: $('#username').val(), password: $('#password').val(), csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val()},
        success: function (res) {
            console.log(res);
            if (res === '1'){
                $('.error').text('登陆成功');
                location.href = '/home/'
             } else {
                $('.error').text('用户名或密码错误')
          }
       }
    })
})

方式2:

#  csrfmiddlewaretoken:'{{ csrf_token }}


$('#sub').click(function(){
  $.ajax({
      url: '{% url "login" %}',
      type: 'post',
      data: {
username: $('#username').val(), 
password: $('#password').val(), 			csrfmiddlewaretoken:'{{ csrf_token }}'},
      success: function (res) {
          console.log(res);
          if (res === '1'){
              $('.error').text('登陆成功');
              location.href = '/home/'
          } else {
              $('.error').text('用户名或密码错误')
          }
      }
  })
})

方式3:

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

注意:需要引入一个jquery.cookie.js插件

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

$.ajax({
 headers:{"X-CSRFToken":$.cookie('csrftoken')}, 
 #其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,我们发contenttype类型数据的时候,csrf_token就可以这样加
})



$('#sub').click(function () {
    $.ajax({
        url: '{% url 'login' %}',
        type: 'post',
        data: {username: $('#username').val(), password: $('#password').val()},
        headers: {'X-CSRFToken': $.cookie('csrftoken'),},
        success: function (res) {
            if (res === '1') {
                $('.error').text('登陆成功');
                location.href = '/home/'
            } else {
                $('.error').text('用户名或密码错误')
            }
        }
    })
})

form表单设置csrf_token

<form action="" method="post">
    {% csrf_token %}  
// form表单里面加上这个标签,模板渲染之后就是一个input标签,type=hidden name=csrfmiddlewaretoken value='asdfasdfasdf'
    用户名: <input type="text" name="username">
    密码: <input type="password" name="password">
    <input type="submit">

</form>

3.Ajax文件上传

基于form表单的文件上传

模板部分

<form action="" method="post" enctype="multipart/form-data"> #上面说的其他两种contenttype都是键值的形式发送数据,这种form_data的格式一般是把大数据一段一段隔开的
      用户名 <input type="text" name="user">
      头像 <input type="file" name="avatar">  
  #如果不用form_data格式来发,那么默认的是urlencoded的格式,这个标签的数据会组成avatar:文件名字来进行发送
    <input type="submit">
</form>

视图部分

def index(request):
    print(request.body)   # 原始的请求体数据
    print(request.GET)    # GET请求数据
    print(request.POST)   # POST请求数据
    print(request.FILES)  # 上传的文件数据

    return render(request,"index.html")

unload函数

def upload(request):

    if request.method == 'GET':
        return render(request,'upload.html')
    else:
        print(request.POST)
        username = request.POST.get('user')
        file_obj = request.FILES.get('file_obj')
        #获得文件数据对象
        print('>>>',file_obj,type(file_obj))
        #>>> 1.txt         <class'django.core.files.uploadedfile.InMemoryUploadedFile'>一个文件对象,可以理解为一个文件句柄
        file_name = file_obj.name #1.txt
        print(file_name)
        # 将数据写到文件里面,需要名字,需要数据
        with open(file_name,'wb') as f: 
#直接把文件名字放这里,那么文件将直接生成在django的整个项目目录下,因为django配置的系统搜索的根路径就是咱们的项目文件夹路径,那个BASE_DIR,一般我们需要自己建立一个文件夹专门存放上传的文件
#所以需要我们自己来拼接一个路径放到这里,os.path.join(settings.BASE_DIR,'media','img',file_name)
  # f.write()  #不能一下写进去,占用的内容太多,要一点一点写
            for data in file_obj: #读数据
                f.write(data)  
 #每次读取的data不是固定长度的,和读取其他文件一样,每次读一行,识别符为\r  \n  \r\n,遇到这几个符号就算是读了一行


       for chunks in file_obj.chunks(): 
    		   f.write(chunks)
    #chunks()默认一次返回大小为经测试为65536B,也就是64KB,最大为2.5M,是一个生成器
         

通过js寻找文件对象

基于Ajax的文件上传

upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" id="username">
    密码:<input type="password" id='password'>
    文件: <input type="file" name="file">
    <button id="sub">提交</button>
    <span class="error"></span>
</form>

</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    
    $('#sub').click(function () {
        var formdata = new FormData();
        var uname = $('#username').val();
        var pwd = $('#password').val();
        var file_obj = $('[type=file]')[0].files[0];  //js获取文件对象
        var csrfmiddlewaretoken = {{ csrf_token }};
        formdata.append('username',uname);
        formdata.append('password',pwd);
        formdata.append('file',file_obj);
        formdata.append('csrfmiddlewaretoken',csrfmiddlewaretoken);

        $.ajax({
            url: '{% url "login" %}',
            type: 'post',
            data:formdata,
            processData:false,   //必须写
            contentType:false,   //必须写
            success: function (res) {
                console.log(res);
                if (res === '1') {
                    $('.error').text('登陆成功');
                    location.href = '/home/'
                } else {
                    $('.error').text('用户名或密码错误')
                }
            }
        })
    })
</script>
</html>

views.py

def upload(request):
    if request.method == 'GET':
        return render(request,'upload.html')
    else:
        print(request.POST)
        print(request.FILES)
        uname = request.POST.get('username')
        pwd = request.POST.get('password')
        file_obj = request.FILES.get('file')   #文件对象
        print(file_obj.name)     #文件名称
        with open(file_obj.name,'wb') as f:
            for i in file_obj:
                f.write(i)
        return HttpResponse('okk')

4.关于json

1.什么是json?

  • JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)

  • JSON 是轻量级的文本数据交换格式

  • JSON 独立于语言 *

  • JSON 具有自我描述性,更易理解

  • JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。

json数据类型和python数据类型的对比:

​ object和python的dict类型是差不多的,但是要求里面必须是双引号,string和list、tuple等也是一样的,都是双引号。python中的datetime等时间日期类型是不能进行json序列化的,因为json没有对应的格式,上面的这几种数据类型虽然进行json.dumps序列化之后都是个字符串,但是也是有格式的

​ 前端ajax拿到后端返回的一个python的json模块序列化之后的一个json字符串,那么js通过自己的json接口,将接受到的json字符串来反序列化为js自己语言能够识别的数据类型,然后再进行操作。

相当于我有一个json方法,你有一个json方法,你给我发数据必须是json字符串的格式,那么你就需要将你的数据类型序列化为json的字符串,那么序列化的时候,就把你的数据序列化为了符合json标准的字符串,然后我接收到这个字符串之后,我通过我的json方法,将数据转换为我的语言支持的数据类型。在进行反序列化的时候,如果你的字符串不符合json的格式,那么反序列化的时候就会报错,所以只要你是通过json序列化成的字符串,都是能够json反序列化的,因为json序列化的时候,就把你的数据改为了符合json标准的字符串形式,例如:里面的单引号,序列化后变成了双引号。

​ 合格的json对象

["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 } #这就是一个json的object类型,符合json的标准格式,就可以通过dumps来进行序列化
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ] 

普通字符串和json字符串,在进行序列化的时候的区别

import json
# s = "{'name':'chao','age':18}" 
#普通字符串,每加引号的没问题,加了引号的,必须是双引号才能使用json.loads()。
s = '{"name":"chao","age":18}'   #json字符串,里面必须是双引号
ret = json.loads(s)
print(ret)
print(ret['name'])

js的stringify与parse方法

JavaScript中关于JSON对象和字符串转换的两个方法:

JSON.parse(): 用于将一个 JSON 字符串转换为 JavaScript 对象

JSON.parse('{"name":"chao"}');
JSON.parse('{name:"chao"}') ;   // 错误
JSON.parse('[18,undefined]') ;   // 错误

JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。

JSON.stringify({"name":"chao"})

前后端使用json

方式一

jsontest.htnl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>



<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $('#sub').click(function () {
        var uname = $('#username').val();
        var pwd = $('#password').val();
        $.ajax({
            url:'{% url "jsontest" %}',
            type:'post',
            data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
                success:function (res) {
                console.log(res,typeof res);
                var res = JSON.parse(res);
                if(res.status === 1000){
                    location.href='/home/'
                }else{
                    $('.error').text(res.msg)
                }
            }

        })

    })
</script>
</html>

views.py

import json
def jsontest(request):
    '''
    状态码:
    1000:登陆成功
    1001:登陆失败
    :param request:
    :return:
    '''
    if request.method == 'GET':
        return render(request,'jsontest.html')
    else:
        uname = request.POST.get('username')
        pwd = request.POST.get('password')
        ret_data = {'status':None,'msg':None}
        print('>>>>',request.POST)
        if uname == 'alex' and pwd == 'alex123':
            ret_data['status'] = 1000
            ret_data['msg'] = '登陆成功'
        else:
            ret_data['status'] = 1001
            ret_data['msg'] = '登陆失败'
        ret_data_json = json.dumps(ret_data,ensure_ascii=False)
        return HttpResponse(ret_data_json)

方式二

jsontest.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>



<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $('#sub').click(function () {
        var uname = $('#username').val();
        var pwd = $('#password').val();
        $.ajax({
            url:'{% url "jsontest" %}',
            type:'post',
            data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
                success:function (res) {
                console.log(res,typeof res);
                if(res.status === 1000){
                    location.href='/home/'
                }else{
                    $('.error').text(res.msg)
                }
            }

        })

    })
</script>
</html>

views.py

import json
def jsontest(request):
    '''
    状态码:
    1000:登陆成功
    1001:登陆失败
    :param request:
    :return:
    '''
    if request.method == 'GET':
        return render(request,'jsontest.html')
    else:
        uname = request.POST.get('username')
        pwd = request.POST.get('password')
        ret_data = {'status':None,'msg':None}
        print('>>>>',request.POST)
        if uname == 'alex' and pwd == 'alex123':
            ret_data['status'] = 1000
            ret_data['msg'] = '登陆成功'
        else:
            ret_data['status'] = 1001
            ret_data['msg'] = '登陆失败'
        ret_data_json = json.dumps(ret_data,ensure_ascii=False)
        return HttpResponse(ret_data_json,content_type='application/json')

方式三

jsonresponse

jsontest.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>



<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $('#sub').click(function () {
        var uname = $('#username').val();
        var pwd = $('#password').val();
        $.ajax({
            url:'{% url "jsontest" %}',
            type:'post',
            data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
                success:function (res) {
                console.log(res,typeof res);
                if(res.status === 1000){
                    location.href='/home/'
                }else{
                    $('.error').text(res.msg)
                }
            }
        })
    })
</script>
</html>

views.py

from django.http import JsonResponse
def jsontest(request):
    '''
    状态码:
    1000:登陆成功
    1001:登陆失败
    :param request:
    :return:
    '''
    if request.method == 'GET':
        return render(request,'jsontest.html')
    else:
        uname = request.POST.get('username')
        pwd = request.POST.get('password')
        ret_data = {'status':None,'msg':None}
        print('>>>>',request.POST)
        if uname == 'alex' and pwd == 'alex123':
            ret_data['status'] = 1000
            ret_data['msg'] = '登陆成功'
        else:
            ret_data['status'] = 1001
            ret_data['msg'] = '登陆失败'
        return JsonResponse(ret_data)
    
    #非字典类型的数据需要给JsonResponse加上 safe=False 参数
相关推荐
rock——you1 小时前
django通过关联表字段进行排序并去重
数据库·后端·postgresql·django
jjw_zyfx2 小时前
Django和websocket实现简单的多人聊天
websocket·django·sqlite
工业互联网专业7 小时前
Python毕业设计选题:基于协同过滤的动漫推荐系统设计与实现_django+hive+spider
hive·python·django·源码·课程设计·spider
素**颜15 小时前
Ajax发送数据的步骤
前端·ajax
XinZong1 天前
【Python】Flask与Django对比详解:教你如何选择最适合你的Web框架
django·flask
大霞上仙1 天前
Django celery异步执行
后端·python·django
程序员大金1 天前
基于python+django+vue的购物商城系统
vue.js·python·mysql·django
_.Switch1 天前
Python Web 开发:FastAPI 依赖注入与中间件应用
开发语言·前端·python·中间件·django·fastapi
A阳俊yi2 天前
jQuery实现Ajax
前端·ajax·jquery
hbnn1112 天前
一次“okhttp访问间隔60秒,提示unexpected end of stream“的问题排查过程
okhttp