框架第十一课---django中间件三个了解的方法,基于django中间件实现功能的插拔式设计,cookie与session简介,django操作cookie与session

昨日内容回顾

  • forms组件渲染标签

    form_obj = MyForm()
    方式1: form_obj.as_p\form_obj.as_ul\form_obj.as_table
    方式2: form_obj.username.label form_obj.username
    方式3: for循环获取一个个字段注释和数据框(推荐使用)
    
  • forms组件展示信息

    利用循环出来的一个个对象点击errors.0即可
    
  • forms组件多种校验

    1.字段对象自带的校验参数		max_length
    2.字段对象自带的正则校验		validators
    3.钩子函数支持自定义代码		clean\clean_xxx
    	def clean_username(self):pass   校验单个字段数据
    	def clean(self):pass	      校验多个字段数据
    
  • forms组件常用参数

    max_length
    min_length
    min_value
    max_value
    label
    required
    error_messages		扩展:django语言环境
    initial
    widget
    
  • forms组件其他补充

    forms组件渲染不同类型的标签
    	radio\checkbox\select
    
  • forms组件源码剖析

    1.is_valid()
    2.先校验每个字段数据是否符合字段自带的校验规则 max_length 正则
    3.再去执行局部钩子函数校验
    4.最后执行全局钩子函数校验
    
  • modelform组件简介

    将模型类与forms组件结合到一起 代码更简单 功能更强大
    
  • django中间件

    '''完整的画出django请求生命周期流程图'''
    1.django自带七个中间件
    2.django还支持自定义中间件
    3.在自定义的中间件里面支持五个绑定方法
    
    需要掌握
    	process_request
    		1.请求来的时候会按照配置文件中从上往下执行注册了的中间件里面的每个该方法 如果没有写则直接跳过
       		2.如果该方法返回了HttpResponse对象则直接原路返回响应
    	process_response
        	1.响应走的时候会按照配置文件中从下往上执行注册了的中间件里面的每个该方法 
       		2.该方法必须要返回形参response 也可以返回自己定义的(中途调包)
    	ps:如果在某个中间件里面的process_request直接返回了HttpResponse对象,则会原路返回执行对应的process_response
    

今日内容概要

  • django中间件三个了解的方法
  • 基于django中间件实现功能的插拔式设计
  • cookie与session简介
  • django操作cookie
  • django操作session
  • csrf跨站请求伪造

今日内容详细

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)

django中间件三个了解的方法

--------------------------------------------------

1.process_view
	路由匹配成功之后,执行视图函数/类之前,自动触发!!!
	(顺序也是执行从上往下注册了的中间件里面的process_view方法)
--------------------------------------------------

2.process_exception
	视图函数/类执行报错自动触发(顺序:执行从下往上注册了的中间件里面的process_exception方法
--------------------------------------------------

3.process_template_response
	视图函数/类返回的HttpResponse对象含有render属性,并且该属性对应的是一个函数的时候,自动触发(顺序同process_response)
--------------------------------------------------

路由匹配成功之后,执行视图函数/类之前,自动触发!!!(顺序也是执行从上往下注册了的中间件里面的process_view方法)
image
.
image
.
.
.
.
.

基于django中间件实现功能的插拔式设计

django的settings文件里面的每一个字符串都不是简单的字符串,而是都代表了一个特定的功能
如果想有这个功能就把这个字符串写上,如果有这个功能就把字符串注掉就行,这种现象就叫做功能的插拔式设计!!!插上就能用,拔掉就没有,但是底层的功能代码一直都在,也就是功能的源代码已经写好了不用改了,但是将来确可以通过在配置文件里面要不要写字符串,来决定功能代码的是否执行!!!
----------------------------------------------------
django中间件也是将各个功能制作成配置文件的字符串形式
	如果想拥有该功能就编写对应的字符串
	如果不想有该功能则注释掉对应的字符串
----------------------------------------------------
这种通过字符串就能实现功能的操作是这么实现的了???
比如:settings文件里面的中间件
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',
]
我们发现SecurityMiddleware实际上是写在security.py文件里面的一个类   'django.middleware.security.SecurityMiddleware'  该字符串实际上就是相当于导模块操作
from django.middleware.security import SecurityMiddleware

