通常在Django框架中写view是这样的:
def book1(request):
if request.method == "GET":
return HttpResponse("GET...")
if request.method == "POST":
return HttpResponse("POST...")def book1(request):
if request.method == "GET":
return HttpResponse("GET...")
if request.method == "POST":
return HttpResponse("POST...")
这是传统FBV即基于函数的视图写法,定义函数,接受一个request,然后返回一个response对象,阅读过"wsgi源码","Django使用WSGI","Django的启动过程"这几篇文章的朋友就会知道:wsgi需要一个application函数对象(其实是类,没有实例化的类,就是函数名不加括号)给WSGI调用,WSGI会将environ及start_response传给这个application函数并调用,这个application在django中就是WSGIHandler类里的__call__方法。
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
response._handler_class = self.__class__
status = "%d %s" % (response.status_code, response.reason_phrase)
response_headers = [
*response.items(),
*(("Set-Cookie", c.OutputString()) for c in response.cookies.values()),
]
start_response(status, response_headers)
if getattr(response, "file_to_stream", None) is not None and environ.get(
"wsgi.file_wrapper"
):
# If `wsgi.file_wrapper` is used the WSGI server does not call
# .close on the response, but on the file wrapper. Patch it to use
# response.close instead which takes care of closing all files.
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
return response
其中有一个关键调用response = self.get_response(request) 在这个__call__中会调用get_response(request),这个request就会最终传给我们写的view函数。 在self.get_response(request)层层调用过程中,通过层层中间件校验来到最后一个中间件时,这个中间件会根据url匹配对应的view,它也只返回函数对象,最终在这里self.get_response(request)拿到这个view进行调用,并传了request。所以在定义完view后url是这样写的:
from django.contrib import admin
from django.urls import path
from users import views
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', views.hello, name='hello'),
path('book1/', views.book1, name='book1')
]
在url里只要填写函数对象就行,不要调用它。wsgi只认function类(函数对象)。你给其他的会报错。基于这个原因,不管你怎么去实现这个视图,最终都是函数对象。 但是在开发中如果都是FBV,功能是没问题的,写着写着发现很多重复,于是就于类来实现,这个基于类的实现django已经写好了,在django.views 中有个View类。 示例:
from django.http import HttpResponse
from django.views import View
class BookView(View):
def post(self, request):
return HttpResponse("view post ...")
url里:
path('book2/', views.BookView.as_view(), name='book1')
在url是通过调用as_view()来注入这个函数对象,我们来看下这个as_view()那边是否会返回一个函数对象。进入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)
@classproperty
def view_is_async(cls):
handlers = [
getattr(cls, method)
for method in cls.http_method_names
if (method != "options" and hasattr(cls, method))
]
if not handlers:
return False
is_async = iscoroutinefunction(handlers[0])
if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]):
raise ImproperlyConfigured(
f"{cls.__qualname__} HTTP handlers must either be all sync or all "
"async."
)
return is_async
@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:
markcoroutinefunction(view)
return view
由于我们直接BookView.as_view()直接调用as_view方法,没有传入任何值,下面代码就跳过
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)
分析: (1)首先是通过cls实例化一个实例,这个给了临时变量self,这个cls就是我们定义的BookView类,因为外部是BookView类调用了as_view方法。 (2)接着调用了BookView类继承的setup方法,主要是将request注入给self实例,所有能获取到的参数也注入,这一步相当于初始化,同时做了拦截,如果self实例有get属性或者方法且没有head属性或者方法,那就将get属性或者方法给head
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
(3)调用了self的dispatch方法 这个方法 在继承的View类有定义,在as_view方法最后返回的是view函数对象,符合url要求。 return self.dispatch(request, *args, **kwargs),这里其实就是闭包了
(4)dispatch方法
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.
method = request.method.lower()
if method in self.http_method_names:
handler = getattr(self, method, 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):
response = HttpResponseNotAllowed(self._allowed_methods())
log_response(
"Method Not Allowed (%s): %s",
request.method,
request.path,
response=response,
request=request,
)
if self.view_is_async:
async def func():
return response
return func()
else:
return response
在dispatch方法里: 检验请求方法是否在self的http_method_names里(View类已经定义好),如果self实例没有这个方法,调用http_method_not_allowed方法返回response,如果self实例有这个方法,调用这个方法返回response。
接下来就是给view函数对象补齐函数对象一些方法。当外部调用view时,错误信息,包括属于哪个类等就是从下面注入的信息查询。
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 atributes 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:
markcoroutinefunction(view)
所以BookView.as_view()返回的是一个函数对象,它里面会根据调用的类来实例化一个对象。表面上cls是个局部变量,其实它扔进了view函数对象里的__closure__了。每一个url里通过as_view()调用会保存一个调用类(cls),通过这个类去创建临时对象self,再通过self.dispatch方法去调用对应方法获得response。相较于FBV,as_view()多了一层创建临时实例内存消耗,但是额外消耗还是比较微末的。
评论
0 条