Django models操作、中间件、缓存、信号、分页
什么是中间件?中间件应用场景?和装饰器区别?
中间件
对于所有批量的请求用中间件合适,而对于某些请求个数少用装饰器合适
- 中间件是什么?
- 是一个类
- 方法名必须是process_request/process_response,否则无效
- 返回值注意:
# 无返回值:继续执行后续中间件和视图函数
# 有返回值:执行自己的process_response和上面的response
- 做过什么:
- 用户登录验证 - csrf
- session
- 权限管理***
下边的图就是一次完整的django生命周期,从客户端输入url,经过wsgi模块处理,得到符合HTTP协议的字符串,走中间件,假如中间件return None,继续往下走到urls......;
假如中间件return Httpresponse/render/redirect ...直接返回给用户,如果是httpresponse则展示给用户,其它两个的话继续走中间件:
在http请求 到达视图函数之前 和视图函数return之后,django会根据自己的规则在合适的时机执行中间件中相应的方法。
中间件的执行流程
1、执行完所有的request方法 到达视图函数。
2、执行中间件的其他方法
3、经过所有response方法 返回客户端。
注意:如果在其中1个中间件里 request方法里 return了值,就会执行当前中间件的response方法,返回给用户 然后 报错。。不会再执行下一个中间件。
1.在project下随便创建一个py文件
from django.utils.deprecation import MiddlewareMixin
class Middle1(MiddlewareMixin):
def process_request(self,request):
print("来了")
def process_response(self, request,response):
print('走了')
2、在setings文件中 注册这个 py文件
django项目的settings模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件
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',
'M1.Middle1',
]
执行结果
为啥报错了呢?
因为 自定义的中间件response方法没有return,交给下一个中间件,导致http请求中断了!!!
注意 自定义的中间件request 方法不要return 因为返回值中间件不再往下执行,导致 http请求到达不了视图层,因为request在视图之前执行!
from django.utils.deprecation import MiddlewareMixin class Middle1(MiddlewareMixin): def process_request(self,request): print("来了") #不用return Django内部自动帮我们传递 def process_response(self, request,response): print('走了') return response #执行完了这个中间件一定要 传递给下一个中间件
中间件(类)中5种方法
中间件中可以定义5个方法,分别是:
- process_request(self,request)
- process_view(self, request, callback, callback_args, callback_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response
1、 process_view(self, request, callback, callback_args, callback_kwargs)方法介绍
(1)执行完所有中间件的request方法‘
(2)url匹配成功
(3)拿到 视图函数的名称、参数,(注意不执行) 再执行process_view()方法
(4)最后去执行视图函数
玩法1(常规)
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
def process_response(self, request, response):
print('M1.response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
执行结果:
玩法2
既然 process_view 拿到视图函数的名称、参数,(不执行) 再执行process_view()方法,最后才去执行视图函数!
那可以在 执行process_view环节直接 把函数执行返回吗?
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
# callback视图函数名称 callback_args,callback_kwargs 视图函数执行所需的参数
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
response=callback(request,*callback_args,**callback_kwargs)
return response
def process_response(self, request, response):
print('M1.response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
执行结果:
结论:
如果process_view函数有返回值,跳转到最后一个中间件, 执行最后一个中间件的response方法,逐步返回。
和 process_request方法不一样哦! request方法在当前中间件的response方法返回。
2、process_exception(self, request, exception)方法
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
def process_response(self, request, response):
print('M1.response')
return response
def process_exception(self, request,exception):
print('M1的process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
def process_exception(self, request, exception):
print('M2的process_exception')
我去 加了process_exception方法 咋啥也没执行呢?!!原来是process_exception默认不执行!!!
原来process_exception方法在 视图函数执行出错的时候才会执行
M1.request
M2.request
M1.process_view
M2.process_view
执行index
M2的process_exception
M1的process_exception
Internal Server Error: /index/
Traceback (most recent call last):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\exception.py", line 41, in inner
response = get_response(request)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\site-packages\django\core\handlers\base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "F:\untitled1\app01\views.py", line 7, in index
int("ok")
ValueError: invalid literal for int() with base 10: 'ok'
M2.response
M1.response
[03/Jul/2017 16:43:59] "GET /index/ HTTP/1.1" 500 62663
1、执行完所有 request 方法
2、执行 所有 process_view方法
3、如果视图函数出错,执行process_exception(最终response,process_exception的return值)
如果process_exception 方法有了 返回值 就不再执行 其他中间件的 process_exception,直接执行response方法响应
4.执行所有response方法
5.最后返回process_exception的返回值
M1.request
M2.request
M1.process_view
M2.process_view
执行index
M2的process_exception (有了return值,直接执行response)
M2.response
M1.response
process_exception的应用
在视图函数执行出错时,返回错误信息。这样页面就不会 报错了!
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
def process_response(self, request, response):
print('M1.response')
return response
def process_exception(self, request,exception):
print('M1的process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
def process_exception(self, request, exception):
print('M2的process_exception')
return HttpResponse('出错了兄弟!!!')
3、process_template_response()
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
def process_response(self, request, response):
print('M1.response')
return response
def process_exception(self, request,exception):
print('M1的process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
def process_exception(self, request, exception):
print('M2的process_exception')
def process_template_response(self,request,response):
print('M2process_template_response')
return response
process_template_response()默认不执行
rocess_template_response()特性
只有在视图函数的返回对象中有render方法才会执行!
并把对象的render方法的返回值返回给用户(注意不返回视图函数的return的结果了,而是返回视图函数 return值(对象)的render方法)
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
def process_request(self, request):
print('M1.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M1.process_view")
def process_response(self, request, response):
print('M1.response')
return response
def process_exception(self, request,exception):
print('M1的process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('M2.request')
def process_view(self, request,callback,callback_args,callback_kwargs ):
print("M2.process_view")
def process_response(self, request, response):
print('M2.response')
return response
def process_exception(self, request, exception):
print('M2的process_exception')
def process_template_response(self,request,response): #如果视图函数中的返回值 中有render方法,才会执行 process_template_response
print('M2process_template_response')
return response
视图函数
from django.shortcuts import render,HttpResponse
# Create your views here.
class Foo():
def __init__(self,requ):
self.req=requ
def render(self):
return HttpResponse('OKKKK')
def index(request):
print("执行index")
obj=Foo(request)
return obj
执行结果
应用:
既然process_template_respnse,不返回视图函数的return的结果,而是返回视图函数 return值(对象)的render方法;(多加了一个环节)
就可以在 这个视图函数返回对象的 render方法里,做返回值的二次加工了!多加工几个,视图函数就可以随便使用了!
(好比 喷雾器有了多个喷头,换不同的喷头喷出不同水,返回值就可以也组件化了)
from django.shortcuts import render,HttpResponse
# Create your views here.
class Dict(): #对视图函数返回值做二次封装 !!
def __init__(self,requ,msg):
self.req=requ
self.msg=msg
def render(self):
a=self.msg #在render方法里面 把视图函数的 返回值 制作成字典 、列表等。。。
# 如果新增了其他 一个视图函数直接,return对象 即可!不用每个视图函数都写 制作字典 列表 拼接的逻辑了
return HttpResponse(a) #
def index(request):
print("执行index")
obj=Dict(request,"vv")
return obj
中间件应用场景
由于中间件工作在 视图函数执行前、执行后(像不像所有视图函数的装饰器!)适合所有的请求/一部分请求做批量处理
1、做IP限制
放在 中间件类的列表中,阻止某些IP访问了;
2、URL访问过滤
如果用户访问的是login视图(放过)
如果访问其他视图(需要检测是不是有session已经有了放行,没有返回login),这样就省得在 多个视图函数上写装饰器了!
3、缓存(还记得CDN吗?)
客户端请求来了,中间件去缓存看看有没有数据,有直接返回给用户,没有再去逻辑层 执行视图函数
csrf:
- 每次初始化一个项目时都能看到 django.middleware.csrf.CsrfViewMiddleware 这个中间件
- 每次在模板里写 form 时都知道要加一个 {% csrf_token %} tag
- 每次发 ajax POST 请求,都需要加一个 X_CSRFTOKEN 的 header
什么是 CSRF
CSRF, Cross Site Request Forgery, 跨站点伪造请求。举例来讲,某个恶意的网站上有一个指向你的网站的链接,如果
某个用户已经登录到你的网站上了,那么当这个用户点击这个恶意网站上的那个链接时,就会向你的网站发来一个请求,
你的网站会以为这个请求是用户自己发来的,其实呢,这个请求是那个恶意网站伪造的。
Django 提供的 CSRF 防护机制
django 第一次响应来自某个客户端的请求时,会在服务器端随机生成一个 token,把这个 token 放在 cookie 里。然后每次 POST 请求都会带上这个 token,
这样就能避免被 CSRF 攻击。
- 在返回的 HTTP 响应的 cookie 里,django 会为你添加一个 csrftoken 字段,其值为一个自动生成的 token
- 在所有的 POST 表单时,必须包含一个 csrfmiddlewaretoken 字段 (只需要在模板里加一个 tag, django 就会自动帮你生成,见下面)
- 在处理 POST 请求之前,django 会验证这个请求的 cookie 里的 csrftoken 字段的值和提交的表单里的 csrfmiddlewaretoken 字段的值是否一样。如果一样,则表明这是一个合法的请求,否则,这个请求可能是来自于别人的 csrf 攻击,返回 403 Forbidden.
- 在所有 ajax POST 请求里,添加一个 X-CSRFTOKEN header,其值为 cookie 里的 csrftoken 的值
Django 里如何使用 CSRF 防护
- 首先,最基本的原则是:GET 请求不要用有副作用。也就是说任何处理 GET 请求的代码对资源的访问都一定要是“只读“的。
- 要启用 django.middleware.csrf.CsrfViewMiddleware 这个中间件
- 再次,在所有的 POST 表单元素时,需要加上一个 {% csrf_token %} tag
- 在渲染模块时,使用 RequestContext。RequestContext 会处理 csrf_token 这个 tag, 从而自动为表单添加一个名为 csrfmiddlewaretoken 的 input
缓存
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
信号
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
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
from django.test.signals import setting_changed
from django.test.signals import template_rendered
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!")
自定义信号
a.定义信号
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
b.注册信号
def callback(sender, **kwargs):
print("callback")
print(sender,kwargs)
pizza_done.connect(callback)
c.触发信号
from 路径 import pizza_done
pizza_done.send(sender='seven',toppings=123, size=456)
由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发
分页
一、Django内置分页
二、自定义分页
分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
4、在数据表中根据起始位置取值,页面上输出数据
需求又来了,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]
1、设定每页显示数据条数
2、用户输入页码(第一页、第二页...)
3、设定显示多少页号
4、获取当前数据总条数
5、根据设定显示多少页号和数据总条数计算出,总页数
6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置
7、在数据表中根据起始位置取值,页面上输出数据
8、输出分页html,如:[上一页][1][2][3][4][5][下一页]
总结,分页时需要做三件事:
- 创建处理分页数据的类
- 根据分页数据获取数据
- 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]
#!/usr/bin/env python
# _*_coding:utf-8_*_
from django.utils.safestring import mark_safe
class PageInfo(object):
def __init__(self,current,totalItem,peritems=5):
self.__current=current
self.__peritems=peritems
self.__totalItem=totalItem
def From(self):
return (self.__current-1)*self.__peritems
def To(self):
return self.__current*self.__peritems
def TotalPage(self): #总页数
result=divmod(self.__totalItem,self.__peritems)
if result[1]==0:
return result[0]
else:
return result[0]+1
def Custompager(baseurl,currentPage,totalpage): #基础页,当前页,总页数
perPager=11
#总页数<11
#0 -- totalpage
#总页数>11
#当前页大于5 currentPage-5 -- currentPage+5
#currentPage+5是否超过总页数,超过总页数,end就是总页数
#当前页小于5 0 -- 11
begin=0
end=0
if totalpage <= 11:
begin=0
end=totalpage
else:
if currentPage>5:
begin=currentPage-5
end=currentPage+5
if end > totalpage:
end=totalpage
else:
begin=0
end=11
pager_list=[]
if currentPage<=1:
first="<a href=''>首页</a>"
else:
first="<a href='%s%d'>首页</a>" % (baseurl,1)
pager_list.append(first)
if currentPage<=1:
prev="<a href=''>上一页</a>"
else:
prev="<a href='%s%d'>上一页</a>" % (baseurl,currentPage-1)
pager_list.append(prev)
for i in range(begin+1,end+1):
if i == currentPage:
temp="<a href='%s%d' class='selected'>%d</a>" % (baseurl,i,i)
else:
temp="<a href='%s%d'>%d</a>" % (baseurl,i,i)
pager_list.append(temp)
if currentPage>=totalpage:
next="<a href='#'>下一页</a>"
else:
next="<a href='%s%d'>下一页</a>" % (baseurl,currentPage+1)
pager_list.append(next)
if currentPage>=totalpage:
last="<a href=''>末页</a>"
else:
last="<a href='%s%d'>末页</a>" % (baseurl,totalpage)
pager_list.append(last)
result=''.join(pager_list)
return mark_safe(result) #把字符串转成html语言