怎么实现 写一串字符串就相当于执行了一个导模块的操作呢?
----------------------------------------------------

.
.

补充知识: 如何利用字符串导入模块

----------------------------------------------
# 正常的导模块操作,实现用其他文件里面的名字
from bbb import b
print(b)  # 结果是 <module 'bbb.b' from 'D:\\pythonProject\\pythondjangomiddle\\bbb\\b.py'>
print(b.desc)
----------------------------------------------

# 利用字符串也可以实现导模块操作!!!

s1 = 'bbb.b'  # 注意该字符串一定要是该模块文件的路径
import importlib  # importlib是很多底层字符串的基本模块

# 该模块能够支持你给它提供一个字符串,自动帮你把字符串拆分成导模块的形式

res = importlib.import_module(s1)
print(res)  # 结果是 <module 'bbb.b' from 'D:\\pythonProject\\pythondjangomiddle\\bbb\\b.py'>
res接收到的就是b模块的模块名
importlib.import_module() 它的功能就是你给它传一个模块文件路径的字符串,它会自动将字符串变成导模块的形式
# 它的底层是无论模块文件的路径前面有多少个点,都只会将字符串从右往左按点号切割一次
# 比如 'aaa.bbb.ccc.d' 往括号里面一放就变成了 from aaa.bbb.ccc import d
--------------------------------------------------
'''注意import_module()方法能够导入的模块的最小单位只能是模块名,所以字符串的结尾最小单位只能是py文件 不能是py文件里面的变量名'''
--------------------------------------------------
思考django中间件是如何处理的('django.middleware.security.SecurityMiddleware'最小单位是类名)

.
.

需求分析:
	模拟编写一个消息通知功能(微信、qq、邮箱)
方式1:基于函数封装的版本
	没有眼前一亮的感觉 很一般
方式2:基于django中间件的功能设计
	眼前一亮 回味无穷

.
基于函数封装的版本

def qq(content):
    print('来自于qq的消息:%s' % content)

def weixin(content):
    print('来自于微信的消息:%s' % content)

def email(content):
    print('来自于邮箱的消息:%s' % content)

def send_all(content):
    qq(content)
    weixin(content)
    email(content)

if __name__ == '__main__':
    send_all('元旦不放假')

.
.
基于django中间件的功能设计

settings.py文件里面代码:
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.qq.Qq',
    'notify.weixin.Weixin'
]
-----------------------------------------------
notify文件里qq.py文件里面代码:
class Qq(object):
    def __init__(self):
        pass  # 模拟发送qq之前需要做的准备

    def send_msg(self, content):
        print('来自于qq的消息>>>:%s' % content)
------------------------------------------------
notify文件里Weixin.py文件里面代码:
class Weixin(object):
    def __init__(self):
        pass  # 模拟发送微信之前需要做的准备

    def send_msg(self, content):
        print('来自于微信的消息>>>:%s' % content)
-------------------------------------------------
notify文件里Email.py文件里面代码:
class Email(object):
    def __init__(self):
        pass  # 模拟发送邮件之前需要做的准备

    def send_msg(self, content):
        print('来自于邮箱的消息>>>:%s' % content)
--------------------------------------------------
notify文件里__init__.py文件里面代码(这里面代码是最核心的):
from settings import NOTIFY_LIST
import importlib

