zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    emberjs初学记要
    自我的一点介绍(七夕礼物)
    JavaScript数据类型
    Vue+Webpack项目配置
    Git知识点整合
    Log4j简单配置解析
    如何明智地向程序员提问
    Navicat连接mysql报错1251
    多表查询sql语句
    PLSQL面向对象
  • 原文地址:https://www.cnblogs.com/wdliu/p/7644792.html
Copyright © 2011-2022 走看看