五、类视图:
1.类视图引入:
以函数的方式定义的视图成为函数视图,即我们常说的视图函数.
但是,
视图函数遭遇不同的请求方法(如get和post),并且需要做不同的处理时,我们如果在一个函数中编写不同的业务逻辑,代码可读性和复用性都不好.
例如:
我们创建一个新的子应用:demoview
python manage.py startapp demoview
在demoview中配置路由部分(urls.py)
from django.conf.urls import url
from . import views
urlpatterns = [
# 添加一个路由视图
url(r"^indexview/", views.indexview)
]
在demoview中配置视图函数部分(views.py)
from django.http import HttpResponse
# 创建糊涂函数indexview
def indexview(request):
# 获取请求方法,判断是GET/POST请求
if request.method == "GET":
# 根据不同的请求方式,做不同的操作处理
return HttpResponse("indexview get function")
else:
# 根据不同的请求方式,做不同的操作处理
return HttpResponse("indexview post function")
总结发现:
上例虽然可以完成根据不同的请求进行不同处理的目的, 但是我们这样没有做到分离处理, get和post的处理方式还是放在一个视图函数内部的, 而且也不利于我们后面内容的扩展.所以我们会对它进行改造:
- 在Django中给我们提供了类视图的概念.
- 即可以使用类来定义一个视图,解决上面的问题.
总结: 使用类来定义的视图, 称为类视图.
2.类视图使用:
如果想要使用类视图需要如下几步:
- 定义类视图, 且类视图继承自View
- 使用: from django.views.generic import View
- 或者是: from django.views.generic.base import View
- 定义路由,路由的第二个参数需要是一个函数,所以我们会调用系统的as_view()方法.
urlpatterns = [
# 类视图:注册
url(r'^register/$',views.RegisterView.as_view()),
]
使用浏览器访问我们定义的路由, 查看结果:
使用类视图可以将视图对应的不同请求方式以类中的不同方法区别定义.
代码实例:
# 导入类视图的父类View
from django.views.generic import View
class RegisterView(View):
"""类视图:处理注册"""
def get(self, request):
"""处理GET请求,返回注册页面"""
return render(request, 'register.html')
def post(self, request):
"""处理POST请求,实现注册逻辑"""
return HttpResponse('这里实现注册逻辑')
类视图的好处:
- 代码可读性好
- 类视图相对于函数视图有更高的复用性
-
- 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可
但是这样写完还不行, 因为我们还需要定义路由部分:
- 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可
urlpatterns = [
# 注意:
# url(路径, 执行的函数)
# url的第二个参数需要是一个函数
# 我们这里如果传入: views.RegisterView 会发现这个是一个类, 不是一个函数,
# 所以我们需要调用系统给我们提供的 as_view() 方法
url(r'^registerview/$', views.RegisterView.as_view())
]
注意:
- 如果我们在类视图函数中没有定义方法, 但是我们请求了. 会报405找不到请求方法的错误.
-
- 例如: 类视图中没有定义Get方法, 但是我们使用Get方法进行了请求, 那么会报405的错误: 找不到对应的请求方法.
- 在类视图中定义的 Get 或者是 POST 都是对象方法, 第一个参数都是self.
- 第二个参数一般情况下都是 request对象. 其他的参数依次往后面写就可以.
- 我们在使用类视图的时候, 需要在路由位置进行设置, 设置的第二个参数需要是一个函数, 所以我们这里调用了类以后, 后面需要调用 as_view( ) 函数.
效果演示:
3.类视图原理:
为什么我们定义url的时候, 调用 as_view() 函数,就可以达到结果, 如果不调用就会报错.
到底 as_view() 帮助我们干了什么了?
@classonlymethod
def as_view(cls, **initkwargs):
...省略代码...
def view(request, *args, **kwargs):
# 这里的cls是as_view这个函数接收的第一个参数,也就是调用当前函数的类.
# 得到调用的类了之后, 创建类的对象: self
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 给当前这个类,添加对应的属性, 如下所示:
self.request = request
self.args = args
self.kwargs = kwargs
# 调用dispatch方法,按照不同请求方式调用不同请求方法
return self.dispatch(request, *args, **kwargs)
...省略代码...
# 返回真正的函数视图
return view
# dispatch本身是分发的意思,这里指的是函数的名字.
def dispatch(self, request, *args, **kwargs):
# self.http_method_names指的是我们的类视图中,对象方法的名字
# 这里把所有方法的名字都存放在了http_methods_names中
# 我们会把当前请求的方式转为小写,然后判断是否在列表中存在.
if request.method.lower() in self.http_method_names:
# 如果在里面, 则进入这里
# 这里的getattr作用是获取当前对象的属性.
# 下面的参数为:
# self : 类视图对象
# request.method.lower() : 请求方法的小写. 例如: 'get' 或 'post'
# http_method_not_allowed : 后续处理方式(不允许请求)
# 下面代码整体的意思: 根据类视图对象, 获取当前类视图中对应名称的方法
# 如果获取到, 则把方法返回给handle, 否则不允许访问.
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果类视图中如果没有的话, 则进入这里, 表明不允许进行请求.
# 我们会把不允许请求这个字段返回给handle.
handler = self.http_method_not_allowed
# 最终返回handle(handle里面要么包含可以访问的方法, 要么就是不允许访问的字段)
return handler(request, *args, **kwargs)
4.类视图使用装饰器:
日常生活中, 我们很多时候都会使用装饰器修饰视图. 那么, 如果我们定义了一个类视图, 它是否可以使用装饰器进行修饰呢?
为了理解方便,我们先来定义一个为函数视图准备的装饰器(在设计装饰器时基本都以函数视图作为考虑的被装饰对象),以及一个要被装饰的类视图, 来说明一下给类添加装饰器的实现要点.
代码实例:
# 定义一个装饰器
# func = my_decorator(func)
def my_decorator(func):
# wrapper函数必然会接收一个request对象,因为传入进来的func这个函数肯定会带这个参数
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求路径%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 我们定义的类视图
class DemoView(View):
# 我们给get方法添加上装饰器, 然后执行.
@my_decorator
def get(self, request):
print('get方法')
return HttpResponse('ok')
# 类视图里面的对象方法: post方法
def post(self, request):
print('post方法')
return HttpResponse('ok')
演示效果:
4.1 在URL配置中修饰:
# 导入views.py视图文件
from . import views
urlpatterns = [
# 我们在路由部分, 把定义好的装饰器添加到当前的函数上
# 这里需要注意: as_view() 会返回一个 view() 函数
# 所以我们把装饰器添加到view()函数上.
url(r'^demo/$', views.my_decorate(views.DemoView.as_view()))
]
运行之后发现, 这样的方法是可以实现的:
这种方式的弊端:
1.种方式最简单,但因装饰行为被放置到了url配置中,单看视图的时候无法知道此视图还被添加了装饰器,不利于代码的完整性,不建议使用。
2.此种方式会为类视图中的所有请求方法都加上装饰器行为(因为是在视图入口处,分发请求方式前)。
4.2 回顾一下在类视图中装饰器的使用:
如果我们不适用上面的方式, 而是直接把装饰器作用到类视图里面的函数上, 又会出现我们上面呈现的问题, 那么我们这个问题到底是怎么来的呢?
我们可以来尝试一下:
由此我们可以得到结论: 我们不能够直接给类视图中的函数添加装饰器, 否则传入的参数会出现错误.
也就是说, 如果我们能够解决传入的参数问题, 那么我们就可以以这样的形式来添加装饰器.
解决方法:
- 由上面的案例我们可知: 我们定义的装饰器不能够直接使用到类视图的方法上.
- 所以我们需要把我们定义的装饰器进行转化, 转化为能够被类视图中函数使用的装饰器
- 转化的方法需要导入:
-
- from django.utils.decorators import method_decorator
- 导入进来之后, 我们需要把自定义的装饰器用这个方法包裹住转化, 例如:
-
- @method_decorator(自定义装饰器)
第一种解决方式:
在类视图中使用为函数视图准备的装饰器时,不能直接添加装饰器
需要使用method_decorator将其转换为适用于类视图方法的装饰器。
from django.views.generic import View
# 导入转换的装饰器方法:
from django.utils.decorators import method_decorator
# 为特定请求方法添加装饰器
class DemoView(View):
# 使用转换的方法将装饰器转化:
@method_decorator(my_decorator)
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
显示效果:
使用post方法的时候::
问题:
- 虽然上面的方式可以解决类视图添加装饰器问题, 但是我们这种是给单个函数添加的, 而不是类视图中的所有函数, 那么有没有给所有函数添加装饰器的方式呢?
- 我们可以对上面的装饰器方法进行再次改造:
第二种解决方式:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 自定义的装饰器方法
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 类视图
class DemoView(View):
# 重写父类的dispatch方法, 因为这个方法被 as_view() 中的 view() 调用
# 所以我们对这个方法添加装饰器, 也就相当于对整个类视图的方法添加装饰器.
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
# 重写父类的这个方法我们不会修改它的任何参数, 所以我们直接调用父类的这个方法即可
# 它里面的参数我们也不动它, 直接还传递过去.
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
展示效果:
如果刚刚的能够接受, 那我们再进一步, method_decorator( ) 这个方法其实还可以直接装饰到类上去. 使当前的视图类中的某一个函数添加装饰器方法:
第三种解决方式:
例如:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 类视图
# 给类视图增加上@method_decorator方法
# 增加上之后,并不能够给其中的某一个函数增加上装饰器
# 所以我们需要给method_decator配置第二个参数
# 第二个参数就是类中某一个函数的名称. 意味着给当前这个函数增加上装饰器.
@method_decorator(my_decorator, name='get')
class DemoView(View):
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
效果演示:
那么我们可以进一步联想: 如果使用@method_decorator在类视图的位置可以给’get’方法添加装饰器, 那么是否意味着也可以给dispatch方法添加呢?
如下所示:
第四种解决方式:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 类视图
# 因为我们可以直接给dispatch方法添加装饰器,意味着, 我们内部不用重写dispatch方法了.
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
演示效果:
使用method_decorator的原因:
为函数视图准备的装饰器,其被调用时,第一个参数用于接收request对象
def my_decorate(func):
def wrapper(request, *args, **kwargs): # 第一个参数request对象
...代码省略...
return func(request, *args, **kwargs)
return wrapper
而类视图中请求方法被调用时,传入的第一个参数不是request对象,而是self 视图对象本身,第二个位置参数才是request对象
class DemoView(View):
def dispatch(self, request, *args, **kwargs):
...代码省略...
def get(self, request):
...代码省略...
所以如果直接将用于函数视图的装饰器装饰类视图方法,会导致参数传递出现问题。
method_decorator的作用是为函数视图装饰器补充第一个self参数,以适配类视图方法。
如果将装饰器本身改为可以适配类视图方法的,类似如下,则无需再使用method_decorator。
def my_decorator(func):
def wrapper(self, request, *args, **kwargs): # 此处增加了self
print('自定义装饰器被调用了')
print('请求路径%s' % request.path)
return func(self, request, *args, **kwargs) # 此处增加了self
return wrapper
4.3 构造Mixin扩展类:
上面的方法已经可以实现给类视图添加装饰器的功能了, 那么还有没有其他的方式可以给当前的视图类添加类似的功能呢?
答案是肯定的, 我们现在来看一种全新的添加方式:
使用类扩展的形式, 给当前的类视图添加装饰器.
我们可以知道, 给类视图所有方法添加装饰器有两种:
- 修改装饰器接收到的参数
-
- 即把装饰器的第一个参数修改为self. 使类中所有的方法都可以直接添加上装饰器.
- 修改类视图中所有函数都会调用的 as_view() 方法
-
- 或者是 dispatch方法, 因为dispatch方法在 as_view() 方法内部,故我们这里不讨论dispatch.
那么我们可以继续思考:
类视图 —————> 继承自View类 (包含有as_view函数)
如果我们在整个继承的过程中添加一步, 例如:
类视图 ———> 继承自额外扩展的类 ———> 继承自View类(包含有as_view函数)
如果我们在额外扩展的类中, 重写 as_view( ) 方法, 并且对 as_view( ) 方法添加装饰器, 那么我们会发现, 类视图中的所有方法都会被装饰器装饰. 故, 我们要实现这一点, 需要分为两步:
第一步: 创建一个扩展类, 并且让类视图继承自该类.
第二步: 在扩展类中重写 as_view 方法,并且给该方法,添加装饰器
第一步代码:
# 定义一个新的扩展类,让该类继承自View类
class BaseView(View):
pass
第二步代码:
class BaseView(View):
# 在扩展类中,重写View类的 as_view 方法, 并且对该方法添加装饰器.
@classmethod
def as_view(cls, *args, **kwargs):
# 重写之后, 不对该方法做其他额外操作,所以我们重新调用父类的该方法.
view = super().as_view(*args, **kwargs)
# 对父类传过来的view方法添加装饰器.
view = my_decorator(view)
return view
我们原来的类视图代码:
# 让我们的类视图继承自扩展类
class DemoView(BaseView):
def get(self, request):
print('get')
return HttpResponse('get func')
def post(self, request):
print('post')
return HttpResponse('post func')
演示效果:
结论:
- 我们发现, 经过中间一层额外扩展类的装饰过滤, 我们原来的DemoView中的所有视图方法是能够经过装饰器的.
那么我们可以继续思考:
类视图 —————> 继承自View类 (包含有as_view函数)
类视图 ———> 继承自额外扩展的类1 —> 继承自额外扩展的类2 ———> 继承自View类(包含有as_view函数)
延伸:
我们定义两个扩展类, 并且重写两次 as_view 方法, 来看看会发生什么 :
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 定义的第一个装饰器:
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 定义的第二个装饰器:
def my_decorator2(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了22222')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 额外增加的第一个扩展类
class BaseView(View):
# 第一次重写父类中的as_view方法
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
# 对获取的view第一次添加装饰器
view = my_decorator(view)
return view
# 额外增加的第二个扩展类
class Base2View(View):
# 第二次重写父类中的as_view方法
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
# 对获取的view进行第二次添加装饰器
view = my_decorator2(view)
return view
# 我们定义的类视图, 继承自两个额外增加的类
class DemoView(BaseView, Base2View):
# 类视图中的get方法
def get(self, request):
print('get')
return HttpResponse('get func')
# 类视图中的post方法
def post(self, request):
print('post')
return HttpResponse('post func')
演示效果:
那么我们来看一下同时调用两个装饰器是怎样实现的:
请看下图:
说明:
- 如果两个扩展类的父类相同: 则两个父类都会调用
- 如果两个扩展类的父类不同: 则只会调用第一个父类
综上:
我们可以把代码变成这个样子:
# 第一个扩展类, 让他继承自object
class BaseView(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator(view)
return view
# 第二个扩展类,让他继承自object
class Base2View(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator2(view)
return view
# 类视图, 让他除了继承自这两个父类外, 最后继承View类.
class DemoView(BaseView, Base2View, View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
说明:
- 因为都是继承自object,所以扩展类中的super.as_view都会去找其他的父类依次执行,最终都会执行到View这个类这里, 所以肯定会执行View中的as_view方法.
- 如图所示:
使用Mixin扩展类,也会为类视图的所有请求方法都添加装饰行为。
六、中间件:
Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性。
我们可以使用中间件,在Django处理视图的不同阶段对输入或输出进行干预。
1. 中间件的定义方法:
定义分为三步:
1.在一个子应用中创建一个中间件文件. 例如: middleware(别的名字也可以)
2.在setting.py文件的MIDDLEWARE部分注册添加.
3.在调用视图时,便会调用中间件了.
定义一个中间件工厂函数,然后返回一个可以调用的中间件。
中间件工厂函数需要接收一个可以调用的get_response对象。
返回的中间件也是一个可以被调用的对象,并且像视图一样需要接收一个request对象参数,返回一个response对象。
# 中间件模板:
def simple_middleware(get_response):
# 此处编写的代码仅在Django第一次配置和初始化的时候执行一次。
def middleware(request):
# 此处编写的代码会在每个请求处理视图前被调用。
response = get_response(request)
# 此处编写的代码会在每个请求处理视图之后被调用。
return response
return middleware
例如,在users应用中新建一个middleware.py文件,
def my_middleware(get_response):
print('init 被调用')
def middleware(request):
print('before request 被调用')
response = get_response(request)
print('after response 被调用')
return response
return middleware
定义好中间件后,需要在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',
'users.middleware.my_middleware', # 添加中间件
]
定义一个视图进行测试:
def demo_view(request):
print('view 视图被调用')
return HttpResponse('OK')
执行结果:
注意:Django运行在调试模式下,中间件init部分有可能被调用两次。
2 多个中间件的执行顺序
- 在请求视图被处理前,中间件由上至下依次执行
- 在请求视图被处理后,中间件由下至上依次执行
示例:
定义两个中间件:
def my_middleware(get_response):
print('init 被调用')
def middleware(request):
print('before request 被调用')
response = get_response(request)
print('after response 被调用')
return response
return middleware
def my_middleware2(get_response):
print('init2 被调用')
def middleware(request):
print('before request 2 被调用')
response = get_response(request)
print('after response 2 被调用')
return response
return middleware
注册添加两个中间件
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',
'users.middleware.my_middleware', # 添加
'users.middleware.my_middleware2', # 添加
]
执行结果
init2 被调用
init 被调用
before request 被调用
before request 2 被调用
view 视图被调用
after response 2 被调用
after response 被调用