Django之锁,事物,Ajax

行级锁

select_for_update(nowait=False, skip_locked=False)

返回一个锁住行直到事务结束的查询集,如果数据库支持,它将生成一个 SELECT ... FOR UPDATE 语句。

举个例子:

entries = Entry.objects.select_for_update().filter(author=request.user)  #加互斥锁,由于mysql在查询时自动加的是共享锁,所以我们可以手动加上互斥锁。create、update、delete操作时,mysql自动加行级互斥锁

所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。

一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。 如果这不想要使查询阻塞的话,使用select_for_update(nowait=True)。 如果其它事务持有冲突的锁,互斥锁, 那么查询将引发 DatabaseError 异常。你也可以使用select_for_update(skip_locked=True)忽略锁定的行。nowait和skip_locked是互斥的,同时设置会导致ValueError。

目前,postgresql,oracle和mysql数据库后端支持select_for_update()。 但是,MySQL不支持nowait和skip_locked参数。

使用不支持这些选项的数据库后端(如MySQL)将nowait=True或skip_locked=True转换为select_for_update()将导致抛出DatabaseError异常,这可以防止代码意外终止。

表锁(了解)

class LockingManager(models.Manager):
    """ Add lock/unlock functionality to manager.

    Example::

        class Job(models.Model): #其实不用这么负载,直接在orm创建表的时候,给这个表定义一个lock和unlock方法,借助django提供的connection模块来发送锁表的原生sql语句和解锁的原生sql语句就可以了,不用外层的这个LckingManager(model.Manager)类

            manager = LockingManager()

            counter = models.IntegerField(null=True, default=0)

            @staticmethod
            def do_atomic_update(job_id)
                ''' Updates job integer, keeping it below 5 '''
                try:
                    # Ensure only one HTTP request can do this update at once.
                    Job.objects.lock()

                    job = Job.object.get(id=job_id)
                    # If we don't lock the tables two simultanous
                    # requests might both increase the counter
                    # going over 5
                    if job.counter < 5:
                        job.counter += 1                                        
                        job.save()

                finally:
                    Job.objects.unlock()


    """    

    def lock(self):
        """ Lock table. 

        Locks the object model table so that atomic update is possible.
        Simulatenous database access request pend until the lock is unlock()'ed.

        Note: If you need to lock multiple tables, you need to do lock them
        all in one SQL clause and this function is not enough. To avoid
        dead lock, all tables must be locked in the same order.

        See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
        """
        cursor = connection.cursor()
        table = self.model._meta.db_table
        logger.debug("Locking table %s" % table)
        cursor.execute("LOCK TABLES %s WRITE" % table)
        row = cursor.fetchone()
        return row

    def unlock(self):
        """ Unlock the table. """
        cursor = connection.cursor()
        table = self.model._meta.db_table
        cursor.execute("UNLOCK TABLES")
        row = cursor.fetchone()
        return row  
了解 

事物

全局开启

在Web应用中,常用的事务处理方式是将每个请求都包裹在一个事务中。这个功能使用起来非常简单,你只需要将它的配置项ATOMIC_REQUESTS设置为True。

它是这样工作的:当有请求过来时,Django会在调用视图方法前开启一个事务。如果请求却正确处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置开启严格sql模式


        }
        "ATOMIC_REQUESTS": True, #全局开启事务,绑定的是http请求响应整个过程
        "AUTOCOMMIT":False, #全局取消自动提交,慎用
    },
  'other':{
    'ENGINE': 'django.db.backends.mysql', 
            ......
  } #还可以配置其他数据库
}
setting中配置

上面这种方式是统一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。是全局性的配置, 如果要对某个http请求放水(然后自定义事务),可以用non_atomic_requests修饰器,那么他就不受事务的管控了

from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()
non_atomic_requests修饰器

局部使用事务(推荐使用)

atomic(using=None, savepoint=True)[source]  ,参数:using='other',就是当你操作其他数据库的时候,这个事务才生效,看上面我们的数据库配置,除了default,还有一个other,默认的是default。savepoint的意思是开启事务保存点,推荐数据库博客里面的事务部分关于保存点的解释。