def send_all(content):
    for full_path in NOTIFY_LIST:
        module_path, class_str_name = full_path.rsplit('.', maxsplit=1)  # 'notify.email'  'Email'
        # 1.利用字符串导入模块 拿到模块名
        module_name = importlib.import_module(module_path)  # from notify import email
        # 2.一切皆对象,模块名也是对象,利用反射从模块名中获取字符串对应的类名
        class_name = getattr(module_name,class_str_name)  # 拿到字符串对应的类名 Email
        # 3.利用类名加括号产生对象
        obj = class_name()
        # 4.对象调用发送消息的方法
        obj.send_msg(content)
--------------------------------------------------
最后启动文件代码:
import notify
# import一个包名,其实是导的是包里面的双下init.py文件

if __name__ == '__main__':
    notify.send_all('哈哈哈')
--------------------------------------------------
这样就实现了在settings.py里面的NOTIFY_LIST列表里面通过添加字符串或注掉字符串,来实现功能的插拔式设计了
比如你还想再一次性发送消息的里面再加个陌陌,也很简单,只要在notify文件里面,先创一个momo.py文件 然后在里面写好对应的类的代码,只要保证发消息的函数名也是send_msg
然后在NOTIFY_LIST的列表里面再加一行字符串就行了'notify.momo.Momo'
这样当start文件启动,就会运行notify里面的send_all()方法,然后先for循环拿到一个一个字符串,然后对字符串切割拿到功能文件路径与功能文件里面的类名的字符串
然后利用importlib模块将字符串转为导模块的形式,然后拿到了模块名也就是功能文件的文件名
因为文件名也是对象,所以再利用反射getattr 拿到文件名里面的类名
最后再利用类名加括号产生对象,以及通过对象点统一的函数名send_msg()  运行send_msg函数
------------
虽然for循环出来的每个字符串不一样,每个字符串最后解析出的文件名与文件名里面的类名也不一样
但是每个类里面的发消息的函数名都是send_msg  所以每个类产生的对象都能正常去点send_msg加括号去运行该函数!!!
--------------------------------------------------

.
.
.
.

cookie与session简介

"""
回忆:HTTP协议四大特性
	1.基于请求响应
	2.基于TCP、IP作用于应用层之上的协议
	3.无状态:不保存客户端的状态
	4.无连接
"""
--------------------------------------------------------
最开始的网站都不需要用户注册,所有人来访问获取到的数据都是一样的

随着互联网的发展,很多网站需要知道当前用户的状态
---------------------------------------------------------
cookie
	保存在客户端与用户状态相关的信息(类似于字典的KV键值对)
session
	保存在服务端与用户状态相关的信息
ps:session的工作需要依赖于cookie

补充:浏览器有资格拒绝保存服务端发送过来的cookie数据

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。Session对象是在客户端第一次请求服务器的时候创建的。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

.
.
.
.

django操作cookie

from django.shortcuts import render,HttpResponse,redirect
return render()
return HttpResponse()
return redirect()

要想操作cookie就不能直接返回HttpResponse对象 必须先用变量接收
obj1 = render()
return obj1
obj2 = HttpResponse()
return obj2
obj3 = redirect()
return obj3

.

登录功能 简单的用一下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':
            obj = redirect('/home/')    # 重定向的整体代码看成是一个对象
            obj.set_cookie('name', username)    # 添加cookie信息到对象
            return obj     # 执行重定向
    return render(request, 'loginPage.html')
-------------------------
def home_func(request):
    # 先获取cookie数据
    if request.COOKIES.get('name'):
        return HttpResponse('home页面 只有登录的用户才可以查看')
    else:
        return redirect('/login/')
------------------------------------------
loginPage.html里面就是简单的form表单的用户输入框与提交按钮
只要用户账户密码输对了,再跳转到home页面前,添加cookie信息。
home的视图函数上来就校验cookie里面有没有对应的'name'对应的值!!!
有才能正常访问home页面,否则就直接跳到登录页面!!!
------------------------------------------

