一、FBV和CBV
在开始讲as_view()函数前,先来聊一聊django里两种不同的视图模式,他们分别是FBV和CBV。
1、FBV:基于函数的视图
urlpatterns = [
path('demo/', views.demo),
path('admin/', admin.site.urls),
]
def demo(request):
return HttpResponse('demo')
在基于函数的视图里,一个URL对应一个视图函数,优点是简单易懂,缺点是难以复用。
2、FCB:基于类的视图
urlpatterns = [
path('DemoView/', views.DemoView.as_view()),
]
class DemoView(View):
def get(self, request):
return HttpResponse('get')
在FCB模式中,视图是基于自定义类的,但类必须继承自View类。而as_view()方法是View中的一个类方法,它会根据请求的Method方法自动调用相应的函数进行处理。
二、手动实现as_view()方法
as_view()
的核心流程:
as_view()
内部定义了view()
函数。view()
函数对类视图进行初始化,调用并返回了dispatch()
方法。dispatch()
根据请求类型的不同,调用不同的函数(如get()
、post()
),并将这些函数的response
响应结果返回。as_view()
返回了这个view
函数闭包,供path()
路由调用。
class DemoView():
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
return view
def dispatch(self, request, *args, **kwargs):
handler = getattr(
self, request.method.lower()
)
return handler(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get')
as_view方法在Django启动时就会被调用,那么对应的URL实际上就和view方法进行了绑定,每一次对该URL的请求都会调用view方法。
三、as_view()源码解析
class DemoView(View):
def get(self, request):
return HttpResponse('get')
在View类中定义了as_view()方法,下面是View中的as_view()方法。
@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(
"The method name %s is not accepted as a keyword argument "
"to %s()." % (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)
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
# __name__ and __qualname__ are intentionally left unchanged as
# view_class should be used to robustly determine the name of the view
# instead.
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__annotations__ = cls.dispatch.__annotations__
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
# the dispatch method.
view.__dict__.update(cls.dispatch.__dict__)
# Mark the callback if the view class is async.
if cls.view_is_async:
view._is_coroutine = asyncio.coroutines._is_coroutine
return view
在View类的as_view()方法中先是对参数进行校验,先是校验接收到的请求中的method是不是合法,再是校验对应的方法是否已实现,接下来定义view方法,最后返回view,这样每一次的URL请求就会由view进行处理,而view接收的参数和普通的函数视图是一样的,也是包括request对象以及从url获取的args和kwargs参数。
在view方法中先是调用cls(**initkwargs)实例化类,并赋值给self,也就是自己编写的类视图的实例。
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, "get") and not hasattr(self, "head"):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
接着调用self.setup(request, *args, **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:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
view方法最后调用了dispatch()方法,dispatch()非常简短,但它是最核心的地方,先是获取请求中的method,再利用反射调用类视图中相应的方法。
回到as_view(),它最后做了属性赋值、修改函数签名等收尾工作后,返回了view函数闭包。