原子性是数据库事务的一个属性。使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。

被atomic管理起来的代码块还可以内嵌到方法中。这样的话,即便内部代码块正常运行,如果外部代码块抛出异常的话,它也没有办法把它的修改提交到数据库中。

用法1:给函数做装饰器来使用

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

用法2:作为上下文管理器来使用,其实就是设置事务的保存点

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():   #保存点
        # This code executes inside a transaction.
        do_more_stuff()

    do_other_stuff()

一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了,比如下面这个例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

用法3:还可以嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等。

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
       #other_task()  #还要注意一点,如果你在事务里面写了别的操作,只有这些操作全部完成之后,事务才会commit,也就是说,如果你这个任务是查询上面更改的数据表里面的数据,那么看到的还是事务提交之前的数据。
    except IntegrityError:
        handle_exception()

    add_children()

 

Ajax

 

1.Ajax简介

AJAXAsynchronous Javascript And XML)翻译成中文就是异步的JavascriptXML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

      a.同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;

      b.异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

AJAX除了异步的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程

2.Ajax的使用

示例:

input标签post请求(csrf_token),ajax提交数据到后端,实现局部刷新

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>

{% csrf_token %}
用户名:<input type="text" name="username" id="username">
<input type="button" id="sub" value="提交">
<span class="error"></span>



<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

<script>
    $("#sub").click(function () {
        var username = $("#username").val();
        var csrf_data = $("[name=csrfmiddlewaretoken]").val();
        $.ajax({
            url: "{% url 'index' %}",
            type: "post",
            data:{
                uname:username,
                csrfmiddlewaretoken:csrf_data,
            },
            success: function (response) {
                if (response==="1"){
                    alert("登录成功")
                }else {
                    $(".error").text("输入的信息有误!")
                }
            }
        })
    })


</script>


</body>
</html>
login.html
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'^index/', views.index, name="index"),
]
urls.py
from django.shortcuts import render, HttpResponse, redirect

def login(request):
    return render(request, "login.html")
    # return HttpResponse("ok")


def index(request):
    username = request.POST.get("uname")
    print(username)
    static = "2"
    return HttpResponse(static)
View Code

启动django项目,然后运行看看效果,页面不刷新。

是没有刷新的,后端返回的是字符串2,判断的是字符串1,所以会提示“输入信息有误!”

3.上传文件

form表单上传文件

<form action="{% url 'upload' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="file" name="elephant">
    <input type="submit">
</form>
upload.html

 urls和之前配置相同

from django.shortcuts import render, HttpResponse, redirect
import os
from ajaxtest import settings

def upload(request):
    if request.method == "GET":
        return render(request, "upload.html")
    else:
        print(request.POST)
        file_obj = request.FILES.get('elephant')
        print(file_obj)
        file_path = os.path.join(settings.BASE_DIR, "statics", "img", file_obj.name)
        with open(file_path, "wb") as f:
            for chunk in file_obj.chunks():
                f.write(chunk)
        return HttpResponse("ok")
views.py

需要注意的是:

01.在form表单中需要加上 enctype="multipart/form-data"。这种form_data的格式一般是把大数据一段一段隔开的。

02.上传文件的input标签是type="file"。

03.需要拼接路径,os.path.join,并且需要引入settings文件。

04.读数据写数据,不要一下写进去,要一点一点写,

for data in file_obj: #读数据
f.write(data)  #每次读取的data不是固定长度的,和读取其他文件一样,每次读一行,识别符为\r\n\r\n,遇到这几个符号就算是读了一行
chunks()默认一次返回大小为经测试为65536B,也就是64KB,最大为2.5M,是一个生成器

Ajax文件上传

