Django进阶

 内容简介:

  • cookie介绍
  • session介绍
  • 分页
  • CSRF
  • 中间件
  • 缓存
  • 信号
一、cookie介绍

 1、cookie机制

在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于同一个会话的,不能放入用户B或用户C的购物车内,这不属于同一个会话。

而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。

Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

2、cookie的工作原理

Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。

由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

3、django中使用cookie

  •  获取cookie
说明:request.COOKIES #代表客户端发送的全部cookie,以键值对方式存储,可以理解为一个字典

request.COOKIES['key'] #key存在则获取,不存在则报错,不建议使用 request.COOKIES.get('key') #获取cookie,不存在返回None,建议使用
  • 设置cookie
#创建响应对象

response=render(request,'index.html')
response=redirect('index')
#设置cookie

response.set_cookie('key',value) #默认关闭浏览器就失效
response.set_signed_cookie(key,value,salt='加密盐',...)#设置带签名的cookie

其他参数:
        key,              键
        value='',         值
        max_age=None,     cookie失效时间,单位秒
        expires=None,     cookie失效时间戳(IE requires expires, so set it if hasn't been already.),参数datetime对象
        path='/',         Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
        domain=None,      Cookie生效的域名
        secure=False,     https传输
        httponly=False    只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖
  • 带签名的cookie:设置时候加签,cookie内容是加密的
#设置带签名的cookie
response.set_signed_cookie('username','wd',salt='adada')

#从请求中获取cookie,salt参数要一致
 request.get_signed_cookie('username',salt='adada')

 

 

  • 使用javascript操作cookie,前提不能设置httponly=True(使用js,需要下载引人jquery.cookie.js插件)

 

  • 示例代码一:使用cookies做用户认证
def index(request):
    if request.method=="GET":
        v=request.COOKIES.get('username')   #获取当前用户
        if not v:
            return redirect('/app01/login/')
        else:
            return render(request,'index.html',{'current_user':v})

def login(request):
    if request.method=="GET":
        return render(request,'login.html')
    if request.method=="POST":
        u=request.POST.get('user')
        p=request.POST.get('pwd')
        print(u,p)
        if u=='admin' and p=='admin':
            response=redirect('/app01/index/')
            #response.set_cookie('username',u,max_age=60*60)#设置一个小时以后cookie失效
            import datetime
            current_date=datetime.datetime.now()
            expre_date=current_date+datetime.timedelta(seconds=5)
            response.set_cookie('username', u, expires=expre_date)  # 设置5秒后cookie过期
            return response
        else:
            return redirect('/app01/login/')
demo
  • 示例代码二:基于Cookie实现定制显示数据条数(包含juery操作cookie)
    
    

模版代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .pg-a{
            padding: 5px;
            margin: 5px;
            background-color: aqua;
        }
        .alive{
            background-color: red;
        }
    </style>
</head>
<body>
<ul>
    {% for row in user_info %}
    <li>{{ row }}</li>
    {% endfor %}
</ul>
<div>
    {{ page_str }}
</div>
<p>
    选择每页显示条目数:
    <select id="choose_item">
        <option value="10" >10</option>
        <option value="30" >30</option>
        <option value="50">50</option>
        <option value="100">100</option>
    </select>
</p>
<script src="/static/jquery-2.0.3.js"></script>
<script src="/static/jquery.cookie.js"></script>
<script>
    $(function () {  
        var v=$.cookie('page_count');  //获取当前选择的值
        $('#choose_item').val(v)    //框架加载完成将选项改为当前选项
    });
    $('#choose_item').change(function () {
       var v=$(this).val();
        $.cookie('page_count',v); //设置cookie
        location.reload()

    })
</script>
</body>
</html>
View Code

视图代码:

def detail(request):
    from .utils import page
    if request.method=="GET":
        current_index= int(request.GET.get('p', 1))  # 第二个参数代表获取不到默认设置为1
        page_num=request.COOKIES.get('page_count')
        print(page_num)
        if page_num.isdigit():
            page_num=int(page_num)
            mypage = page.PagiNation(current_index=current_index,data_num=500,page_num=page_num)
            page_str=mypage.page_str('/app01/detail')
            data =userlist[mypage.start:mypage.end]
            return render(request,'userlist.html',{'user_info':data,'page_str':page_str})
View Code

 

4、基于cookie的登录验证装饰器 

基础篇中的django视图提到了,对于view函数中我们可以定义两种视图,一种是函数(FBV),一种是类(CBV)

所以对于认证的装饰器也有两种:

  • FBV
#定义装饰器
def auth(func):
    def inner(reqeust,*args,**kwargs):
        v = reqeust.COOKIES.get('username')
        if not v:
            return redirect('/login/')
        return func(reqeust, *args,**kwargs)
    return inner

#装饰需要登录验证的函数
@auth      
def index(reqeust):return render(reqeust,'index.html',{'current_user': v})
  • CBV
#定义装饰器
def auth(func):
    def inner(reqeust,*args,**kwargs):
        v = reqeust.COOKIES.get('username')
        if not v:
            return redirect('/login/')
        return func(reqeust, *args,**kwargs)
    return inner


#使用装饰器,方法有三种

from django import views
from django.utils.decorators import method_decorator

@method_decorator(auth,name='dispatch')  
# 第三种方法,name="dispatch",就是给dispatch方法加上此装饰器,作用在于在执行post、get方法之前就验证,避免在给post和get方法加装饰器
class Order(views.View):

  # @method_decorator(auth)  # 第二种方法,作用等同于等三种
  # def dispatch(self, request, *args, **kwargs):
  #    return super(Order,self).dispatch(request, *args, **kwargs)

  # @method_decorator(auth)  # 第一种方法,作用可以用针对性,可以只给get方法加装饰器
  def get(self,reqeust):return render(reqeust,'index.html',{'current_user': v})

  def post(self,reqeust):return render(reqeust,'index.html',{'current_user': v})

 

二、session介绍

 1、session机制

除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力

2、session工作原理

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了,实质上session就是保存在服务器端的键值对。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

 3、Django中使用session

  • 基本操作
#获取session值
request.session['key']  #存在则获取,不存在则包错,不建议使用
request.session['key']  #存在则获取,不存在返回None,建议使用


#设置session
request.session['k1']='wd'
request.session.setdefault('key','value')   #存在则不设置

#删除某个session键值
del request.session['key']

#循环session中的字典,与字典循环类似
request.session.keys() #所有的key
request.session.values() #所有的value
request.session.items() #k,v形式
request.session.iterkeys() #所有的key
request.session.itervalues() #所有的value
request.session.iteritems() #k,v形式

#获取用户session中的随机字符串
request.session.session_key

#删除所有session失效日期小于当前日志的数据(删除同一用户制造的脏数据)
request.session.clear_expired()

#删除当前用户的所有session数据(在用户退出登录时候使用)
request.session.delete('用户随机字符串')#使用比较麻烦,因为还的获取用户随机字符串
request.session.clear()#该方法会先获取用户的随机字符串,然后把其对应的所有数据删除,推荐注销(退出登录)时候使用
#设置seesion失效时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略,默认全局两周失效

 4、Django中session相关配置

django默认session存储位置在数据库表中,表名为django_session,当然django还提供了多样化存储session,有以下几种:

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存+数据库
  • 加密cookie

1、数据库Session配置

a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
     
    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认),最好设置为True,这样超时时间都是最新的

