Ajax
1. importlib
# 和demo.py同目录下的pp package中的mm.py文件;文件中有一个Person类 # 不使用importlib导入 from pp import mm zz = mm.Person("zzz") print(zz.name) # 使用importlib将字符串映射导入 import importlib s = "pp.mm" tmp = importlib.import_module(s) # 作用于字符串 print(tmp) # <module 'pp.mm' from '/Users/zhangyaqian/Desktop/day72/day72/pp/mm.py'> lsd = tmp.Person("llll") print(lsd.name) # llll
2. 基于中间件做一个访问频率限制,每个IP每分钟最多访问3次
import datetime HISTORY = {} # {'ip':{'last':time,'times':number},} BLACK_LIST = [] class Throttle(MiddlewareMixin): # throttle 节流阀 def process_request(self,request): ip = request.META.get("REMOTE_ADDR") # 通过request找到访问ip now = datetime.datetime.now() # 拿到当前时间 if ip in BLACK_LIST: return HttpResponse("ACCESS DENIED") #如果在黑名单中直接返回,不需要做更多重复无效的判断和搜索 if ip not in HISTORY: HISTORY[ip] = {"last": now, "times": 0} # 初始化 if (now - HISTORY[ip]["last"]) < datetime.timedelta(seconds=60): # 注意timedelta()的用法 HISTORY[ip]["times"] += 1 else: HISTORY[ip]["times"] = 0 HISTORY[ip]["last"] = now if HISTORY[ip]["times"] > 3: BLACK_LIST.append(ip) return HttpResponse("ACCESS DENIED: more than 3 times within 60s")
3. 渲染(字符串替换)是发生在后端(views.py<->template)(渲染成完整的html文件),不是在浏览器端(接收渲染好的html文件,按照html标签显示出来)
4. 如果找不到urls, django框架级别返回404,不走中间件的process_response
一、Ajax
1. AJAX预备知识点(JSON:JavaScript Object Notation)
a. Python中
import json
json.loads() # dict <== JSON格式字符串
json.dumps() # dict ==> SON格式字符串
举例:
import json
d = {"name": "zzz", "age": 16}
ret1 = json.dumps(d, indent=4) # json.dumps()后面可可以设置一系列参数
print(ret1)
ret2 = json.loads(ret1)
print(ret2)
b. JavaScript中:
JSON.parse() # JavaScript obj <== JSON格式字符串
JSON.stringify() # JavaScript obj ==> JSON格式字符串
# 日期对象要先转化成字符串,才能继续序列化
# 不能使用undefined. 在JSON中为null
2. AJAX (Asynchronous Javascript And XML)
a. AJAX: 使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML);其最开始传输的主要是XML,目前依旧可以用来传输XML,但主要用来传输更精简的JSON等
AJAX和FORM表达:
AJAX:主要应用于用户无感的区域(页面不刷新,后端查数据)
FORM:页面刷新
AJAX主要的应用场景:
1) 搜索引擎根据用户输入的关键字,自动提示检索关键字。
2) 注册时候的用户名的查重。
b. jQuery实现的AJAX (示例:页面输入两个整数,通过AJAX传输到后端计算出结果并返回。)
# 可以用jQuery实现,也可用JS实现AJAX;用jQuery实现相对便捷,但需要提前导入jQuery;用JS实现AJAX的方式需了解,不常用
# AJAX_TEST.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <title>AJAX</title> </head> <body> {# 没有做csrf token的处理,本示类注释了settings中中间件的第四行(csrf相关)#} <input type="text" id="i1"> + <input type="text" id="i2"> = <input type="text" id="i3"> <button id="b1">AJAX TEST</button> <script src="/static/jquery-3.3.1.min.js"></script> # 用jQuery的方式($),必须先导入jQuery <script> $('#b1').click(function(){ $.ajax({ url: "/ajax/", type: 'POST', {# ajax 只能处理GET和POST请求#} data: {i1:$("#i1").val(),i2:$("#i2").val()}, /* data参数中的键值对,如果不是字符串,需要将其转换成字符串类型。如果键值对的值是类似Python列表套列表[1[1,2,3],[4,5,6]],
那么需要加一个设置traditional为True,不然会变成[1,2,3,4,5,6];或者将其序列化成json格式。
success 为回调函数,data为views.py中传来的结果res
*/
success: function (data) { $("#i3").val(data); } }) }) </script> </body> </html>
# views.py
from django.shortcuts import render,redirect,HttpResponse def ajax(request): if request.method == 'POST': print(request.POST) # <QueryDict: {'i1': ['12'], 'i2': ['12']}> i1 = request.POST.get('i1') i2 = request.POST.get('i2') # 得到的是字符串 res = int(i1) + int(i2) # 必须先转化成int类型,才能做进一步运算 return HttpResponse(res) return render(request,'AJAX_TEST.html')
c. AJAX优缺点
1)优点:
局部更新:AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
异步:在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作
2)缺点:
服务端压力大:滥用AJAX(大量的button绑定AJAX),导致后段需要维护好多API,导致服务端压力大,进而影响性能
#补充:
同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
d. AJAX处理csrf_token
1) 方式一:通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。
# {% csrf_token%} 的本质为隐藏的input框
# .html中:
{% csrf_token %} $("#b11").click(function () { // 点击之后要做的事儿 $.ajax({ url: "/ajax_test2/", type: "POST", data: { i11: $("#i11").val(), i22: $("#i22").val(), // 使用JQuery取出csrf_token中csrfmiddlewaretoken的值,拼接到data中 csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); $("#i33").val(data); } }) });
2) 方式二:通过获取返回的cookie中的字符串放置在请求头中发送(不需在html中加入隐藏的input框{% csrf_token%})。(需要引入一个jquery.cookie.js插件。)
# jquery.cookie.js依赖于jQuery,先导入jQuery再导入jquery.cookie.js
# .html中:
<script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $("#b22").click(function () { $.ajax({ url: "/ajax_test2/", type: "POST", headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 从Cookie取csrf_token,并设置ajax请求头 data: { i11: $("#i11").val(), i22: $("#i22").val(), }, success: function (data) { console.log(data); $("#i33").val(data); } }) });
3) 方式三:使用$.ajaxSetup()方法为ajax请求统一设置
# 在static中配置setupAjax.js文件,使用前导入; 在后续调用$.ajax的过程中不需再做额外的配置
# setupAjax.js
// 自己写一个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'); // 为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); } } });
# .html中:
<script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/setupAjax.js"></script> <script> // 点击b33 $("#b33").click(function () { $.ajax({ url: "/ajax_test2/", type: "POST", data: { i11: $("#i11").val(), i22: $("#i22").val(), }, success: function (data) { console.log(data); $("#i33").val(data); } }) });
e. AJAX上传文件
# views.py
def upload(request): if request.method == "POST": print(request.POST) file_obj = request.FILES.get("f1") # 通过.FILES取文件 with open(file_obj.name, "wb") as f: for chunk in file_obj.chunks(): f.write(chunk) return HttpResponse("O98K") return render(request, "upload.html")
# upload.html
{# 使用form表单,提交文件#} <form action="/upload/" method="post" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="f1"> <input type="submit" value="提交"> </form> {#使用ajax,提交文件#} <input id="i1" type="file"> <button id="b1">点我</button> <script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/setupAjax.js"></script> <script> $("#b1").click(function () { // 先生成一个表单对象 var formData = new FormData(); // 向form表单对象添加键值对数据 formData.append("f1", $("#i1")[0].files[0]); formData.append("name", "张曌"); {#这种方式可以上传文件,亦可添加普通数据#} $.ajax({ url: "/upload/", type: "POST", processData: false, // 告诉jQuery不要去处理发送的数据 contentType: false, // 告诉jQuery不要去设置Content-Type请求头 {#data不能直接放文件($("#i1")[0].files[0]),因此要做以上操作(建立一个表单对象,然后把键值对对象添加进去)#} data: formData, {#之后的data和之前的不是一个data;前面的是提交的文件对象,后面的是后端返回的response(OK)#} success:function (data) { console.log(data) } }) });
d. 用JS实现AJAX(了解)
# views.py
def ajax_js(request): if request.method == "POST": print(request.POST) return HttpResponse("OK") return render(request, "ajax_js.html")
#ajax_js.html
<button id="b1">点我 发送 JS 版AJAX请求</button> <script> let b1Ele = document.getElementById("b1"); b1Ele.onclick = function () { // 1. 生成xmlHttp对象 let xmlHttp = new XMLHttpRequest(); // 2. 调用xmlHttp的open方法设置请求相关配置项 xmlHttp.open("POST", "/ajax_js/", true); // 3. 设置请求头 xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); // 4. 调用send方法发送数据 xmlHttp.send("username=张曌&age=16"); // 5. 处理返回的响应 xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { alert(xmlHttp.responseText); } }; } </script>
3. Django内置的serializers
# 把QuerySet对象转成字符串可以用Json,也可以用serializers;
# 用Json的话,需要一个一个转,性能低。后续会经常使用serializers批量转化
#.html
$("#b44").click(function () { $.ajax({ url: "/books/", type: "GET", success: function (data) { if (data.code === 0) { console.log(data.data); # 拿到数据库中的数据 } } }) });
#views.py
from django.http import JsonResponse # JsonResponse为HttpResponse的一个子类;接受字典转成Json格式的字符串进行进一步传输 def books(request): ret = {"code": 0} book_list = models.Book.objects.all() from django.core import serializers data = serializers.serialize("json", book_list) ret["data"] = data return JsonResponse(ret)
4. 补充(SweetAlert)
https://github.com/lipis/bootstrap-sweetalert
#views.py
def delete_book(request): if request.method == "POST": delete_id = request.POST.get("id") import time time.sleep(2) # 可以看出showLoaderOnConfirm: true 的效果 models.Book.objects.filter(id=delete_id).delete() ret = {"code": 0} return JsonResponse(ret)
# .html
<link rel="stylesheet" href="/static/sweetalert/sweetalert.css"> $("#b55").click(function () { swal({ title: "你确定要删除吗?", text: "删除可就找不回来了哦!", type: "warning", showCancelButton: true, // 是否显示取消按钮 confirmButtonClass: "btn-danger", // 确认按钮的样式类 confirmButtonText: "删除", // 确认按钮文本 cancelButtonText: "取消", // 取消按钮文本 closeOnConfirm: false, // 点击确认按钮不关闭弹框 showLoaderOnConfirm: true // 显示正在删除的动画效果 }, function () { var deleteId = 2; $.ajax({ url: "/delete_book/", type: "post", data: {"id": deleteId}, success: function (data) { if (data.code === 0) { swal("删除成功!", "你可以准备跑路了!", "success"); } else { swal("删除失败", "你可以再尝试一下!", "error") } } }) }); })
5. 补充(echarts:画图)
http://echarts.baidu.com/download.html
下载并导入<script src="/static/echarts.simple.min.js"></script>
# views.py
data = { "date": ["2018-01", "2018-02", "2018-03", "2018-04", "2018-05"], "code_amount": [1200, 1000, 800, 600, 12000] } def demo(request): if request.method == "POST": return JsonResponse(data) return render(request, "demo.html")
# demo.html
<script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/echarts.simple.min.js"></script> <script> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 使用AJAX向后端请求数据 $.ajax({ url: "/demo/", type: "POST", data: {name: "长牙签"}, success: function (data) { // 指定图表的配置项和数据 var option = { title: { text: 'ECharts 入门示例' }, tooltip: {}, legend: { data: ['代码量'] }, xAxis: { data: data.date }, yAxis: {}, series: [{ name: '代码量', type: 'bar', data: data.code_amount }] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); } }); </script>
6. 回顾input(用户名查重,用input事件追踪input框的内容变化)
# .html
<input type="text" id="i2"> $("#i2").on("input", function () { console.log($(this).val()) })