django框架:django中间件三个了解的方法、django中间件五个方法的执行流程详解、基于django中间件的功能设计、cookie与session简介、django操作cookie、django操作session、作业
一、django中间件三个了解的方法
1.process_view
process_view方法是在Django路由系统之后(路由匹配成功之后),视图系统之前(执行视图函数/类之前)执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
2.process_exception
视图函数/类执行报错自动触发(触发顺序是根据注册顺序从后往前执行)
3.process_template_response
视图函数/类返回的HttpResponse对象含有render属性(需要在内部定义一个render函数,并且要把这个函数名称绑定给render属性)并且对应一个方法的时候自动触发(触发顺序是根据注册顺序从后往前执行)
代码如下(用的不多不用深究):
views.py
from django.shortcuts import render, HttpResponse, redirect
# Create your views here.
def index_func(request):
print('from index view')
def render():
return HttpResponse('我是一个大奇葩')
obj = HttpResponse('index view')
obj.render = render
return obj
这里是自定义的中间件的代码
from django.utils.deprecation import MiddlewareMixin
class MyMiddleWare01(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleWare01 process_request')
def process_response(self, request, response):
print('from MyMiddleWare01 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
# print(view_func,view_args,view_kwargs)
print('from MyMiddleWare01 process_view')
def process_exception(self, request, exception):
print(exception)
print('from MyMiddleWare01 process_exception')
def process_template_response(self,request,response):
print('from MyMiddleWare01 process_template_response')
return response
class MyMiddleWare02(MiddlewareMixin):
def process_request(self, request):
print('from MyMiddleWare02 process_request')
def process_response(self, request, response):
print('from MyMiddleWare02 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('from MyMiddleWare02 process_view')
def process_exception(self, request, exception):
print(exception)
print('from MyMiddleWare02 process_exception')
def process_template_response(self,request,response):
print('from MyMiddleWare02 process_template_response')
return response
结果如图:
二、django中间件五个方法的执行流程详解
上一部分,我们了解了中间件中的5个方法,它们的参数、返回值以及什么时候执行,现在总结一下中间件的执行流程。
请求到达中间件之后,先按照正序执行每个注册中间件的process_request方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法(注意不是掉头执行所有的process_response方法),将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。
process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。
process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:
三、基于django中间件的功能设计
功能设计介绍
在使用自定义中间件的时候我们体会到了通过注册来启动或关闭一个功能的方便,我们称为功能的插拔式设计.
使用方式介绍:
- 将各个功能制作成配置文件的字符串形式
- 如果想拥有该功能就编写对应的字符串
- 如果不想有该功能则注释掉对应的字符串
如何利用字符串导入模块
这里我们使用importlib模块来模仿django中间件的调用功能的方式.
代码演示如下:
import importlib
s1 = 'bbb.b' # aaa.bbb.ccc.b
res = importlib.import_module(s1) # 相当于from aaa.bbb.ccc import b
print(res) # <module 'bbb.b' from 'D:\\pythonProject03\\djangomiddle\\bbb\\b.py'>
'''注意字符串的结尾最小单位只能是py文件 不能是py文件里面的变量名'''
思考django中间件是如何处理的(最小单位是类名)
功能模拟
需求分析
模拟编写一个消息通知功能(微信、qq、邮箱)
实现方式1:基于函数封装的版本
使用函数封装三个发送消息的应用(其实就写简单一些直接上print打印一句话即可)
然后再类似之前写atm的时候一样用一个启动函数给他运行
如果我们不想执行某个应用的消息通知功能,就把这个功能在启动函数中对应的调用代码注释掉即可.
但是这种使用发那个时没有眼前一亮的感觉,很一般.
实现方式2:基于django中间件的功能设计
首先是使用软件开发目录规范创建文件,将功能分到不同的接口层中去然后写上对应的代码(都是简单模拟)
接下去我们在配置文件settings中模仿这写注册表,同时具体到py文件内部的类名(django也是具体到类名)
在配置文件中注册后我们想到在软件开发目录中我们是把这些接口层的文件当模块导入调用的,而当我们把这个接口层的文件夹当成包导入的时候会更加方便(调包的时候其实就是在掉他内部的双下init文件)
这时候我们通过查看Django模块的配置文件导入中间件的方式,可以看到他在配置文件中注册的信息其实是具体到类的,但是importlib只能具体到py文件,这时候我们想到用split方法切割字符串.
基于上面的这些思考,我们在双下init文件中定义函数设置调用的模块的函数,然后导入配置文件中的配置信息,通过配置信息获取要执行的应用做代表的功能,然后通过切割字符串获取需要执行的功能类,然后创建对象执行功能,这时候我们就实现了通过更改配置信息中的注册表修改功能的执行与否.
四、cookie与session简介
知识点回顾
回忆:HTTP协议四大特性
1.基于请求响应
2.基于TCP、IP作用于应用层之上的协议
3.无状态
不保存客户端的状态
4.无连接
需求变化分析
最开始的网站都不需要用户注册 所有人来访问获取到的数据都是一样的
随着互联网的发展很多网站需要指定当前用户的状态,因此需要用到cookie和session
cookie
保存在客户端,存储与用户状态相关的信息
session
保存在服务端,存储与用户状态相关的信息
ps:session的工作需要依赖于cookie
补充:浏览器有资格拒绝保存服务端发送过来的cookie数据(需要在设置里设置,但都拒绝的话会出现不能登陆的情况)
我们可以在浏览器中右键检查查看cookies信息,如果我们主动删除这些cookies信息就需要重新登陆,如果短时间频繁操作,可能被某些网页检测到会封你ip(有人上课演示被封了,我也不知道是谁).
五、django操作cookie
要想操作cookie就不能直接返回HttpResponse对象 必须先用变量接收,因为我们如果要操作cookie就需要对三板斧产生的对象进行操作
obj1 = render()
return obj1
obj2 = HttpResponse()
return obj2
obj3 = redirect()
return obj3
首先实现一个本地Cookie的简单操作 通过后端传给Cookie 随时随地可以调用
views
def set_cookie(request):
obj = HttpResponse('Happy Happy !!!')
obj.set_cookie('name', 'James')
return obj
这里我们可以看到set_cookie方法把内部的数据发送到前端转换成了cookie.
同时我们可以在后端用request.COOKIES获取cookie信息
再来写一个登录账号跳转页面的情况吧!
这里的装饰器是用于判断cookie所代表的信息是否正确
def login_func(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
target_path = request.GET.get('next')
if target_path:
obj = redirect(target_path)
else:
obj = redirect('/home/')
obj.set_cookie('name', username)
return obj
return render(request, 'loginPage.html')
def login_auth(func_name):
def inner(request, *args, **kwargs):
# print(request.path) # 只获取用户输入的路由信息
# print(request.path_info) # 只获取用户输入的路由信息
target_path = request.path_info
# print(request.get_full_path()) # 获取用户输入的路由信息+问号后面携带的数据
if request.COOKIES.get('name'):
res = func_name(request, *args, **kwargs)
return res
return redirect('/login/?next=%s' % target_path)
return inner
@login_auth
def home_func(request):
return HttpResponse('home页面 只有登录的用户才可以查看')
@login_auth
def home1_func(request):
return HttpResponse('home1页面 只有登录的用户才可以查看')
@login_auth
def home2_func(request):
return HttpResponse('home2页面 只有登录的用户才可以查看')
在上面的代码中我们又联系实际情况,想到了一个问题,做的好一些的网站,通常都是会 记住我们之前访问的网页,在进行登陆有继续访问该网页.这里就需要用到一些小知识点了.也就是我们需要在装饰器中修改跳转的内容让他记住我们之前想要访问的网址.
def login_auth(func_name):
def inner(request, *args, **kwargs):
# print(request.path) # 只获取用户输入的路由信息
# print(request.path_info) # 只获取用户输入的路由信息
target_path = request.path_info
# print(request.get_full_path()) # 获取用户输入的路由信息+问号后面携带的数据
if request.COOKIES.get('name'):
res = func_name(request, *args, **kwargs)
return res
return redirect('/login/?next=%s' % target_path)
return inner
六、django操作session
由于session是保存在服务端上面的数据 就应该有个地方能够存储
我们只需要执行数据库迁移命令即可 django会自动创建很多需要的表
ps:django默认的session失效时间是14天
设置session的代码:
request.session['key'] = value
当我们在设置session的时候会执行下列操作:
- 1.生成一个随机字符串
- 2.对value数据做加密处理 并在django_session表中存储
随机字符串>>>加密数据 - 3.将随机字符串也发送一份给客户端保存(cookie)
sessionid:随机字符串
获取session的代码
request.session.get('key')
在获取session的时候会执行下列操作
- 1.自动获取随机字符串
- 2.去django_session表中根据随机字符串获取加密的数据
- 3.自动解密数据并处理到request.sesion.get()中
总结和补充
总结
通过上面的知识点讲解我们可以发现session的使用非常简单,但是在底层来看,其实代码执行了非常多的操作,今后我们写代码也应该朝这方面靠拢.
补充说明
当我们在执行代码进行实操的时候会遇到一些问题
设置session中的注意事项
def set_session(request):
request.session['name'] = 'Like' # 设置Session
return HttpResponse('设置Session')
'''
第一次运行的时候访问接口会发生报错 no such table: django_session
因为当客户端登录成功之后 服务端会产生一个随机字符串 返回客户端去保存(之所以要保存就是因为我们只有保存了这个字符串对应的信息,才能去进行信息校对,否则前端发送cookie我们无法识别)
服务端会针对这些字符串跟对应用户信息做一个保存 这个关系就保存在服务端执行迁移命令里面的Django_session表里面(但是目前我们第一次跑程序还没执行迁移命令)
表里面有三个字段 Session_key Session_data Expire_date
session_ky 随机产生的一个加密字符串令牌
session_data session数据
expire——date django默认的session失效时间14天(不登录)
这个时候迁移命令创建完之后就可以访问接口了
'''
迁移操作截图
获取session中的注意事项
def get_session(request): # 获取session
print(request.session.get('name'))
return HttpResponse('获取Session') # 自动会加密解密
'''
request.session['name'] = 'Like'
1.django自动产生一个随机字符串返回给客户端(对name加密)
2.往django_session创建数据(对jason加密)
request.session.get('name')
1.自动从请求中回去sessionid对应的随机字符串
2.拿着随机字符串去django_session中匹配数据
3.如果匹配上还会自动解密数据并展示
'''
其他注意事项
session的存储位置可以有五种模式(数据库、缓存数据库、文件、缓存+数据库、加密)
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
其他公用设置项:
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其他操作
'''
删除当前会话的所有Session数据
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
'''
七、作业
1.编写一个session版本的用户登录功能
装饰器、自动记忆跳转、过期时间
views
from django.shortcuts import render, redirect, HttpResponse
from django import forms
from app01 import models
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# Create your views here.
class Login(forms.Form):
username = forms.CharField(max_length=16, min_length=2,
# widget=forms.widgets.TextInput(),
error_messages={
'max_length': "用户名长了",
'min_length': "用户名短了",
}
) # 字符字段,限定最大最小长度
password = forms.CharField(max_length=16, min_length=6,
widget=forms.widgets.PasswordInput(),
error_messages={
'max_length': "密码长了",
'min_length': "密码短了",
}
)
# 用forms组件校验数据,这里是用全局钩子判断用户名跟密码是否正确
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
user_obj = models.User.objects.filter(username=username).first()
if not user_obj:
self.add_error('username', '用户名不存在')
elif not user_obj.password == password:
self.add_error('password', '密码不正确')
return self.cleaned_data
# 定义装饰器判断用户的登陆状态(如果有cookie就是登陆过了,如果没有就记住当前需要访问的网址在登陆后用于自动跳转)
def login_auth(func):
def inner(request, *args, **kwargs): # 视图函数一定含request参数,所以单独先接收便于操作
target_path = request.path_info # 拿到请求的路由
username = request.session.get('key') # 拿到登录态
# username = request.COOKIES.get('name')
if username: # 校验用户态
res = func(request, *args, **kwargs) # 用户登录态则直接返回原本访问的网页
return res
return redirect(f'/login/?next={target_path}') # 将被装饰的视图函数的路由发送给login函数
return inner
# 这是让csrf策略不要检测的装饰器
@csrf_exempt
def login_func(request):
login_obj = Login()
if request.method == 'POST': # 提交表单打算登录
login_obj = Login(request.POST)
if login_obj.is_valid(): # 数据有效且密码校验也正确
if not request.GET.get('next'):
obj = redirect('/home/') # 如果不是通过装饰器重定向来的,就重定向到首页
else:
obj = redirect(request.GET.get("next")) # 有next就重定向到原本的网页
request.session['key'] = login_obj.cleaned_data.get('username') # 保存登录态
request.session.set_expiry(3000)
return obj
return render(request, 'loginPage.html', locals()) # get请求拿登录界面
# 主页需要登陆后访问,用装饰器来实现需求
@login_auth
def homePage_func(request):
return render(request, 'homePage.html')
models
记得换成MySQL数据库
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=16, verbose_name='用户名')
password = models.CharField(max_length=16, verbose_name='密码')
urls
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', views.homePage_func, name='homePage'),
path('login/', views.login_func, name='login'),
]