2、缓存session(memchache)配置

a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 
 
    SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存

3、文件session配置

a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    SESSION_FILE_PATH = None         # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
 

4、数据库+缓存session配置

4、缓存+数据库Session
数据库用于做持久化,缓存用于提高效率
 
a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5、加密cookie session配置

#加密cookie Session
settings.py
     
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

 

5、基于session的用户验证装饰器

def login(func):
    def wrap(request, *args, **kwargs):
        # 如果未登陆,跳转到指定页面
        if not request.session.get('is_login'):
            return redirect('/login/')
        return func(request, *args, **kwargs)
    return wrap

 

 6、session和cookie区别

1.作用

当你要登录京东和天猫的时候,当登录成功的时候。我点击其他功能例如购物车 订单等功能的时候
是如何判断你已经登录的呢。那就是用的cookie或者session功能。

2.区别

cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

3.session流程

1、自动生成一段字符串

2、将字符串发送到客户端的浏览器,同时把字符串当做key放在session里。(可以理解为session就是一个字典)

3、在用户的session对应的value里设置任意值

 

三、分页

一、Django自带的分页

视图:

from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

L = []
for i in range(999):
    L.append(i)

def index(request):
    current_page = request.GET.get('p')

    paginator = Paginator(L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'index.html', {'posts': posts})
