Django基础七之Ajax
本节目录
一 Ajax简介
1.简介
AJAX(Asynchronous javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
a.同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
b.异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
AJAX除了异步的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程
2.示例
页面输入两个整数,通过AJAX传输到后端计算出结果并返回。
html文件名称为ajax_demo1.html,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AJAX局部刷新实例</title>
</head>
<body>
<input type="text" id="i1">+
<input type="text" id="i2">=
<input type="text" id="i3">
<input type="button" value="AJAX提交" id="b1">
<script src="/static/jquery-3.2.1.min.js"></script>
<script>
$("#b1").on("click", function () {
$.ajax({
url:"/ajax_add/", //别忘了加双引号
type:"GET",
data:{"i1":$("#i1").val(),"i2":$("#i2").val()}, //object类型,键值形式的,可以不给键加引号
success:function (data) {
$("#i3").val(data);
}
})
})
</script>
</body>
</html>
views.py里面的内容:
def ajax_demo1(request):
return render(request, "ajax_demo1.html")
def ajax_add(request): #time.sleep(10) #不影响页面发送其他的请求
i1 = int(request.GET.get("i1"))
i2 = int(request.GET.get("i2"))
ret = i1 + i2
return JsonResponse(ret, safe=False) #return render(request,\'index.html\') #返回一个页面没有意义,就是一堆的字符串,拿到了这个页面,你怎么处理,你要做什么事情,根本就没有意义
urls.py里面的内容
urlpatterns = [
...
url(r\'^ajax_add/\', views.ajax_add),
url(r\'^ajax_demo1/\', views.ajax_demo1),
...
]
启动django项目,然后运行看看效果,页面不刷新
3.AJAX****常见应用情景
搜索引擎根据用户输入的关键字,自动提示检索关键字。
还有一个很重要的应用场景就是注册时候的用户名的查重。
其实这里就使用了AJAX技术!当文件框发生了输入变化时,使用AJAX技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后再把后端返回的结果展示出来。
a.整个过程中页面没有刷新,只是刷新页面中的局部位置而已!
b.当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应!
当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为lemontree7777777的用户是否存在,最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”。
a.整个过程中页面没有刷新,只是局部刷新了;
b.在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作;
4.AJAX****的优缺点
优点:
1.AJAX使用JavaScript技术向服务器发送异步请求;
2.AJAX请求无须刷新整个页面;
3.因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高;
5.作业
写一个登陆认证页面,登陆失败不刷新页面,提示用户登陆失败,登陆成功自动跳转到网站首页。
login.html,内容如下:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
用户名:<input type="text" id="username">
密码:<input type="text" id="pwd">
{% csrf_token %}
<button id="sub">提交</button>
<span style="color: red;font-size: 12px;" id="error"></span>
</div>
<script src="{% static \'jquery.js\' %}"></script>
<script>
$(\'#sub\').click(function () {
$.ajax({
url:"{% url \'login\' %}",
type:\'post\',
data:{username:$(\'#username\').val(),pwd:$(\'#pwd\').val(),csrfmiddlewaretoken:$(\'[name=csrfmiddlewaretoken]\').val()},
success:function (data) {
data = JSON.parse(data);
console.log(data,typeof data);
if (data[\'status\']){
location.href=data[\'home_url\'];
}
else {
$(\'#error\').text(\'用户名或者密码错误!\')
}
}
})
})
</script>
</body>
</html>
base.html,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
欢迎来到xxx官网
</h1>
</body>
</html>
urls.py,内容如下
url(r\'^login/\', views.login,name=\'login\'),
url(r\'^home/\', views.home,name=\'home\'),
views.py,内容如下
def login(request):
res_dict = {\'status\':None,\'home_url\':None}
if request.method == \'GET\':
return render(request,\'login.html\')
else:
uname = request.POST.get(\'username\')
pwd = request.POST.get(\'pwd\')
user_obj = models.UserInfo.objects.filter(name=uname,password=pwd).exists()
import json
if user_obj:
res_dict[\'status\'] = True
res_dict[\'home_url\'] = reverse(\'home\')
res_json_dict = json.dumps(res_dict)
return HttpResponse(res_json_dict) #直接回复字典格式是不可以的,必须转换成json字符串,转换成普通字符串也是不行的,因为前端需要对json进行反序列获得这个字典,在通过字典的形式来操作数据。
else:
res_dict[\'status\'] = False
res_json_dict = json.dumps(res_dict)
return HttpResponse(res_json_dict)
# 如果你就是不使用JsonResponse的话,也可以给HttpResponse添加一个参数,content_type=\'application/json\',那么前端ajax拿到数据之后,也是不需要反序列化的,ajax的回调函数就收到的就是一个反序列化之后的一个对象,因为ajax接受到数据后,通过这个data_type或者content_type发现你发送来的是个json格式的数据,那么ajax内容就自动将这个数据反序列化得到了js的数据对象,然后通过对象可以直接操作数据。 # return HttpResponse(res_json_dict,data_type=\'application/json\') # return JsonResponse(res_dict)
def home(request):
return render(request,\'base.html\')
还有一点注意一下,如果你想通过ajax来删除表格中某条记录,并且ajax里面的url不写死的情况下(url反向解析),那么就需要下面这种方式,实现url里面参数的动态:
还有一个细节要注意:
并且删除一条数据的时候,后端删除成功之后,你通过后端给你的返回值判断后端是否删除成功,如果删除成功,你有两种方式来删除前端页面的对应一行的记录,1:刷新页面,2:如果不让刷新页面,那么你就需要找到你点击的这个按钮的那一行的tr标签,通过dom操作把它删除
ajax里面写$(this)时要注意的问题:还有一点注意,如果你添加某些dom对象的时候,如果你想在不刷新页面的情况下来添加这个对象,那么你要注意,如果这个对象也需要绑定事件的话,你需要用on来给和他相同的标签对象来绑定事件。
在这里补充个事情:
settings配置文件里面加上下面这句话,意思是说,告诉django,如果别人请求我的路径的时候,你不要自己处理别人输入的路径最后面的/了,如果这个值为True,而我们假如写了一个url为url(\'index/\',views.test),如果用户输入的时127.0.0.1:8000/index的话,django会让浏览器重新再发一次请求,并且在这个路径后面加上/,也就成了127.0.0.1:8000/index/,此时和我们的url就能匹配上了,因为我们的url正则写的就加了/,如果你将下面这个值设置成false,那么django就不会自动帮你做这个事情了,那么用户在输入127.0.0.1:8000/index,没有最后那个斜杠的路径时,就无法和我们的url正则匹配上了,所以就找不到url了,就会报错,但是注意,django只能帮你重定向让浏览器再发一个get请求,如果你是post请求(非get请求),django就没有办法了,他还是帮你重新定向发送get请求,不能满足你的需求,所以如果你用post方法提交数据的时候,就像上面这个ajax里面的那个url写的必须和你后端配置的那个url对应好,所以别忘了如果你后端url上url(\'index/\',views.test)这个index后面加了/,那么你写ajax往这个路径下提交数据的时候,ajax里面的url参数后面别忘了写上/,让这个url和后端url正则规则对应好。
二 Ajax的使用
1.基于jQuery的实现
看代码:
<button class="send_Ajax">send_Ajax</button>
<script>
$(".send_Ajax").click(function(){
$.ajax({
url:"/handle_Ajax/",
type:"POST",
data:{username:"chao",password:123},
success:function(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>
2.基于原生js实现
看代码
var b2 = document.getElementById("b2");
b2.onclick = function () {
// 原生JS
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "/ajax_test/", true);
xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHttp.send("username=chao&password=123456");
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
alert(xmlHttp.responseText);
}
};
};
3.Ajax-服务器-Ajax流程图
4.ajax参数
请求参数:
######################------------data---------################
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---------################
processData:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;if为false,
那么对data:{a:1,b:2}会调用json对象的toString()方法,即{a:1,b:2}.toString()
,最后得到一个[object,Object]形式的结果。
######################------------contentType---------################
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---------################
traditional:一般是我们的data数据有数组时会用到 :data:{a:22,b:33,c:["x","y"]},
traditional为false会对数据进行深层次迭代;
响应参数:
dataType: 预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。
默认不需要显性指定这个属性,ajax会根据服务器返回的content Type来进行转换;
比如我们的服务器响应的content Type为json格式,这时ajax方法就会对响应的内容
进行一个json格式的转换,if转换成功,我们在success的回调函数里就会得到一个json格式
的对象;转换失败就会触发error这个回调函数。如果我们明确地指定目标类型,就可以使用
data Type。
dataType的可用值:html|xml|json|text|script
见下dataType实例
三 Ajax请求设置csrf_token
方式1
通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "chao",
"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插件。
<script src="{% static \'js/jquery.cookie.js\' %}"></script>
$.ajax({
headers:{"X-CSRFToken":$.cookie(\'csrftoken\')}, #其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,我们发contenttype类型数据的时候,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。
我又有疑问了,同一次登录,form表单中的token每次都会变,而cookie中的token不便,django把那个salt存储在哪里才能保证验证通过呢。直到看到源码。
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:一次性令牌在完成他们的工作后将被销毁,比较安全。
...等等吧,还有很多其他的。
$.ajax({
url: "/cookie_ajax/",
type: "POST",
headers: {"X-CSRFToken": $.cookie(\'csrftoken\')}, // 从Cookie取csrftoken,并设置到请求头中
data: {"username": "chao", "password": 123456},
success: function (data) {
console.log(data);
}
})
或者用自己写一个getCookie方法:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== \'\') {
var cookies = document.cookie.split(\';\');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + \'=\')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie(\'csrftoken\');
每一次都这么写太麻烦了,可以使用$.ajaxSetup()方法为ajax请求统一设置。
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
注意:
如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。
如果你的视图渲染的HTML文件中没有包含 {% csrf_token %},Django可能不会设置CSRFtoken的cookie。
这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie。
django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def login(request):
pass
更多细节详见:Djagno官方文档中关于CSRF的内容
四 Ajax文件上传
请求头ContentType
ContentType指的是请求体的编码类型,常见的类型共有3种:
1 application/x-www-form-urlencoded(看下图)
这应该是最常见的 POST 提交数据的方式了。浏览器的原生