<h2>ajax上传</h2>
{% csrf_token %}
用户名:<input type="text" name="ajaxname">
密码:<input type="password" name="ajaxpwd">
头像:<input type="file" name="ajaxele">
<input type="button" value="上传" id="btn">


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $("#btn").click(function () {

        var formdata = new FormData();
        var username = $('[name=ajaxname]').val();
        var pwd = $('[name=ajaxpwd]').val();
        var file_obj = $('[name=ajaxele]')[0].files[0];
        var csrf_data = $('[name=csrfmiddlewaretoken]').val();
        formdata.append('username', username);
        formdata.append('pwd', pwd);
        formdata.append('file_obj', file_obj);
        formdata.append('csrfmiddlewaretoken', csrf_data);

        $.ajax({
            url: '{% url "upload" %}',
            type: 'post',
            data: formdata,
            processData: false,    // 不处理数据
            contentType: false,    // 不设置内容类型
            success: function (res) {
                console.log(res);
            }
        })

    })

</script>
upload.html
def upload(request):
    if request.method == "GET":
        return render(request, "upload.html")
    else:
        print(request.POST)
        file_obj = request.FILES.get('file_obj') # 和form表单唯一不一样的地方
        print(file_obj)
        file_path = os.path.join(settings.BASE_DIR, "statics", "img", file_obj.name)
        with open(file_path, "wb") as f:
            for chunk in file_obj.chunks():
                f.write(chunk)
        return HttpResponse("ok")
views.py

1. 给input标签的button按钮绑定点击事件。

2. var formdata=new FormData(); 因为ajax上传文件的时候,需要这个类型,它会将添加给它的键值对加工成formdata的类型

3. 添加键值的方法是append,注意写法,键和值之间是逗号

4. processData: false,和contentType: false,是必须要加上的。

4.设置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(pass)

$.ajaxSetup({
    data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

方式3

通过获取返回的cookie中的字符串 放置在请求头中发送。

注意:需要引入一个jquery.cookie.js插件。

下载与引入:jquery.cookie.js基于jquery;先引入jquery,再引入:jquery.cookie.js;下载:http://plugins.jquery.com/cookie/

导入cookie
<script src="{% static 'js/jquery.cookie.js' %}"></script>

$.ajax({
  url: "/cookie_ajax/",
  type: "POST",
  headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrftoken,并设置到请求头中
  data: {"username": "chao", "password": 123456},
  success: function (data) {
    console.log(data);
  }
})

使用$.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

5.请求头ContentType

ContentType指的是请求体的编码类型,常见的类型共有3种:

1 application/x-www-form-urlencoded(默认)

是最常见的 POST 提交数据的方式。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个。

 

这个是get请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="button" value="提交" id="btn">


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $("#btn").click(function () {
        $.ajax({
            url:{% url  "home1" %},
            type:"get",
            data:{
                username:$("[name=username]").val(),
                password:$('[name=password]').val()
            },
            headers:{
                "Content-type":'application/x-www-form-urlencoded'
            },
            success:function (res) {
                console.log(res)
            }
        })
    })
</script>

</body>
</html>
home.html
def home(request):
    return render(request, 'home.html')


def home1(request):
    username = request.GET.get("username")
    password = request.GET.get("password")
    print(username)
    print(password)

    return HttpResponse("ok")
views.py

post请求会显示出来(不需要手动添加)

2 multipart/form-data

也是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data,form表单不支持发json类型的contenttype格式的数据,而ajax什么格式都可以发,也是ajax应用广泛的一个原因。

enctype="multipart/form-data  告诉http,请求后端服务端的时候,发送的请求体里面的数据格式是multipart/form-data,这个格式发送的是片段数据

3 application/json

 application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。

由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

当请求头设置了application/json这种方式发送的时候,django没有内置的解析器,需要自己来解析

如何解析:request.body拿原始数据,自己反序列化,解析数据

当时get时无法打印出内容,get是放到url进行传输,url是有一定的编码格式,加了一层编码,所以看不到。

当时post请求时:会得到这样的数据:b'{"username":"123","password":"123"}',自己进行解析即可。

如果在ajax里面写上这个contenttype类型,那么data参数对应的数据,就不能是个object类型数据了,必须是json字符串,contenttype:'json',简写一个json,它也能识别是application/json类型

 

服务端接受到数据之后,通过contenttype类型的值来使用不同的方法解析数据,其实就是服务端框架已经写好了针对这几个类型的不同的解析数据的方法,通过contenttype值来找对应方法解析,django里面不能帮我们解析contenttype值为json的数据格式,能够解析application/x-www-form-urlencoded 和multipart/form-data(文件上传会用到),如果我们传json类型的话,需要我们自己来写一个解析数据的方法,其实不管是什么类型,我们都可以通过原始发送来的数据来进行加工处理。

 

 总结

 6.响应数据的方式

1.自己序列化数据

需要自己调用json,序列化传过去

  

2.使用HttpResponse指定请求头

js支持contenttype-json解析,当指定了content_type="application/json"时,前端拿到res就不需要自己parse反序列化。

 

3.使用JsonResponse

from django.http import JsonResponse

def home1(request):
    
    res = {"status": 0, "info": [11, 22, 33]}
    return JsonResponse(res)

字典格式都是可以的,但是如果是列表数据,就不行了。

解决方法:加上safe=False就可以了。

res = [11,22,33]
return JsonResponse(res,safe=False)

for循环取数组

 

 非字典类型的需要加safe=False

 json补充

什么是json

 JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)

 JSON 是轻量级的文本数据交换格式

 JSON 独立于语言 *

 JSON 具有自我描述性,更易理解

 JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言

 

 json数据类型和python数据类型的对比:

 

 object和python的dict类型是差不多的,但是要求里面必须是双引号,string和list、tuple等也是一样的,都是双引号。python中的datetime等时间日期类型是不能进行json序列化的,因为json没有对应的格式,上面的这几种数据类型虽然进行json.dumps序列化之后都是个字符串,但是也是有格式的

 举例说明:

import json
from datetime import datetime, date

a = {'name': 'tian', 'timer': datetime.now()}
a_json = json.dumps(a)
print(a_json)

会报错

 

改正:

json序列化时间类型的数据,直接引用这个类,dumps后面加cls后面加类名即可。

import json
from datetime import datetime, date

a = {'name': 'tian', 'timer': datetime.now()}


class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, field)


