11 Django -- Ajax/CSRF以及文件上传
Ajax
Ajax简介
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
特点:异步请求、局部刷新
异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
局部刷新:可以局部添加网页内容,如提示:用户名或者密码错误等。
AJAX常见应用情景
-
搜索引擎根据用户输入的关键字,自动提示检索关键字。
-
注册时的用户名的查重。
当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为输入的用户是否存在,最终服务器返回true表示用户名已经存在了,浏览器在得到结果后显示“用户名已被注册!”。
a.整个过程中页面没有刷新,只是局部刷新了;
b.在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作;
-
提交数据,上传文件。
Ajax的优缺点
https://www.cnblogs.com/SanMaoSpace/archive/2013/06/15/3137180.html
优点:
- AJAX使用JavaScript技术向服务器发送异步请求;
- AJAX请求无须刷新整个页面;
- 因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高;
缺点:
- 安全问题,不经意间会暴露比以前更多的数据和服务器逻辑
- 如果用户禁用了JS,网站就取不到数据
- AJAX干掉了Back和History功能,即对浏览器机制的破坏。用户通常会希望单击后退按钮能够取消他们的前一次操作,但是在Ajax应用程序中,这将无法实现。
- AJAX不能很好支持移动设备。
Ajax简单登录认证
用form表单登录,提交数据会刷新页面。ajax不会刷新。
login.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div style="width: 500px;height:200px;background-color: #4ba7ff;margin-top: 180px; margin-left: 350px;">
<div style="padding-top:50px;">
<div class="form-group">
<label class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="请输入用户名" id="username">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" placeholder="请输入密码" id="password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default" id="sub">登 录</button>
<span id="error" style="color: red"></span>
</div>
</div>
</div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<script >
$('#sub').click(function () {
// 绑定点击事件,一点击 登录 会将数据提交给后端,不会刷新页面
var username=$('#username').val();
var password=$('#password').val();
$.ajax({
url:'{% url "login" %}', //
type:'post',
data:{'user':username,'pwd':password},
// data会携带数据提交到url路径 res:是接收后端返回的数据
success:function (res) {
if(res === '1'){
// 表示登录成功,跳转到home页面
location.href='{% url "home" %}'}
else{
// 登录失败,局部刷新,添加提示
$('#error').text('用户名或密码错误!')
}
}
})
})
</script>
</html>
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login, name='login'),
url(r'^home/', views.home, name='home'),
views.py
from django.shortcuts import render, HttpResponse, redirect
def login(request):
if request.method=='GET':
return render(request, 'login.html')
else:
user = request.POST.get('user') # 获取ajax中data携带的数据
pwd = request.POST.get('pwd')
if user == 'yan' and pwd == '123': # 假设用户名与密码
return HttpResponse('1') # 登录成功返回1
else:
return HttpResponse('0') # 登录成功返回0
def home(request):
return render(request, 'home.html')
csrf认证
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。攻击者通过HTTP请求将数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。
所以解决csrf攻击的最直接的办法就是生成一个随机的csrftoken值,保存在用户的页面上,每次请求都带着这个值过来完成校验。
token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。
form表单设置csrf_token
{% csrf_token %} 标签,加上这个标签,模板渲染之后就是一个隐藏的input标签,提交数据时,会随数据一起提交给后端。
<input type="hidden" name=‘csrfmiddlewaretoken’ values=‘dkjdkasiofaad...’>
<form action="" method="post">
{% csrf_token %} // form表单里面加上这个标签,模板渲染之后就是input标签
<div style="width: 500px;height:200px;background-color: #4ba7ff;margin-top: 180px; margin-left: 350px;">
<div style="padding-top:50px;">
<div class="form-group">
<label class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="请输入用户名" id="username" name='username'>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" placeholder="请输入密码" id="password" name='password'>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default" id="sub">登 录</button>
<span id="error" style="color: red"></span>
</div>
</div>
</div>
</div>
</form>
Ajax设置csrf认证
方式一:
通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。
// 在body标签中,增加 {% csrf_token %} 标签
<script >
$('#sub').click(function () {
var username=$('#username').val();
var password=$('#password').val();
// csrf方式一:
var csrf=$('[name=csrfmiddlewaretoken]').val();
// 使用jQuery取出csrfmiddlewaretoken的值,
$.ajax({
url:'{% url "login" %}',
type:'post',
data:{'user':username,'pwd':password, "csrfmiddlewaretoken":csrf}, // data会携带数据
success:function (res) {
if(res === '1'){
location.href='{% url "home" %}'}
else{
console.log(res);
$('#error').text('用户名或密码错误!')
}
}
})
})
</script>
方式二:
用 {{ csrf_token }} 变量直接获取csrfmiddlewaretoken的值
<script >
$('#sub').click(function () {
var username=$('#username').val();
var password=$('#password').val();
// csrf方式二:
var csrf = '{{ csrf_token }}';
$.ajax({
url:'{% url "login" %}',
type:'post',
data:{'user':username,'pwd':password, "csrfmiddlewaretoken":csrf}, // data会携带数据
success:function (res) {
if(res === '1'){
location.href='{% url "home" %}'}
else{
console.log(res);
$('#error').text('用户名或密码错误!')
}
}
})
})
</script>
方式三:
引入:jquery.cookie.js;下载:http://plugins.jquery.com/cookie/
通过获取返回的cookie中的字符串 放置在请求头中发送。
Ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,发contenttype类型数据的时候,csrf_token就可以这样加。
<script >
$('#sub').click(function () {
var username=$('#username').val();
var password=$('#password').val();
$.ajax({
url:'{% url "login" %}',
type:'post',
data:{'user':username,'pwd':password, "csrfmiddlewaretoken":csrf}, // data会携带数据
// csrf 方式三:
headers:{
"X-CSRFToken":$.cookie('csrftoken'),
}, // 必须引入jquery.cookie.js
success:function (res) {
if(res === '1'){
location.href='{% url "home" %}';
{#$('#error').text('登录成功!'); }#}
}
else{
{#console.log(res);#}
$('#error').text('用户名或密码错误!')
}
}
})
})
</script>
注意:
如果使用从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
文件上传
请求头Content-Type
服务端告诉浏览器,数据是以什么封装的。否则浏览器不知道怎样解开数据。
1. Content-Type: application/x-www-form-urlencoded;charset=utf-8
# 最常见的 POST 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个。
user=yan&pwd=123 #这就是上面这种content-type规定的数据格式,后端对应这个格式来解析获取数据,不管是get方法还是post方法,都是这样拼接数据,大家公认的一种数据格式.
2. Content-Type:multipart/form-data;
# form表单设置enctype属性时,post提交数据的方式
3. Content-Type:application/json
# 告诉浏览器 服务端消息主体是序列化后的 JSON 字符串。
form表单上传文件
在form中加 enctype="multipart/form-data"。form表单不支持发json类型的content-type格式的数据,而ajax什么格式都可以发,也是ajax应用广泛的一个原因。
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
用户名: <input type="text" name="username">
密码: <input type="password" name="password">
文件: <input type="file" name="file">
<input type="submit">
</form>
views.py
from django.conf import settings
# 能用全局settings获取一些配置,如路径等
def upload(request):
if request.method == 'GET':
print(settings.BASE_DIR) #/static/
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)
# 方式二:chunks()默认一次返回大小为65536B,也就是64KB,最大为2.5M的数据,是一个生成器
for chunk in file_obj.chunks():
f.write(chunk)
return HttpResponse('ok')
通过js来找文件对象
Ajax的上传文件
<script >
$('#sub').click(function () {
// 创建一个对象
var formdata = new FormData();
var user = $('#username').val();
var pwd = $('#password').val();
var file_obj = $('[type=file]')[0].files[0]; // js获取文件对象
// 封装
formdata.append('username',user);
formdata.append('password',pwd);
formdata.append('file',file_obj);
$.ajax({
url:'{% url "upload" %}',
type:'post',
// data:{username:user,password:pwd,csrfmiddlewaretoken:csrf},
//data:{username:uname,password:pwd},
data:formdata,
processData:false, // 必须写,告诉不做任何预处理
contentType:false, // 必须写
headers:{
"X-CSRFToken":$.cookie('csrftoken'),
},
success:function (res) {
console.log(res);
if (res === '1'){
// $('.error').text('登录成功');
location.href = '/home/'; // http://127.0.0.1:8000/home/
}else{
$('.error').text('用户名密码错误!');
}
}
})
})
</script>
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)
# 方式二:chunks()默认一次返回大小为65536B,也就是64KB,最大为2.5M的数据,是一个生成器
for chunk in file_obj.chunks():
f.write(chunk)
return HttpResponse('ok')
JsonResponse
发送json类型的数据
from django.http import JsonResponse
username = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None}
print('>>>>>',request.POST)
#<QueryDict: {'{"username":"123","password":"123"}': ['']}>
if username == 'chao' and pwd == '123':
ret_data['status'] = 1000 # 状态码
ret_data['msg'] = '登录成功!'
else:
ret_data['status'] = 1001 # 状态码
ret_data['msg'] = '登录失败!'
# 方式一 ,关闭ensure_ascii后不乱码
# ret_data_json = json.dumps(ret_data,ensure_ascii=False)
# return HttpResponse(ret_data_json)
# 方式二,指定响应头
# return HttpResponse(ret_data_json,content_type='application/json')
# 方式三
return JsonResponse(ret_data)
# 非字典类型数据,需要加上 safe=False参数,否则认为是不合法的
#ret_data=[0,1,'a']
#return JsonResponse(ret_data, safe=False)
jsontest.html
<script>
$.ajax({
url:'{% url "jsontest" %}',
type:'post',
// data:{username:uname,password:pwd,csrfmiddlewaretoken:csrf},
//序列化 //data:JSON.stringify({username:uname,password:pwd}),
data:{username:uname,password:pwd},
headers:{
// contentType:'application/json',
"X-CSRFToken":$.cookie('csrftoken'),
},
success:function (res) {
{#console.log(res,typeof res); // statusmsg {"status": 1001, "msg": "登录失败"}#}
// 方式一:反序列化
{#var res = JSON.parse(res); //-- json.loads()#}
// 方式二、三
console.log(res,typeof res); //直接就是反序列化之后的了
//JSON.stringify() -- json.dumps
if (res.status === 1000){
// $('.error').text('登录成功');
location.href = '/home/'; // http://127.0.0.1:8000/home/
}
else{ $('.error').text(res.msg);
}
}
})
</script>
SweetAlert插件--对话框
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">
</head>
<body>
<div>
<button class="btn btn-danger">删除</button>
</div>
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.js' %}"></script>
<script>
$(".btn-danger").on("click", function () {
swal({
title: "你确定要删除吗?",
text: "删除可就找不回来了哦!",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "我已经下定决心",
cancelButtonText: "容我三思",
closeOnConfirm: false
},
function () {
var deleteId = $(this).parent().parent().attr("data_id");
$.ajax({
url: "/delete_book/",
type: "post",
data: {"id": deleteId},
success: function (data) {
console.log(data,typeof data);
if (data === '1') {
swal("删除成功!", "你可以准备跑路了!", "success");
} else {
swal("删除失败", "你可以再尝试一下!", "error")
}
}
})
});
})
</script>
</html>