views.py

模版文件

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<ul>
    {% for item in posts %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<div class="pagination">
      <span class="step-links">
        {% if posts.has_previous %}
            <a href="?p={{ posts.previous_page_number }}">Previous</a>
        {% endif %}
          <span class="current">
            Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
          </span>
          {% if posts.has_next %}
              <a href="?p={{ posts.next_page_number }}">Next</a>
          {% endif %}
      </span>

</div>
</body>
</html>
html模版

扩展内置分页:

from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger


class CustomPaginator(Paginator):
    def __init__(self, current_page, max_pager_num, *args, **kwargs):
        """
        :param current_page: 当前页
        :param max_pager_num:最多显示的页码个数
        :param args:
        :param kwargs:
        :return:
        """
        self.current_page = int(current_page)
        self.max_pager_num = max_pager_num
        super(CustomPaginator, self).__init__(*args, **kwargs)

    def page_num_range(self):
        # 当前页面
        # self.current_page
        # 总页数
        # self.num_pages
        # 最多显示的页码个数
        # self.max_pager_num
        print(1)
        if self.num_pages < self.max_pager_num:
            return range(1, self.num_pages + 1)
        print(2)
        part = int(self.max_pager_num / 2)
        if self.current_page - part < 1:
            return range(1, self.max_pager_num + 1)
        print(3)
        if self.current_page + part > self.num_pages:
            return range(self.num_pages + 1 - self.max_pager_num, self.num_pages + 1)
        print(4)
        return range(self.current_page - part, self.current_page + part + 1)


L = []
for i in range(999):
    L.append(i)

def index(request):
    current_page = request.GET.get('p')
    paginator = CustomPaginator(current_page, 11, L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request, 'index.html', {'posts': posts})
扩展分页
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

<ul>
    {% for item in posts %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
    <a href="?p={{ posts.previous_page_number }}">Previous</a>
{% endif %}

    {% for i in posts.paginator.page_num_range %}
        <a href="?p={{ i }}">{{ i }}</a>
    {% endfor %}

    {% if posts.has_next %}
        <a href="?p={{ posts.next_page_number }}">Next</a>
    {% endif %}
</span>

<span class="current">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>

</div>
</body>
</html>
扩展分页html模版

 

二、自定义分页

先介绍下XSS(跨站脚本攻击):

跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

原因:我们在自定义分页时候会额外的给页面添加html代码,默认Django会认为这些代码是不安全的属于XSS攻击,所以会当作字符串处理不会作为html代码显示,所以我们需要告诉Django这个代码是安全的。

方法有两种:

1.在html模板中方法:

{{ html_str|safe }}

2.在后台函数中使用

from django.utils.safestring import mark_safe
html_str=mark_safe(html_str)

 3.自定义分页(可以作为一个工具类,作为公共模块使用)

#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#Author:wd
from django.utils.safestring import mark_safe
import math
class PagiNation(object):
    def __init__(self,current_index,data_num,page_num=10,show_index_num=10):
        '''
        :param current_index: 当前显示的页码
        :param data_num: 显示的内容的总条目数
        :param page_num: 每页显示的内容的条目数
        :param show_index_num: 每一页显示的页码数目
        '''
        self.current_index=current_index
        self.data_num=data_num
        self.page_num=page_num
        self.show_index_num=show_index_num
    @property
    def start(self):  #获取显示的页码起始位置
        return (self.current_index - 1) * self.page_num
    @property
    def end(self):    #获取显示页码的终止位置
        return self.current_index * self.page_num
    @property
    def total_index(self): #获取显示页码的总页码数量
        return math.ceil(self.data_num / self.page_num)

    def page_str(self,base_url):   #获取最后展现的a标签字符串
        '''
        :param base_url: 跳转链接地址
        :return:        返回django认可的html代码
        '''
        page_list=[]
        half_index = int(self.show_index_num/2)  # 显示页码数的一半
        if self.total_index < self.show_index_num:  # 起始页码小于显示的页码数目,结束页码则为总显示页数
            start_index = 1
            end_index = self.total_index+1
        else:
            if self.current_index < half_index or self.current_index == half_index:#当前页码小于或等于显示页码数量的一半
                start_index = 1
                end_index = self.show_index_num + 1
            else:
                start_index = self.current_index - half_index + 1

                if self.current_index + half_index > self.total_index:#当前页码+显示页码数的一半大于总的页码数目
                    start_index = self.total_index - self.show_index_num + 1
                    end_index = self.total_index + 1
                else:
                    end_index = self.current_index + half_index + 1

        if self.current_index == 1:
            page_list.append('<a class="pg-a" href="javascript:void(0);">上一页</a>')#javascript:void(0)意思是不错任何操作
        else:
            page_list.append('<a href=%s?p=%s class="pg-a alive">上一页</a>' % (base_url, self.current_index - 1,))
        for i in range(start_index, end_index):
            if i == self.current_index:
                page_url = '<a href=%s?p=%s class="pg-a alive">%s</a>' % (base_url, i, i)
            else:
                page_url = '<a href=%s?p=%s class="pg-a ">%s</a>' % (base_url, i, i)
            page_list.append(page_url)
        if self.current_index == self.total_index:
            page_list.append('<a class="pg-a" href="javascript:void(0);">下一页</a>')
        else:
            page_list.append('<a href=%s?p=%s class="pg-a alive">下一页</a>' % (base_url, self.current_index + 1,))
        page_str = mark_safe("".join(page_list))
        return page_str
分页

4.使用示例

def detail(request):
    from .utils import page
    if request.method=="GET":
        current_index= int(request.GET.get('p', 1))  # 第二个参数代表获取不到默认设置为1
        mypage = page.PagiNation(current_index=current_index,data_num=500)
        page_str=mypage.page_str('/app01/detail')
        return render(request,'userlist.html',{'user_info':data,'page_str':page_str})    

 

四、跨站请求伪造(CSRF)

 1、简介:

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

 

2、Django中防护CSRF模块

Django中的CSRF防护是通过中间件的手段来达到防护的目的,中间件路径:django.middleware.csrf.CsrfViewMiddleware ,配置文件settings中指定了全局配置,单也可以单独针对某个views函数来配置,具体配置:

全局:

中间件 django.middleware.csrf.CsrfViewMiddleware

局部:

@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。

@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

TIPS:配置模块导入from django.views.decorators.csrf import csrf_exempt,csrf_protect

 3、原理

当我们向Django后台post数据时,在Django中防护CSRF是通过获取用户请求头中是否含有X-CSRFtoken请求头所对应的随机字符串(),如果有并且是正确的,则可以提交,否则则返回Forbidden(403)错误。

4、提交数据

form提交数据:

form表单提交数据:在form表单中加上{% csrf_token %},默认会在表单中生成隐藏的input标签

提交过程:

用户访问页面,{% csrf_token %}模版语言生成CSRF随机随机字符串,并且cookie中也生成了该字符串,当用户提交数据时候cookie中会带着这个字符串进行提交,如果没有该字符串则提交失败

 ajax提交数据:

通过ajax提交数据时候,和form表单一样,我们提交数据时需要在请求头中加上X-CSRFtoken,所以提交的时候需要利用js获取cookie中的该随机字符串进行提交:

$("#btn").click(function () {
        $.ajax({
            url:"/login/",
            type:"POST",
            data:{"user":"wd","pwd":"1234"},
            headers:{ "X-CSRFtoken":$.cookie("csrftoken")},
            success:function (arg) {

            }
        })
    })

但是如果页面中有多个ajax请求的话就在每个ajax中添加headers信息,所以可以在页面框架加载完时候通过以下方式提交,这样会在页面中所有ajax提交数据之前加上csrftoken信息

    $.ajaxSetup({
            beforeSend:function (xhr,settings) {    #xhr是XMLHttpRequest
                xhr.setRequestHeader("X-CSRFtoken",$.cookie("csrftoken"))
            }
        });

最后,一般我们只希望post提交数据时候才会使用csrf验证,所以官网推荐

function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));//test是正则表达中的方法,返回true或者flase
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });

