Ajax
Ajax简介
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。
- 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
- 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
- AJAX局部刷新
jQuery实现的ajax
<script> //.shadow{position: fixed;left: 0;top: 0;right: 0;bottom: 0;background-color: black;opacity: 0.4;z-index: 999;} $("#m_add_ok").click(function(){ $.ajax({ url: '/m_user_add/', // 要提交的地址 type: 'POST', // GET或POST,提交方式 //dataType:'JSON', data: { // 提交的数据 'name': $("#m_add_box input[name='name']").val(), 'gender':$("#m_add_box input[name='gender']:checked").val(), 'email':$("#m_add_box input[name='email']").val(), 'depart_id':$("#m_add_box select[name='depart_id']").val() }, success: function(data){ // 当服务端处理完成后,返回数据时,该函数自动调用 // data=服务端返回的值 默认是字符串 // console.log(data); // JSON.parse(字符串) => 对象 // JSON.stringify(对象) => 字符串 var data=JSON.parse(data) //如果上面写了dataType:'JSON' 就相当于把success函数的参数data字符串转化成json对象,这就不需要写了json.parse(data) if(data.status){ location.reload(); // location.href='/user_list/'; }else{ $('#titlemsg').text(data.titlemsg); } } }) }); </script>
一、jQuery.ajax()
1、请求参数
######################------------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会对数据进行深层次迭代;
2、响应参数
/*
dataType: 预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。
默认不需要显性指定这个属性,ajax会根据服务器返回的content Type来进行转换;
比如我们的服务器响应的content Type为json格式,这时ajax方法就会对响应的内容
进行一个json格式的转换,if转换成功,我们在success的回调函数里就会得到一个json格式
的对象;转换失败就会触发error这个回调函数。如果我们明确地指定目标类型,就可以使用
data Type。
dataType的可用值:html|xml|json|text|script
见下dataType实例
*/
二、jQuery.serialize()
该函数主要根据用于提交的有效表单控件的name和value,将它们拼接为一个可直接用于表单提交的文本字符串,该字符串已经过标准的URL编码处理(字符集编码为UTF-8)。
- jQuery 1.0 新增该函数。语法:jQueryObject.serialize()
- 返回值:serialize()函数的返回值为String类型,返回将表单元素编码后的可用于表单提交的文本字符串。
- 不在
- serialize()函数通常用于将表单内容序列化,以便通过AJAX方式提交。
- serialize()函数用于序列化所有表单元素。$("form").serialize()
- serialize()函数用于序列化部分表单元素。$(":text, select, :checkbox").serialize()
def ajax_login(request): if request.method == 'GET': return render(request, 'login.html') else: import json ret = {'status': True,'msg': None} obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: # print(obj.errors) # obj.errors对象 ret['status'] = False ret['msg'] = obj.errors v = json.dumps(ret) return HttpResponse(v) <body> <h1>用户登录</h1> <form id="f1" > {% csrf_token %} <p> <input type="text" name="user" /> </p> <p> <input type="password" name="pwd" /> </p> <a onclick="submitForm();">提交</a> </form> <script src="/static/jquery-1.12.4.js"></script> <script> function submitForm(){ $('.c1').remove(); $.ajax({ url: '/ajax_login/', type: 'POST', data: $('#f1').serialize(),// user=alex&pwd=456&csrftoen=dfdf\ dataType:"JSON", success:function(arg){ console.log(arg); if(arg.status){ }else{ $.each(arg.msg,function(index,value){ console.log(index,value); var tag = document.createElement('span'); tag.innerHTML = value[0]; tag.className = 'c1'; $('#f1').find('input[name="'+ index +'"]').after(tag); }) } } }) } </script> </body>
csrf跨站请求伪造
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'); 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页面上通过导入该文件即可自动帮我们解决ajax提交post数据时校验csrf_token的问题,(导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的)
<body> <!-- {% csrf_token %} 生成input框 {{ csrf_token }} 生成随机字符串 --> <form method="POST" action="/csrf1.html"> {% csrf_token %} <input id="user" type="text" name="user" /> <input type="submit" value="提交"/> <a id="a_sub" >Ajax提交</a> </form> <script src="/static/jquery-1.12.4.js"></script> <script> $("#a_sub").click(function(){ var user = $('#user').val(); $.ajax({ url: '/csrf1.html', type: 'POST', //方式1: data: { "user":user,'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()}, //方式2: // data: { "user":user,'csrfmiddlewaretoken': '{{ csrf_token }}'}, success:function(arg){ console.log(arg); } }) }); </script> <script src="/static/jquery.cookie.js"></script> <script> $("#a_sub").click(function(){ var user = $('#user').val(); $.ajax({ url: '/csrf1.html', //方式3:放在请求头中 headers:{'X-CSRFToken': $.cookie('csrftoken')}, type: 'POST', data: { "user":user}, success:function(arg){ console.log(arg); } }) }); </script> </body>
JSON.parse(): 用于将一个 JSON 字符串转换为 JavaScript 对象 eg: console.log(JSON.parse('{"name":"tom"}')); console.log(JSON.parse('{name:"tom"}')) ; // 错误 console.log(JSON.parse('[12,undefined]')) ; // 错误 JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。 eg: console.log(JSON.stringify({'name':"tom"}));
上传文件
一、Ajax(FormData)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">注册</h1> <form id="myform"> <!--这里我们不用form表单提交数据 知识单纯的用一下form标签而已--> {% csrf_token %} {% for form in form_obj %} <div class="form-group"> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{ form }} <span style="color: red" class="pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="myfile">头像 {% load static %} <img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px"> </label> <input type="file" id="myfile" name="avatar" style="display: none" > </div> <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> </form> </div> </div> </div> <script> $("#myfile").change(function () { //本地上传预览 方法1: // 文件阅读器对象 // 1 先生成一个文件阅读器对象 let myFileReaderObj = new FileReader(); // 2 获取用户上传的头像文件 let fileObj = $(this)[0].files[0]; // 3 将文件对象交给阅读器对象读取 myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作 // 4 利用文件阅读器将文件展示到前端页面 修改src属性 // 等待文件阅读器加载完毕之后再执行 myFileReaderObj.onload = function(){ $('#myimg').attr('src',myFileReaderObj.result) } //本地上传预览 方法2: /* var obj = $(this)[0].files[0]; var v = window.URL.createObjectURL(obj); $('#myimg').attr('src',v); $('#myimg').load(function(){ window.URL.revokeObjectURL(v); }); */ }) $('#id_commit').click(function () { // 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件 let formDataObj = new FormData(); // 1.添加普通的键值对 {#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#} $.each($('#myform').serializeArray(),function (index,obj) { {#console.log(index,obj)#} // obj = {} formDataObj.append(obj.name,obj.value) }); // 2.添加文件数据 formDataObj.append('avatar',$('#myfile')[0].files[0]); // 3.发送ajax请求 $.ajax({ url:"", type:'post', data:formDataObj, // 需要指定两个关键性的参数 contentType:false, processData:false, success:function (args) { if (args.code==1000){ // 跳转到登陆页面 window.location.href = args.url }else{ // 如何将对应的错误提示展示到对应的input框下面 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg,function (index,obj) { {#console.log(index,obj) // username ["用户名不能为空"]#} let targetId = '#id_' + index; $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }) // 给所有的input框绑定获取焦点事件 $('input').focus(function () { // 将input下面的span标签和input外面的div标签修改内容及属性 $(this).next().text('').parent().removeClass('has-error') }) </script> </body> </html>
二、伪Ajax上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <style> .avatar_con { height: 100px; width: 100px; position: relative; } .avatar_con iframe { display: none } .avatar_con img { height: 100%; width: 100%; border: 0; overflow: hidden; max-height: 100px; } .avatar_con #previewImg { border-radius: 50%; border: 1px solid #dddddd; padding: 3px; height: 96px; width: 96px; } .avatar_con #avatarImg { top: 0; left: 0; right: 0; bottom: 0; opacity: 0; position: absolute; z-index: 102; } .avatar-avatar_con .text { position: absolute; left: 0; bottom: 0; text-align: center; } </style> <body> <h1>伪 Ajax上传文件</h1> <div class="avatar_con"> <iframe id="upload_iframe" name="upload_iframe"></iframe> <form method="POST" action="/upload/" enctype="multipart/form-data" target="upload_iframe"> <!--target和iframe的name 进行关联--> <!--别忘了 编码格式 enctype="multipart/form-data"--> {% csrf_token %} <img id="previewImg" src="/media/avatar/default.png"> <div class="text">点击图片更换</div> <input id="avatarImg" name="avatar" type="file"/> </form> </div> <script src="http://code.jquery.com/jquery-1.12.3.min.js"></script> <script type="text/javascript"> $(function () { bindChangeAvatar(); }); function bindChangeAvatar() { $('#avatarImg').change(function () { $(this).parent().submit(); $('#upload_iframe').load(function () { var con = this.contentWindow.document.body.innerText; con = JSON.parse(con); if (con.status) { console.log(con.data); $('#previewImg').attr('src', '/' + con.data); } }) }) } </script> </body> </html>
三、原生Ajax上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <style> .avatar_con { height: 100px; width: 100px; position: relative; } .avatar_con iframe { display: none } .avatar_con img { height: 100%; width: 100%; border: 0; overflow: hidden; max-height: 100px; } .avatar_con #previewImg { border-radius: 50%; border: 1px solid #dddddd; padding: 3px; height: 96px; width: 96px; } .avatar_con #avatarImg { top: 0; left: 0; right: 0; bottom: 0; opacity: 0; position: absolute; z-index: 102; } .avatar-avatar_con .text { position: absolute; left: 0; bottom: 0; text-align: center; } </style> <body> <h1>原生Ajax上传文件</h1> <div class="avatar_con"> <label for="avatar"> <img id="previewImg" src="/media/avatar/default.png"> <div class="text">点击图片更换</div> </label> <input onchange="bindChangeAvatar()" id="avatarImg" name="avatar" type="file"/> </div> <script> function bindChangeAvatar(){ var formdata = new FormData(); formdata.append("avatar",document.getElementById('avatarImg').files[0]); formdata.append("csrfmiddlewaretoken","{{ csrf_token }}"); //csrf var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ var arg = JSON.parse(xhr.responseText); document.getElementById('previewImg').src='/' + arg.data } }; xhr.open('POST','/upload/'); /*xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); 发post请求必须设置Content-Type,request.POST才有数据,并且已转换成字典, 不然就要去request.body中取值还是字节 b'a=1&b=2' 如果发送的是formdata 则不需要设置 */ xhr.send(formdata); } </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>首页</h1> <input type="text" id="i1" /> + <input type="text" id="i2" /> = <input type="text" id="i3" /> <input type="button" id="btn1" value="jQuery Ajax" onclick="add1();" /> <input type="button" id="btn2" value="原生Ajax" onclick="add2();" /> <script src="/static/jquery-1.12.4.js"></script> <script> function add1(){ $.ajax({ url: '/add1/', type: 'POST', data: {'i1': $('#i1').val(),'i2': $('#i2').val()}, success:function(arg){ $('#i3').val(arg); } }) } function add2(){ /* var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ alert(xhr.responseText); } }; xhr.open('GET','/add2/?i1=12&i2=19'); xhr.send(); */ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ alert(xhr.responseText); } }; xhr.open('POST','/add2/'); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); /*发post请求必须设置Content-Type,request.POST才有数据,并且已转换成字典,不然就要去request.body中取值还是字节 b'a=1&b=2' * 如果发送的是formdata 则不需要设置 * */ xhr.send("i1=12&i2=19"); } </script> </body> </html>
四、服务端处理上传文件
# 配置文件: STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static') ] AUTH_USER_MODEL = 'app01.UserInfo' # 与用户上传相关的配置 MEDIA_ROOT= os.path.join(BASE_DIR, "media") MEDIA_URL = "/media/" # 路由中: from django.conf.urls import url from django.contrib import admin from app01 import views from django.views.static import serve from django.conf import settings urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^register/',views.register,name='reg'), url(r'^login/',views.login,name='login'), url(r"media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}), ] # 视图中: import os from django.shortcuts import render,HttpResponse,redirect from app01.myforms import MyRegForm from app01 import models from django.http import JsonResponse import time # Create your views here. def register(request): form_obj = MyRegForm() if request.method == 'POST': back_dic = {"code": 1000, 'msg': ''} # 校验数据是否合法 form_obj = MyRegForm(request.POST) # 判断数据是否合法 if form_obj.is_valid(): # print(form_obj.cleaned_data) # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 # 将字典里面的confirm_password键值对删除 clean_data.pop('confirm_password') # {'username': 'jason', 'password': '123', 'email': '123@qq.com'} # 用户头像 file_obj = request.FILES.get('avatar') file_obj.name = "%s.%s" % (time.time(), file_obj.name.rsplit('.')[-1]) """针对用户头像一定要判断是否传值 不能直接添加到字典里面去""" if file_obj: clean_data['avatar'] = file_obj # 直接操作数据库保存数据 models.UserInfo.objects.create_user(**clean_data) back_dic['url'] = '/login/' else: back_dic['code'] = 2000 back_dic['msg'] = form_obj.errors return JsonResponse(back_dic) return render(request,'register.html',locals()) def login(request): return render(request,'login.html')
五、models.py
from django.db import models from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用户信息 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/%Y-%m', default="avatars/default.png") create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE) def __str__(self): return self.username class Meta: verbose_name = '用户信息' verbose_name_plural = '用户信息'
六、带form验证
# 书写针对用户表的forms组件代码 from django import forms from app01 import models class MyRegForm(forms.Form): username = forms.CharField(label='用户名', min_length=3, max_length=8, error_messages={ 'required': '用户名不能为空', 'min_length': "用户名最少3位", 'max_length': "用户名最大8位" }, # 还需要让标签有bootstrap样式 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) ) password = forms.CharField(label='密码', min_length=3, max_length=8, error_messages={ 'required': '密码不能为空', 'min_length': "密码最少3位", 'max_length': "密码最大8位" }, # 还需要让标签有bootstrap样式 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) confirm_password = forms.CharField(label='确认密码', min_length=3, max_length=8, error_messages={ 'required': '确认密码不能为空', 'min_length': "确认密码最少3位", 'max_length': "确认密码最大8位" }, # 还需要让标签有bootstrap样式 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空', 'invalid': '邮箱格式不正确' }, widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) ) # 钩子函数 # 局部钩子:校验用户名是否已存在 def clean_username(self): username = self.cleaned_data.get('username') # 去数据库中校验 is_exist = models.UserInfo.objects.filter(username=username) if is_exist: # 提示信息 self.add_error('username', '用户名已存在') return username # 全局钩子:校验两次是否一致 def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password', '两次密码不一致') return self.cleaned_data
{% extends 'mybook/base.html' %} {% block title %} <title>图书管理系统</title> {% endblock %} {% block content %} <div class="container"> <div class="row"> <a href="/mybook/add_book/" class="btn btn-info">添加图书</a> </div> </div> <div class="container"> <div class="row"> <table class="table table-striped table-hover"> <thead> <tr> <th>书名</th> <th>作者</th> <th>出版社</th> <th>出版日期</th> <th>零售价</th> <th>操作</th> </tr> </thead> <tbody> {% for book in book_list %} <tr> <td>{{ book.title }}</td> <td> {% for author in book.authors.all %} {% if forloop.last %} {{ author.name }} {% else %} {{ author.name }}, {% endif %} {% endfor %} </td> <td>{{ book.publish.name }}</td> <td>{{ book.publishDate|date:"Y-m-d" }}</td> <td>{{ book.price }}</td> <td> <button class="btn btn-sm btn-warning" data-toggle="modal" data-target="#myModal" data-whatever="{{ book.nid }}">编辑 </button> <a href="/mybook/delete_book?id={{ book.nid }}&next_action={{ current_path }}" class="btn btn-sm btn-danger">删除</a> </td> </tr> {% endfor %} </tbody> </table> </div> </div> <div class="modal fade" id="myModal"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button class="close" data-dismiss="modal">X</button> <div class="modal-title">修改图书信息</div> </div> <div class="modal-body"> <form id="book_form"> {% csrf_token %} <div class="form-group"> <input type="text" hidden name="book_nid" id="book_nid"> <label for="book">书名</label> <input type="text" class="form-control" id="title" name="title" disabled> <span class="label label-danger" id="msg_tittle"></span> </div> <div class="form-group"> <b style="margin-bottom: 5px;display: inline-block">出版社</b> <select class="form-control" id="publisher" name="publisher"> {% for publisher in publisher_list %} {% if publisher.nid == book_obj.publisher.nid %} <option value={{ publisher.nid }} selected>{{ publisher.name }}</option> {% else %} <option value={{ publisher.nid }}>{{ publisher.name }}</option> {% endif %} {% endfor %} </select> <span class="label label-danger"></span> </div> <div class="form-group"> <label for="publish_date">出版日期</label> <input type="date" class="form-control" id="publish_date" name="publish_date"> <span class="label label-danger " id="msg_publish_date"></span> </div> <div class="form-group"> <label for="price">零售价</label> <input type="number" class="form-control" id="price" name="price"> <span class="label label-danger " id="msg_price"></span> </div> <div class="form-group"> <b style="margin-bottom: 5px;display: inline-block">作者</b> <select class="form-control" multiple id="authors" name="authors"> {% for author in author_list %} {% if author in book_obj.authors.all %} <option value={{ author.nid }} selected>{{ author.name }}</option> {% else %} <option value={{ author.nid }}>{{ author.name }}</option> {% endif %} {% endfor %} </select> <span class="label label-danger " id="msg_author"></span> </div> </form> </div> <div class="modal-footer"> <button class="btn btn-primary" id="book_save">保存</button> <button class="btn btn-primary" data-dismiss="modal">取消</button> </div> </div> </div> </div> {% endblock %} {% block script %} <script> $(function () { $("li[class=active]").parent().children().eq(0).addClass("active").siblings().removeClass("active"); }); $('#myModal').on('show.bs.modal', function (event) { var button = $(event.relatedTarget);// 触发事件的按钮 var book_id = button.data('whatever');// 解析出data-whatever内容 $.ajax({ url: "/mybook/edit_book?book_id=" + book_id, type: "GET", contentType: false, processData: false, data: {}, success: function (data) { //console.log(data); //console.log(book_id); $(".label-danger").html(""); $('#book_nid').val(book_id); $('#title').val(data.title); $('#price').val(data.price); $('#publish_date').val(data.publish_date); $('#publisher').val(data.publisher); $('#authors').val(data.book_author); } }); }); $("#book_save").click(function () { var formdata = new FormData(); var request_data = $("#book_form").serializeArray(); console.log(request_data); //[Object, Object, Object, Object, Object, Object, Object, Object, Object] $.each(request_data, function (index, data) { formdata.append(data.name, data.value); }); $.ajax({ url: "/mybook/edit_book/", type: "POST", contentType: false, processData: false, data: formdata, success: function (data) { $(".label-danger").html(""); if (isNullObj(data.msg)) { location.href = window.location.href; //"/mybook/index/"; } else { $.each(data.msg, function (field, error_list) { $('#' + field).html(error_list); }) } } }) }); </script> {% endblock %}
浏览器的同源策略
- 跨域是因为浏览器的同源策略导致的,也就是说浏览器会阻止非同源的请求
- 域名不同或者端口不同都属于非同源的
- 浏览器只阻止表单以及ajax请求,并不会阻止src请求,所以cnd,图片等src请求都可以发
解决跨域
方式一:CORS跨域请求
- CORS即Cross Origin Resource Sharing 跨域资源共享
- 随着技术的发展,现在的浏览器可以支持主动设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是设置响应头,使得浏览器允许跨域请求。
- 跨域请求还分为两种,一种叫简单请求,一种是复杂请求
1、简单请求
- HTTP方法是下列方法之一 : HEAD, GET,POST
- HTTP头信息不超出以下几种字段 : Accept, Accept-Language, Content-Language, Last-Event-ID , Content-Type
- 其中Content-Type只能是下列类型中的一个 : application/x-www-from-urlencoded , multipart/form-data , text/plain
- 简单请求:一次请求
- 简单请求:服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
2、复杂请求
- 任何一个不满足上述要求的请求,即会被认为是复杂请求
- 复杂请求:两次请求,在发送数据之前会先发一次请求用做“预检”,OPTIONS请求,只有“预检”通过后才再发送一次请求用于数据传输。
3、关于“预检”
- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
4、如何“预检”
- 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过 Access-Control-Request-Method
- 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过 Access-Control-Request-Headers
5、ContentType
- 请求头ContentType指的是请求体的编码类型,常见的类型共有3种:
- application/x-www-form-urlencoded 这应该是最常见的 POST 提交数据的方式了。浏览器的原生表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
- multipart/form-data 这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让表单的 enctype 等于 multipart/form-data。
- application/json 用来告诉服务端消息主体是序列化后的 JSON 字符串。
方式二:Jsonp
- Jsonp的实现原理是根据浏览器不阻止src请求来实现的,通过script标签的跨域特性来绕过同源策略。
- JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。
- JsonP解决跨域只能发送get请求,并且实现起来需要前后端交互比较多。
- 如果报错ALLOWED_HOSTS 需要在settings.py 中配置需要访问的域名 ALLOWED_HOSTS = ['http://www.s4.com']
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <input type="button" onclick="AjaxRequest()" value="跨域Ajax" /> <div id="container"></div> <script src="jquery-1.8.2.min.js" type="text/javascript"></script> <script type="text/javascript"> function AjaxRequest() { $.ajax({ url: 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403', type: 'GET', dataType: 'jsonp', jsonp: 'callback', jsonpCallback: 'list', success: function (data) { $.each(data.data,function(i){ var item = data.data[i]; var str = "<p>"+ item.week +"</p>"; $('#container').append(str); $.each(item.list,function(j){ var temp = "<a href='" + item.list[j].link +"'>" + item.list[j].name +" </a><br/>"; $('#container').append(temp); }); $('#container').append("<hr/>"); }) } }); } </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <input type="button" value="获取用户列表" onclick="getUsers();" /> <ul id="user_list"> </ul> <script src="/static/jquery-1.12.4.js"></script> <script> /* function getUsers(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ var content = xhr.responseText; console.log(content); } }; xhr.open('GET','http://www.s4.com:8001/users/'); xhr.send(); } */ //a = ['alex','eric','egon'] /* function getUsers(){ var tag = document.createElement('script'); tag.src = "http://www.s4.com:8001/users/?callback=bbb?sdd"; document.head.appendChild(tag); } function bbb(arg){ console.log(arg); } */ function getUsers(){ // XMLHttpRequest /* $.ajax({ url: 'http://www.s4.com:8001/users/?callback=bbb', type: 'GET', success:function(arg){ console.log(arg); } }) */ // JSONP $.ajax({ url: 'http://www.s4.com:8001/users/', type: 'GET', dataType: 'JSONP', jsonp: 'callback', jsonpCallback: 'bbb' }) } function bbb(arg){ console.log(arg); } </script> </body> </html>
import json from django.shortcuts import render,HttpResponse from django.core.handlers.wsgi import WSGIRequest from django.views import View def users(request): v = request.GET.get('callback') print('请求来了...') user_list = [ 'alex','eric','egon' ] user_list_str = json.dumps(user_list) temp = "%s(%s)" %(v,user_list_str,) print(temp) return HttpResponse(temp) def new_users(request): print(request.method) if request.method == "OPTIONS": obj = HttpResponse() obj['Access-Control-Allow-Origin'] = "*" obj['Access-Control-Allow-Methods'] = "DELETE" return obj obj = HttpResponse('asdfasdf') obj['Access-Control-Allow-Origin'] = "*" return obj # user_list = [ # 'alex','eric','egon' # ] # user_list_str = json.dumps(user_list) # obj = HttpResponse(user_list_str) # # obj['Access-Control-Allow-Origin'] = "*" # return obj
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <input type="button" value="获取用户列表" onclick="getUsers();" /> <ul id="user_list"> </ul> <script src="/static/jquery-1.12.4.js"></script> <script> function getUsers(){ $.ajax({ url: 'http://www.s4.com:8001/new_users/', type:"DELETE", success:function(arg){ console.log(arg); } }) } </script> </body> </html>
<button id="btn_one">点击我向JsonP1发送请求</button> <script> // 测试发送请求失败 跨域不能得到数据 $('#btn_one').click(function () { $.ajax({ url: "http://127.0.0.1:8000/jsonp1", type: "get", success: function (response) { console.log(response) } }) }); function handlerResponse(response) { alert(response) }; window.onload = function () { $("#btn_one").click(function () { let script_ele = document.createElement("script"); script_ele.src = "http://127.0.0.1:8000/jsonp1?callback=handlerResponse"; document.body.insertBefore(script_ele, document.body.firstChild); }) } </script>
class Test(APIView): def get(self, request): callback = request.query_params.get("callback", "") ret = callback + "(" + "'success'" + ")" return HttpResponse(ret)
添加响应头
from django.middleware.security import SecurityMiddleware from django.utils.deprecation import MiddlewareMixin class MyCors(MiddlewareMixin): def process_response(self, request, response): response["Access-Control-Allow-Origin"] = "*" if request.method == "OPTIONS": response["Access-Control-Allow-Methods"] = "PUT, DELETE" response["Access-Control-Allow-Headers"] = "content-type" return response
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middlewares.MyCors', ]
<!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>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script> </head> <body> <div id="app"> </div> <script> const app = new Vue({ el: "#app", mounted(){ axios.request({ url: "http://127.0.0.1:8000/demo/", method: "POST", data: { "name": "Alex" } }).then(function (data) { console.log(data) }) } }) </script> </body> </html>
基于cors实现AJAX请求
简单请求 OR 非简单请求 条件: 1、请求方式:HEAD、GET、POST 2、请求头信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 对应的值是以下三个中的任意一个 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求 简单请求和非简单请求的区别? 简单请求:一次请求 非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。 关于“预检” - 请求方式:OPTIONS - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息 - 如何“预检” => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过 Access-Control-Request-Method => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过 Access-Control-Request-Headers
补充一个SweetAlert插件示例
SweetAlert : https://www.sweetalert.cn/guides.html
$("#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") } } }) }); })
上面这个二次确认的动态框样式,你也可以直接应用到你的项目中
提醒事项:
1.上述的样式类部分渲染的样式来自于bootstrap中,所有建议在使用上述样式时,将bootstrap的js和css也导入了,这样的情况下,页面效果就不会有任何问题
2.弹出的上述模态框中,可能字体会被图标掩盖一部分,可通过调整字体的上外边距来解决
图片预览
定制上传按钮:
<div style="position: relative;display: inline-block;height: 50px;min-width: 300px;overflow: hidden;"> <div style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 1000;border: 1px dotted #9d9d9d;color: #9d9d9d;line-height: 50px;padding-left: 15px;"> <i class="fa fa-cloud-upload" aria-hidden="true"></i> <span>点击上传文件</span> </div> <input name="customer_excel" type="file" id="excelFile" style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: #333333;z-index: 1001;opacity: 0;filter:alpha(opacity=0);"> </div> $(function () { $('#excelFile').change(function (e) { var fileName = e.currentTarget.files[0].name; $(this).prev().find('span').text(fileName); }) })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;"> <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg" src="/static/imgs/default.png"> <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 0;position: absolute;z-index: 102;" id="avatarImg" name="avatar_img" type="file" class="img-file"/> </div> <div>点击图片更换(<a href="#">撤销</a>)</div> <p> <input type="text" placeholder="用户名"> </p> <p> <input type="text" placeholder="密码"> </p> </form> </div> </body> </html>
预览
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;"> <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg" src="/static/imgs/default.png"> <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 1;position: absolute;z-index: 102;" id="avatarImg" name="avatar" type="file" class="img-file"/> </div> <div>点击图片更换(<a href="#">撤销</a>)</div> <p> <input type="text" placeholder="用户名"> </p> <p> <input type="text" placeholder="密码"> </p> <input type="submit" value="提交"> </form> <script src="/static/jquery-1.12.4.js"></script> <script> $(function () { bindChangeAvatar1(); }); function bindChangeAvatar1() { $('#avatarImg').change(function () { var file_obj = $(this)[0].files[0]; var blob = window.URL.createObjectURL(file_obj); document.getElementById('previewImg').src = blob; $('#previewImg').load(function () { window.URL.revokeObjectURL(blob); }) }) } </script> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;"> <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg" src="/static/imgs/default.png"> <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 1;position: absolute;z-index: 102;" id="avatarImg" name="avatar" type="file" class="img-file"/> </div> <div>点击图片更换(<a href="#">撤销</a>)</div> <p> <input type="text" placeholder="用户名"> </p> <p> <input type="text" placeholder="密码"> </p> <input type="submit" value="提交"> </form> <script src="/static/jquery-1.12.4.js"></script> <script> $(function () { bindChangeAvatar2(); }); function bindChangeAvatar2() { $('#avatarImg').change(function () { var file_obj = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(file_obj); reader.onload = function (e) { $('#previewImg')[0].src = this.result; }; }) } </script> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;"> <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg" src="/static/imgs/default.png"> <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 1;position: absolute;z-index: 102;" id="avatarImg" name="avatar" type="file" class="img-file"/> </div> <div>点击图片更换(<a href="#">撤销</a>)</div> <p> <input type="text" placeholder="用户名"> </p> <p> <input type="text" placeholder="密码"> </p> <input type="submit" value="提交"> </form> <script src="/static/jquery-1.12.4.js"></script> <script> $(function () { bindChangeAvatar3(); }); function bindChangeAvatar3() { $('#avatarImg').change(function () { var file_obj = $(this)[0].files[0]; var form = new FormData(); form.add('img_upload', file_obj); $.ajax({ url: '', data: form, processData: false, // tell jQuery not to process the data contentType: false, // tell jQuery not to set contentType success: function (arg) { // 给img标签设置src属性,预览 } }) }) } </script> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;"> <iframe style="display: none;" id="upload_iframe" name="upload_iframe"></iframe> <form method="POST" action="上传地址" enctype="multipart/form-data" target="upload_iframe"> <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg" src="/static/imgs/default.png"> <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 1;position: absolute;z-index: 102;" id="avatarImg" name="avatar" type="file" class="img-file"/> </form> </div> <div>点击图片更换(<a href="#">撤销</a>)</div> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <p> <input type="text" placeholder="图片路径"> </p> <p> <input type="text" placeholder="用户名"> </p> <p> <input type="text" placeholder="密码"> </p> <input type="submit" value="提交"> </form> <script src="/static/jquery-1.12.4.js"></script> <script> $(function () { bindChangeAvatar4(); }); function bindChangeAvatar4() { $('#avatarImg').change(function () { $(this).parent().submit(); $('#upload_iframe').load(function () { var iframeContents = this.contentWindow.document.body.innerText; iframeContents = JSON.parse(iframeContents); if (iframeContents.status) { $('#previewImg').attr('src', '/' + iframeContents.data); } }) }) } </script> </div> </body> </html>
参考兼容性:
- https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
- https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
- https://developer.mozilla.org/zh-CN/docs/Web/API/FormData
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> <script src="/static/js/jquery-3.2.1.min.js"></script> <style> #avatar_img { margin-left: 20px; } #avatar { display: none; } .error { color: red; } </style> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} <span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/blog/img/default.png" alt=""> </label> <input type="file" id="avatar" name="avatar"> </div> <input type="button" class="btn btn-default reg_btn" value="submit"><span class="error"></span> </form> </div> </div> </div> <script> // 头像预览 $("#avatar").change(function () { // 获取用户选中的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性 ,src=文件对象的路径 reader.onload = function () { $("#avatar_img").attr("src", reader.result) }; }); // 基于Ajax提交数据 $(".reg_btn").click(function () { //console.log($("#form").serializeArray()); var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data, function (index, data) { formdata.append(data.name, data.value) }); formdata.append("avatar", $("#avatar")[0].files[0]); $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success: function (data) { //console.log(data); if (data.user) { // 注册成功 location.href="/login/" } else { // 注册失败 //console.log(data.msg) // 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展此次提交的错误信息! $.each(data.msg, function (field, error_list) { console.log(field, error_list); if (field=="__all__"){ $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); } $("#id_" + field).next().html(error_list[0]); $("#id_" + field).parent().addClass("has-error"); }) } } }) }) </script> </body> </html>