1. 前言
在之前django的学习中,有两个术语FBV和CBV。
FBV(function base views)
就是在视图里使用函数处理请求。
CBV(class base views)
就是在视图里使用类处理请求。
2. FBV
FBV就是我们常用的视图函数。通过URLconf(urls.py)找到对应的视图函数。
3. CBV
本文着重讲CBV
。
3.1 CBV和FBV用法的不同
首先,讲讲CBV相对于FBV有什么优势吧。
视图类相对于视图函数的优势就是更灵活、更易扩展。因为类可以继承啊。
Django官网例子中视图函数如下:
# views.py
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
Django官网例子中视图类如下:
# views.py
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request): # 名字对应的HTTP的请求方法,get、post、put patch、delete、 head、 options、 trace。
# <view logic>
return HttpResponse('result')
视图类对应的URLconf中的内容:
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
3.2 视图的逻辑
在上面的URLconf中视图类的as_view()
方法返回的是一个视图函数。
django.views.View
类的源码如下:
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
self.request = request
self.args = args
self.kwargs = kwargs
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
# 根据request请求的方法将此请求分派给对应的实例方法处理。
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
as_view()
方法调用了setup()
方法和dispatch()
方法。
setup()
方法是用来初始化参数的。
dispatch()
方法是根据request请求的方法来讲这个URL分派给对应的方法。例如:request.method
是’GET’,则将此请求交由def get(self, request)
处理。所以在继承的子类视图中需要我们自己定义对应的get()
实例方法、post()
实例方法等。
4. 装饰视图类
4.1 在URLconf中装饰
装饰基于类的视图最简单的方式是装饰 as_view()
方法的结果。
官方示例:
# urls.py
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = [
path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]
这种方法并不能装饰该视图类的每一个实例,只有匹配该URL才能装饰该视图类的as_view()类方法的返回值。
4.2 直接装饰视图类
通过这种方式,能够装饰该视图类的每一个实例。
官方示例:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
下面这种方式等价:
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
需要使用到多个装饰器时,有如下两种方式:
decorators = [never_cache, login_required]
@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
需要注意的是装饰的顺序时先never_cache
,再login_required
。
5. 总结
使用CBV要比FBV更好,FBV能做的事,CBV都能做,但CBV的扩展性要比FBV强的多。
6. 参考文献
[1] 【Django】Django架构流程分析
[2] Django源码
[3] 廖雪峰官方网站