5、Django后台验证过程

csrf在前端的key为X-CSRFtoken,通过form或者ajax提交到后台,后端的django会自动添加HTTP_,并且最后为的key变成HTTP_X_CSRFtoken,如果随机字符串通过验证则数据提交,否则报错(403)。

五、Django中间件

 1、简介

django中的中间件,在其他web框架中,有的叫管道或者httphandle,Django中很多功能都是通过中间件实现的,实际上中间件就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法。在工程中的settings.py中由变量MIDDLEWARE控制,请求从上到下依次穿过中间件,每个中间件中都可以有5个方法:

  • process_request(self,request)#请求最开始执行的方法
  • process_response(self, request, response)#请求返回时候执行的方法
  • process_view(self, request, callback, callback_args, callback_kwargs)#请求到达views视图之前执行的方法
  • process_exception(self, request, exception)  # 默认不执行,除非Views方法出错
  • process_template_response(self,request,response)   # 默认不执行,除非Views中的函数返回的对象中,具有render方法(区别于模板渲染的render)

2、请求在中间件中的方法请求过程

  1. 用户请求先中间件,由上到下先后执行每个中间件process_request方法;
  2. 执行完每个中间件的process_request方法之后,请求到达urls路由关系映射;
  3. 到达urls路由映射以后,由下至上执行每个中间件的process_views方法;
  4. 执行完process_views,请求到达views函数,此时若views函数有错误信息,则由下到上执行process_exception方法,直到错误被抓住停止,都没有抓住页面包错;
  5. 如果views函数中没有出错,那么请求由下到上执行每个中间件的process_response方法,最后响应客户;