a_json = json.dumps(a, cls=JsonCustomEncoder)
print(a_json)

# {"name": "tian", "timer": "2019-05-30 21:56:57"}

Django内置的serializers做序列化(难用)

from django.core import serializers

# 用户序列化queryset类型
def home2(request):
    book_objs = models.Book.objects.all()
    res = serializers.serialize('json',book_objs)
    print(res)
    return JsonResponse(books,safe=False)

自己序列化

#建议这种方式来序列化我们的models数据

book_objs = models.Book.objects.all().values('title','price')
books = list(book_objs)
print(books)
return JsonResponse(res)
# 得出的结果就是完整的列表套字典

登录成功跳转网页

from django.shortcuts import render, HttpResponse, redirect
from django.urls import reverse
from ajaxtest import settings

def home1(request):

    res = {"status": 0, "data": {'name': 'chao'}, 'url': reverse("upload")}

    return JsonResponse(res)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="button" value="提交" id="btn">


<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
    $("#btn").click(function () {
        $.ajax({
            url:{% url  "home1" %},
            type:"post",
            data:JSON.stringify({
                username:$("[name=username]").val(),
                password:$('[name=password]').val()
            }),
            headers:{
                "Content-type":'application/json'
            },
            success:function (res) {
                console.log(res);
                //for (var i in res){
                //    console.log(res[i])
                //}
                {#var res1 = JSON.parse(res);#}
                {#console.log(res1.status);#}
                {#console.log(res1.info)#}
                var url = res.url;
                location.href = url
            }
        })
    })
</script>

</body>
</html>
home.html

home.html页面有点乱,注释的不用管。

主要是这个:location.href

 

posted @ 2019-05-29 16:37  blog_wu  阅读(507)  评论(0编辑  收藏  举报