当登录成功后,浏览器上就会有cookie信息了,此时就算重新输入home/路由也能正常跳转到home页面了,浏览器的cooklie里已经有记录了,如果我把cooklie清除掉,再重新输入home/路由就不能跳转到home页面上了,因为home_func视图函数的if判断不符合,只能走else 重新定向到登录页面
image
image
.
image
image
自动跳转到登录页面了
image
.
.
.
.

django操作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':
            obj = redirect('/home/')
            obj.set_cookie('name', username)
            return obj
    return render(request, 'loginPage.html')

def home_func(request):
    # 先获取cookie数据
    if request.COOKIES.get('name'):
        return HttpResponse('home页面 只有登录的用户才可以查看')
    else:
        return redirect('/login/')

def home1_func(request):
    if request.COOKIES.get('name'):
        return HttpResponse('home1页面 只有登录的用户才可以查看')
    else:
        return redirect('/login/')

def home2_func(request):
    if request.COOKIES.get('name'):
        return HttpResponse('home2页面 只有登录的用户才可以查看')
    else:
        return redirect('/login/')

--------------------------------------------------------
用装饰器的情况下:
def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            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):
        if request.COOKIES.get('name'):
            res = func_name(request, *args, **kwargs)
            return res
        return redirect('/login/')
    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页面 只有登录的用户才可以查看')

写装饰器费劲,写视图函数方便
--------------------------------------------------------

复习一下装饰器语法糖:@login_auth实际上就是  home_func = login_auth(home_func)
这样先运行装饰器函数,返回了一个inner 被左边的home_func是变量名接收了
所以当其他地方想运行home_func()时,实际上此时运行的并不是真正的home_func函数
而是运行的装饰器里面的inner函数,只不过inner函数里面含有真正home_func函数名加括号运行的代码,所以真正home_func函数也会正常运行。
在网关接口层请求体数据,变成了request字典了,当在触发视图函数时,又会传给视图函数的行参request.
原来我们用装饰器的时候inner()括号里面是*args, **kwargs  所以如果这么写,此时request位置参数被*号接收放在元组里面再赋值给args  但现在的需求是在装饰器里面,先真正的函数运行前就要用request,那么就要*args[0]才能拿到request,这时在inner与func_name的括号里面都多加个request,这样原来触发视图函数的操作变成了触发inner函数的操作了,这样原来传给视图函数的字典就会被inner函数的行参request接收,最后再传给正真的视图函数的request行参,所以整体没有影响正真的视图函数的运行!!!
-------------------------------------------------------------

.
.

进阶操作

上面的代码写死了,比用用户还没有登录,现在想访问home3页面,点击访问home3页面的标签按钮后,由于没登录被装饰器自动跳转到登录页面了,但是当用户用户名与密码都输对后,却固定跳到了home页面了!!并没有跳到用户想访问的home3页面!!!

进阶操作:用户没有登录之前想访问某个网站,输入用户名密码之后就应该调回该网站

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:               # 详解见下方1
                obj = redirect(target_path)
            else:                         # 详解见下方2
                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

拿到了用户输入的路由信息后,我们最终的目的是在用户登录成功后,往一开始用户想要访问的路由跳转,在用户登录成功之后需要用到(一开始用户想要访问的路由)
当一个用户在没有登录前想要访问一个路由,肯定会被装饰器跳转到登录的路由去,但是在跳转到登录的路由去之后,会在登录的路由后面通过问号?携带将用户想要访问的路由信息。
这样在登录的页面,只要用户的用户名与密码输入正确提交post请求时,后端就可以拿到用户登录前想访问的路由,拿到该路由就可以继续跳转了!!!
-----------------------------
详解1 :当登录的post请求数据过来,通过request.GET拿到'login/'路由问号后面携带的数据了再通过get('next')拿到用户登录前想访问的路由,但是这个地方有个细节容易忽视,假如用户没登录前点其他需要先登录才能执行的路由时,没有问题,自动跳到登录页面,登录成功再自动跳到登陆前想访问的路由,,但是当用户上来就要直接访问登录页面的时候,这个时候get('next')就什么都拿不到了,没有路由给redirect 肯定会报错的!!
所以要再多加个小判断,判断request.GET.get('next')有没有值,有值再去跳转,没值登录成功就直接跳到主页面去!!!
-------------------------------------------------

