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/')
- 示例代码二:基于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>
视图代码:
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})
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})
模版文件
<!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>
扩展内置分页:
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>
二、自定义分页
先介绍下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、请求在中间件中的方法请求过程
- 用户请求先中间件,由上到下先后执行每个中间件process_request方法;
- 执行完每个中间件的process_request方法之后,请求到达urls路由关系映射;
- 到达urls路由映射以后,由下至上执行每个中间件的process_views方法;
- 执行完process_views,请求到达views函数,此时若views函数有错误信息,则由下到上执行process_exception方法,直到错误被抓住停止,都没有抓住页面包错;
- 如果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分钟
局部使用
a. 引入TemplateTag {% load cache %} b. 使用缓存 {% cache 5000 缓存key %} 缓存内容 {% endcache %}
全站缓存
使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # 其他中间件... 'django.middleware.cache.FetchFromCacheMiddleware', ] CACHE_MIDDLEWARE_ALIAS = "" CACHE_MIDDLEWARE_SECONDS = "" CACHE_MIDDLEWARE_KEY_PREFIX = ""
七、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)