大致的请求过程:

 3、自定义中间件

自定义中间件,我们至少包括两个方法,一个是process_request,还有一个是process_response,创建一个py文件,在工程中创建MyMiddleWare目录(与tempates同级),创建文件md.py,注意使用process_response必须返回response:

#AUTHOR:FAN
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

#中间件1
class Row1(MiddlewareMixin):
    def process_request(self,request):
        print("中间件1请求")
    def process_response(self,request,response):
        print("中间件1返回")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("中间件1view")


#中间件2
class Row2(MiddlewareMixin):
    def process_request(self,request):
        print("中间件2请求")
        # return HttpResponse("走")
    def process_response(self,request,response):
        print("中间件2返回")
        return response
    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("中间件2view")

#中间件3
class Row3(MiddlewareMixin):
    def process_request(self,request):
        print("中间件3请求")
    def process_response(self,request,response):
        print("中间件3返回")
        return response
    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("中间件3view")
    def process_exception(self, request, exception):
        """只有出现异常时才会执行"""
        if isinstance(exception, ValueError):
            return HttpResponse('出现异常》。。')
        elif isinstance(exception, TemplateDoesNotExist):
            print(request.path_info)
            return render(request, "404.html")

    def process_template_response(self, request, response):
        """如果Views中的函数返回的对象中,具有render方法才会执行"""
        print('-----------------------')
        return response

 

注册中间件:使用中间件需要在settings.py中注册你的中间件,配置就是你的中间件文件路径

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',
    'MyMiddleWare.md.Row1',
    'MyMiddleWare.md.Row2',
    'MyMiddleWare.md.Row3',
]

4、版本变化

上面所说的Django中间件请求过程是在1.10版本之后(包括1.10),而在1.10版本之前稍微有些区别,区别就在请求异常时候,1.10以及以后的版本直接使用当前类中的proess_reponse方法返回给用户请求,而在1.10以前的版本请求返回是从最底部的中间件的process_response方法向上返回

六、缓存

 简介

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者memchazhe中之前缓存的内容拿到,并返回。

Django提供了6种缓存方式:

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

缓存方式配置

 通用配置

# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',     # 引擎
                'TIMEOUT': 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                'OPTIONS':{
                    'MAX_ENTRIES': 300,                                       # 最大缓存个数(默认300)
                    'CULL_FREQUENCY': 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
                'KEY_PREFIX': '',                                             # 缓存key的前缀(默认空)
                'VERSION': 1,                                                 # 缓存key的版本(默认1)
                'KEY_FUNCTION' 函数名                                          # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
            }
        }


    # 自定义key
    def default_key_func(key, key_prefix, version):
        """
        Default function to generate keys.

        Constructs the key used by all other methods. By default it prepends
        the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
        function with custom key making behavior.
        """
        return '%s:%s:%s' % (key_prefix, version, key)

    def get_key_func(key_func):
        """
        Function to decide which key function to use.

        Defaults to ``default_key_func``.
        """
        if key_func is not None:
            if callable(key_func):
                return key_func
            else:
                return import_string(key_func)
        return default_key_func

使用内存

# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                'LOCATION': 'unique-snowflake',
            }
        }

    # 注:其他配置同开发调试版本

使用文件

# 此缓存将内容保存至文件
    # 配置:

        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/var/tmp/django_cache',
            }
        }
    # 注:其他配置同开发调试版本

使用数据库

# 此缓存将内容保存至数据库

    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
                'LOCATION': 'my_cache_table', # 数据库表
            }
        }

    # 注:执行创建表命令 python manage.py createcachetable

使用memcache缓存(python-memcached模块)

# 此缓存使用python-memcached模块连接memcache

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': 'unix:/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }

使用Memcache缓存(pylibmc模块)

# 此缓存使用pylibmc模块连接memcache

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }

 

缓存应用

 单独视图缓存

方式一:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
            ...



方式二:
        from django.views.decorators.cache import cache_page

        urlpatterns = [
            url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
        ]

即通过装饰器的方式实现,导入模块之后,在需要缓存的函数前加@cache_page(60 * 15) 60*15表示缓存时间是15分钟
View Code

局部使用

a. 引入TemplateTag

        {% load cache %}

b. 使用缓存

        {% cache 5000 缓存key %}
            缓存内容
        {% endcache %}
View Code

全站缓存

使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

    MIDDLEWARE = [
        'django.middleware.cache.UpdateCacheMiddleware',
        # 其他中间件...
        'django.middleware.cache.FetchFromCacheMiddleware',
    ]

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = ""
    CACHE_MIDDLEWARE_KEY_PREFIX = ""
View Code

 

七、Django中的信号

简介

Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。

内置信号

Model signals
    pre_init                    # django的modal执行其构造方法前,自动触发
    post_init                   # django的modal执行其构造方法后,自动触发
    pre_save                    # django的modal对象保存前,自动触发
    post_save                   # django的modal对象保存后,自动触发
    pre_delete                  # django的modal对象删除前,自动触发
    post_delete                 # django的modal对象删除后,自动触发
    m2m_changed                 # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发
Request/response signals
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发
Test signals
    setting_changed             # 使用test测试修改配置文件时,自动触发
    template_rendered           # 使用test测试渲染模板时,自动触发
Database Wrappers
    connection_created          # 创建数据库连接时,自动触发

因为这些信号中并没有注册函数,所以运行时并没有调用触发这些信号

对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数

from django.core.signals import request_finished  # 请求结束后
from django.core.signals import request_started  # 请求到来前
from django.core.signals import got_request_exception  # 请求异常后

from django.db.models.signals import class_prepared  # 程序启动时,检测已注册的app中的modal类,对于每一个类,自动触发
from django.db.models.signals import pre_init, post_init  # 构造方法前和构造方法后
from django.db.models.signals import pre_save, post_save  # 对象保存前和对象保存后
from django.db.models.signals import pre_delete, post_delete  # 对象删除前和对象删除后
from django.db.models.signals import m2m_changed  # 操作第三张表前后
from django.db.models.signals import pre_migrate, post_migrate  # 执行migrate命令前后

from django.test.signals import setting_changed  # 使用test测试修改配置文件时
from django.test.signals import template_rendered  # 使用test测试渲染模板时

from django.db.backends.signals import connection_created  # 创建数据库连接时


def callback(sender, **kwargs):
    print("xxoo_callback")
    print(sender,kwargs)

xxoo.connect(callback)
# xxoo指上述导入的内容


#示例
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

########################说明####################
#这里的xxoo代指上面导入的信号,如request_finished,request_started,request_started等,而callback就是你要注册的函数
#如果我们把导入信号以及将注册函数都写到一个单独的文件里,为了在程序启动的时候执行信号中的注册函数,可以在于项目同名的文件中的init文件中导入该文件即可,与使用pymysql一样

自定义信号

步骤:

  • 定义信号
  • 触发信号
  • 注册信号

示例:

tips:由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。

#定义信号
import django.dispatch
pizza_done=django.dispatch.Signal(providing_args=["toppings", "size"])

#注册信号
def callback(sender, **kwargs):

    print("callback")

    print(sender,kwargs)
pizza_done.connect(callback)


#触发信号
from 路径 import pizza_done
 
pizza_done.send(sender='seven',toppings=123, size=456)

 

posted @ 2017-10-10 11:42  W-D  阅读(703)  评论(0编辑  收藏  举报