.
.
.
.

django操作session

想要操作session 必须先进行数据库迁移命令 让django自动帮我们创个session表出来
不需要在models文件里面写任何的类,直接进行数据库迁移命令

request.session['desc'] = '嘿嘿嘿'
------------------------------------------------------------
session_key             session_data               expire_data
                    存储的跟用户相关的数据        session数据过期时间

比如上面的'desc'经过加密后的数据放在session_key里面,并且把这加密数据交给浏览器一份,这样浏览器每次发送请求把这个加密数据带着,后端通过比对session表里的数据,来判断用户的状态

比如上面的'嘿嘿嘿'经过加密后的数据放在session_data里面
------------------------------------------------------------
由于session是保存在服务端上面的数据 就应该有个地方能够存储
我们只需要执行数据库迁移命令即可 django会自动创建很多需要的表
---------------------------------
django默认的session失效时间是14天
------------------------------------------------------------
设置session
	request.session['key'] = value
        1.生成一个随机字符串(放在session_key里面)
        2.对value数据做加密处理 并在django_session表中session_data下存储
                随机字符串与加密数据在django_session表中的同一行(两个是对应关系)

        3.将随机字符串也发送一份给客户端保存(cookie)
            sessionid:随机字符串
------------------------------------
获取session
	request.session.get('key')
        1.自动获取请求头里面的 随机字符串
        2.去django_session表中根据随机字符串获取加密的数据
        3.自动解密数据并处理到request.sesion.get()中
-------------------------------------------------------------
补充说明
	1.可以设置过期时间
	2.存储session数据的位置也可以修改(比如修改与django连接的数据库)
--------------------------------------------------------------
def set_se_func(request):
    request.session['desc'] = '嘿嘿嘿'
    request.session.set_expiry(30000)     # 设置session的过期时间单位是秒
    return HttpResponse('你好美')

def get_se_func(request):
    print(request.session.get('desc'))
    return HttpResponse('你好骚')
--------------------------------------------------------------

.
image
.
image
.
image
.
image
.
.
.
.

作业

1.编写一个session版本的用户登录功能
	装饰器、自动记忆跳转、过期时间
2.整理今日内容及博客
--------------------------
urlpatterns = [
    path('admin/', admin.site.urls),

    # 编写一个session版本的用户登录功能
    path('login/', views.login_func),
    path('home/', views.home_func),
    path('home1/', views.home1_func),
    path('home2/', views.home2_func),
]
--------------
<body>
<form action="" method="post">
    <p>username:
        <input type="text" name="username">
    </p>
    <p>password:
        <input type="text" name="password">
    </p>
    <input type="submit" value="登录">
</form>
</body>
--------------
from django.shortcuts import render, HttpResponse, redirect
# Create your views here.

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')  # 3. 在前端提交post请求后,拿到登录路由问号后面携带的信息,也就是想要访问的路由
            if target_path:
                obj = redirect(target_path)  # 4. 如果信息存在,就跳转到想要访问的路由去!!!
            else:
                obj = redirect('/home/')
            request.session['user'] = password      # 设置session
            request.session.set_expiry(30000)       # 设置session  的保存时间
            return obj
    return render(request, 'loginPage.html')

# 装饰器
def login_auth(func_name):
    def inner(request, *args, **kwargs):
        target_path = request.path_info  # 1. 这步很关键,拿到想要访问的路由
        if request.session.get('user'):          # 获取session
            res = func_name(request, *args, **kwargs)
            return res
        return redirect('/login/?next=%s' % target_path)  # 2. 利用问号携带数据的方法将想要访问的路由挂在登录的路由屁股后
    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页面 只有登录的用户才可以查看')

posted @   tengyifan  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示