Django中的事务与ajax
一 事务与锁
1.行级锁
行级锁是由存储引擎实现的。如mysql里默认指定的InnoDB存储引擎,由它实现行级锁。InnoDB的行级锁定同样分为两种类型,共享锁(X)和排他锁(S)。
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁
事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE .. LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE .. FOR UPDATE
修改时,加了排他锁,其他临时会话不能查和修改等操作
Lock table tab_name write;给表加了写锁,临时会话不能往里面写。
Unlock tables; 解除写锁
2.ORM里加锁
加行级锁:select_for_update(nowait=False, skip_locked=False)
加行级锁必须用在事务里。
手动加互斥锁:
entries = models.Entry.objects.select_for_update().filter(author=request.user)
共享锁Mysql会自动加,因此不需用加共享锁。create、update、delete操作时,mysql自动加行级互斥锁,因此查看时可加互斥锁。
所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。
3.事务
(1)事务简介
事务具有ACID属性
事务控制语句
BEGIN或START TRANSACTION;显式地开启一个事务;
COMMIT; COMMIT会提交事务,并使已对数据库进行的所有修改成为永久性的;
ROLLBACK; 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
SAVEPOINT identifier; SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
RELEASE SAVEPOINT identifier; 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
ROLLBACK TO identifier; 把事务回滚到标记点;
(2)Django里事务操作
Django里开启事务有两大类,一是全局形式,二是局部形式。
a.全局形式
方法:在用户settings.py文件里,在DATABASES配置里,加入 "ATOMIC_REQUESTS":True。
原理:当有请求过来时,Django会在调用视图方法前开启一个事务。如果请求正确处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。因此全局开启事务,绑定的是http请求响应整个过程。
b.局部形式
atomic(using=None, savepoint=True)[source]
atomic是原子性。使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。(要么全成功,要么全失败)
(i)方式一:装饰器
from django.db import transaction
@transaction.atomic def viewfunc(request): do_stuff() 里面代码就在事务里
(ii)方式二:上下文管理器(设置事务的保存点)
from django.db import transaction def viewfunc(request): do_stuff() #这段代码使用默认的方式 with transaction.atomic(): #保存点 do_more_stuff() #这段代码在事务里 do_other_stuff()
注意::如果配置了全局的事务,它和局部事务可能会产生冲突。比如说:局部的事务完成之后,如果该函数里面其他的sql出了问题,那么局部事务也是提交不上。因为全局会回滚这个请求和响应所涉及到的所有的sql(局部的也被回滚了)。建议项目用局部事务。
设置事务小原则:
1.保持事务短小
2.尽量避免事务中rollback
3.尽量避免savepoint
4.默认情况下,依赖于悲观锁
5.为吞吐量要求苛刻的事务考虑乐观锁
6.显示声明打开事务
.锁的行越少越好,锁的时间越短越好
Ajax使用
一、Ajax介绍
Ajax:Asynchronous Javascript And XML。即异步的Javascript和XML。使用Javascript语言与服务器进行异步交互,传输的数据为XML(现在多为json)
(一)Ajax两大特性:
异步交互和局部刷新。
异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。同步交互指客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求。
局部刷新:不会刷新整个页面,局部内容发生变化。
(二)应用场景
1.搜索引擎根据用户输入的关键字,自动提示检索关键字。
2.注册时候的用户名的查重。(提示该用户已经注册过了)
当输入用户名后,光标移动到其他表单项时,浏览器会使用AJAX技术向服务器发出请求(这个时候你可以继续其他操作,这就是异步交互),服务器会查询用户名是否存在,然后返回结果(这个页面没有刷新,只有局部内容刷新了)。
二、Ajax操作
做个简单的注册页面,利用Ajax实现异步请求和局部刷新
(一)django基本设置
静态文件配置(引入juqery.js),分发路由操作
(二)操作
1.回复html页面
2.写html文件
ajax上传文件:<input type="file" id="file"> ajax用户名:<input type="text" id="uname"> <button id="btn">提交</button>
注意:form表单最后的提交按钮的type必须时button。如果还是写submit,那么提交还是按照form表单提交,不按照ajax提交。提交方式与form表单无关了,自然服务器取数据也与input标签的name值无关了。
3.ajax代码接收用户数据
(1)绑定事件
$('#btn').click(function () {}
给提交按钮绑定点击事件。
(2)ajax
通过 $.ajax({})方式来使用ajax。{}里是字典,主要包括三大部分url、type和success。浏览器有数据时还需data获取数据。
data内容:
data:{ uname:$('#username').val(), {# 获取数据 #} pwd:$('#password').val(), //csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val(), {#和form表单一样的crsf #} //方式二: csrfmiddlewaretoken: '{{ csrf_token }}' },
data数据是字典的形式。键为后台获取值的关键字。值:先定位到html文件里数据的标签,再通过val()取值。data数据里还要给后台传输csrf值(post请求提交要验证):html标签里有,直接以键值对的形式传输过去。
4.后台处理数据并发送数据给ajax
Ajax将data数据发给后台,后台在视图函数进行处理:
(1)获取ajax发来的数据
name=request.POST.get('uname') pwd=request.POST.get('pwd')
(2)匹配成功---进行数据处理和给ajax发消息
根据取来的值和数据库的进行比较,如果再数据库里,就给用户返回首页。不用ajax的话,django就直接重定向redirect。但用ajax,ajax识别不了redirect。因此要给ajax回复一个字典,让ajax进行重定向。
字典里有code码。方便ajax做判断,再做不同的处理。
发送形式:django发送给浏览器只有两种。Render发网页,HttpResponse发字符串。因此发过去的字典必须是字符串形式的。而ajax那边需要的是字典形式的数据。因此需要用到json解析。
数据传输
方式一:(不推荐)
发送端:字典外面直接包裹‘’,写成字符串的形式(注意json格式。里面都是双引号,最外面单引号),然后通过HttpResponse发送过去
ret = '{"code":0,"redirect_url":"/index/"}' return HttpResponse(ret)
接收端:JSON.parse(接收数据) 去掉’’,由字符串变成字典
var resStr=JSON.parse(res);
res是响应数据,从success:function (res)里接收
方式二:
用dumps方法将ret转为json字符串
d1={"code":0,"redirect_url":"/index/"} import json d1_json=json.dumps(d1) #给字典外面加个单引号,里面都是双引号 return HttpResponse(d1_json,content_type='application/json') #声明数据是json格式的,ajax接收数据不用反序列化。
HttpResponse发送json字符串过去时,可以再后面告诉ajax这个数据的格式是json格式的,ajax接收数据不用反序列化,可以直接处理。
方式三:
利用Django的JsonResponse方法。
from django.http import JsonResponse d1 = {"code": 0, "redirect_url": "/index/"} return JsonResponse(d1)
JsonResponse内部有转化json字符串的功能,同时也声明了类型是json。因此直接传输数据给ajax。Ajax也不需要反序列化。
(3)匹配失败:
匹配失败,也需告诉ajax信息,让ajax操作。
ret = '{"code":3,"redirect_url":"用户名或者密码错误"}' return HttpResponse(ret)
5.ajax处理后台的数据,并操作dom标签
Ajax接收到后台的数据,并进行处理,这部分代码都写在success:function (res) {}字典里。参数res是后台的响应数据,即传过来的值。
(1)针对不同code做不同的处理
如果是code是3,即用户名或密码错误,就新增标签给用户提示。(标签这个也可以写在html里。)
success:function (res) { console.log(res); {#res为响应数据#} var resStr=JSON.parse(res); {# 反序列化 解析res,由字符串变为字典 直接传过来json字符串需要,用dumps序列化并且发送过来声明类型了或者是JsonResponse发送过来不需要反序列化#} if (resStr['code']===3){ {#局部刷新:'{"code":3,"redirect_url":"用户名或者密码错误"}'#} var spanEle=document.createElement('span'); $(spanEle).text(resStr['redirect_url']); $('form').append(spanEle); }
(2)如果输入正确,即code=0,就重定向
location.href=resStr['redirect_url'];
(三)Ajax补充:
1.ajax流程
Ajax代码可以通过外部文件引入的方式导入到html文件。但这有一个问题:加载顺序会导致ajax里的模板渲染失败({{}}和{%%})。
地址栏输入网址------》django进行路由分发------》执行逻辑函数--------》渲染html文件---------》把渲染好的html给浏览器渲染------》浏览器再次渲染。
如果ajax文件是通过外部方式导入的,django在模板渲染时是渲染不到ajax的。浏览器渲染HTML文件时才加载ajex文件,此时django已经渲染结束了。
解决方法:ajax是外部文件时,js语法就不要用django的模板语法。
2.ajax的csrf_token验证
三个方式:(1)通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。(2)模板语法。在ajax设置时发送数据过去。(3)通过获取返回的cookie中的字符串 放置在请求头中发送。
(1)"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()
(2)$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
三、Ajax文件上传
(一)ContentType介绍
1.简介
ContentType可在请求头中看到,指的是请求体的编码类型。表示的是数据提交的方式。
ContentType类型有三种:application/x-www-form-urlencoded、 multipart/form-data和 application/json。
2.application/x-www-form-urlencoded
urlencoded是最常见的 POST 提交数据的方式。在<form> 表单,如果不设置 enctype 属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个。
数据形式:以&作为分割,通过等于方式连接键和值
3.multipart/form-data
上传文件
使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data
4.application/json
在Network里可以查看到response headers和requests headers。response headers的contenttype是服务器告诉浏览器数据是什么格式封装的。requests headers的contenttype是浏览器告诉服务器,数据是什么格式。
5.ajax里的contenttype
ajax里面写上这个contenttype类型,表示data的数据类型也要变成这个格式。Data默认是urlencoded格式。服务端接受到数据之后,通过contenttype类型的值来使用不同的方法解析数据。django能解析application/x-www-form-urlencoded 和multipart/form-data,但不能解析application/json类型。
Django里可通过request.body取出原始值,再将byte类型的数据转化为字符串。
(二)ajax文件上传
1.操作
Ajax给服务端上传文件的数据不能直接写在data里了,要借助一个FormData的类生成的对象formdata,把文件添加到这个对象中,在通过data发送给服务端。其次,还要在data下面写两个参数,保证ajax上传文件时不对文件内容做任何处理。
(1)把数据添加到formdata对象里
var formdata=new FormData(); formdata.append('aa',$('#uname').val()); formdata.append('headpic',$('#file')[0].files[0]); formdata.append('csrfmiddlewaretoken',$('[name=csrfmiddlewaretoken]').val());
找到文件操作: $('#file')[0]将jquery对象变为dom对象,利用dom的files方法拿到文件(是个列表),再取第一个值,即是文件内容。
(2)将formdata放到data里,提交给服务端。并且设置参数
data:formdata, processData:false, //不处理数据(不让预处理) contentType:false, //不设置内容类型 这两个在ajax上传文件是固定死的.不对文件做任何操作
(3)视图函数最开始要返回页面
if request.method=='GET': return render(request,'upload.html')
(4)取到数据
file_obj=request.FILES.get('headpic')
文件必须通过FILES方法取得,file_obj里就有数据。调用该对象的name属性,获得文件名。
(5)写入数据
方式一:
with open(path,'wb')as f: for i in file_obj: #每次读一行(以\r \n \r\n为分隔符) f.write(i)
方式二:
with open(path, 'wb')as f: for chunk in file_obj.chunks(): #读数据有个固定大小 默认64KB 最大2.5M .django的全局配置里面可以改chunks大小 f.write(chunk) # 64 * 2 ** 10 在base里