基类View

 

 

尽管类视图看上去类的种类繁多,但每个类都是各司其职的,且从类的命名就可以很容易地看出这个类的功能。大致可分为如下三个大的功能块,分别由三个类提供对应的方法:

  • 处理 HTTP 请求。根据 HTTP 请求方法的不同做出相应处理。例如同一个视图函数既要处理 get 请求,又要处理 post 请求。这一块的功能由 View 类及其派生类实现。
  • 渲染模板。这一块功能由 TemplateResponseMixin 及其派生类实现。
  • 获取渲染模板所需的模板变量字典(通常称为 context),这个功能由 ContextMixin 及其派生类实现。

现在我们来看看 View 的具体实现,TemplateResponseMixin 以及ContextMixin 将在接下来的系列文章中讲解。

View

Django 类视图的核心就是这个类,这个类是所有其它类视图的基类,它定义所有类视图共有的初始化逻辑,以及一些共有的方法,以便其它类视图继承。始终记住一点,这个类的功能主要是处理不同的 HTTP 请求,因此这个类的属性和方法也是围绕这个功能点设计的。

__init__

先来看看这个类的初始化:

class View(object):
    """  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 six.iteritems(kwargs): setattr(self, key, value) 

源码中的注释其实已经清楚的说明了这个类的作用。http_method_names 属性记录 HTTP 协议所允许的全部 HTTP 方法。初始化 __init__ 方法非常简单,就是将所有传入的关键字参数 kwargs 通过 setattr(self, key, value) 设置为类实例的属性。

dispatch

接下来是一个重要的方法 dispatch,该方法会根据 HTTP 请求方法的不同而将请求转发给类视图中对应的方法处理,先来看代码实现:

    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) 

首先它通过 request.method (即 HTTP 请求的方法)判断请求的方法是否是被 HTTP 协议所允许的。如果不合法,就会调用错误处理函数 self.http_method_not_allowed;如果请求方法是合法的,就会试图根据 request.method去类中寻到对应的处理方法,如果找不到则还是委托给 self.http_method_not_allowed 处理。

可能只看代码有点糊涂,举个例子就能形象地说明 dispatch 方法的处理逻辑。假设 HTTP 请求的方法为 post,则 request.method.lower() == 'post'。此时 dispatch 将尝试调用类视图的 post 方法,并返回 post 方法调用后的值。而如果类视图中没有定义 post 方法(例如现在所说的 View 类中就没有定义),或者请求的方法不是 post而是 HTTP 协议未规定的方法如 foo,那么 dispatch 就会返回调用 http_method_not_allowed 后的结果。

事实上这个方法的处理逻辑放在视图函数中我们就再熟悉不过了:

def view(request):
    if request.method.lower() == 'get': do_something() if request.method.lower() == 'post': do_something() 

http_method_not_allowed

至于上面所说的错误处理方法则非常简单,它的代码如下:

  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 http.HttpResponseNotAllowed(self._allowed_methods()) 

即立即返回一个 HttpResponseNotAllowed,这一个 HttpResponse 对象,根据 HTTP 规定其状态码为 405,代表不允许的 HTTP 方法。

options

在 HTTP 协议规定的方法中,View 类只实现了一个,即 options方法:

    def options(self, request, *args, **kwargs): """  Handles responding to requests for the OPTIONS HTTP verb.  """ response = http.HttpResponse() response['Allow'] = ', '.join(self._allowed_methods()) response['Content-Length'] = '0' return response 

HTTP 规定客户端使用该方法查询服务器所能处理的全部 HTTP 方法,对任何视图函数来说该方法的逻辑基本是不变的,所以写在了 View 基类中,至于其它需要处理的 HTTP 方法如 post、get 等方法则由 View 的子类根据其具体功能实现。

当然 View 中还有一个辅助方法,就是返回视图类所定义的全部 HTTP 规定的方法。例如在 View 这个类中只定义了 options 方法,所以只会返回 ['options', ]

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)] 

as_view

最后剩下一个最重要的方法,即 as_view 方法。如果你曾经使用过类视图,那么最熟悉的应该就是这个方法了。要想让类视图生效,必须在 urls.py 的 URL 模式(Pattern)里做类似如下的配置:

...
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'), ] 

Django 使用如上的方式配置 URL 到对应视图函数的路由映射。注意到 url() 函数前两个位置参数需要传递的值,第一个是需要捕获的 url 的正则模式,第二个参数则是一个可调用的对象(即视图函数)。如果我们通过 def 定义视图函数,那么传入的这个可调用对象就是这个函数本身;而如果我们定义的是类视图,则必须调用类视图的 as_view 方法返回一个根据这个类生成的可调用对象。类视图所有的魔法就在这个函数里了,来看看 Django 究竟是如何神奇地把一个类转为一个函数的。

    @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.request = request self.args = args self.kwargs = kwargs 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 

as_view 方法被调用时允许传递一些关键字参数,不过需要做一个点点检查,第一防止你传入诸如 get、post 这样的关键字参数把类本身的 get、post 方法覆盖了;第二是防止你传入未定义为类属性的参数。最开始的 for 循环就是做这个事。

接下来在 as_view 方法中又定义了一个 view 方法,这个方法相信如果你经常写视图函数的话应该非常眼熟,这就是视图函数的标准定义:接收一个 HttpRequest 对象,以及从 url 捕获的非命名组和命名组参数。只不过在 view 这个视图函数里还多做了一点事,它首先实例化了一个类视图对象,然后把函数的参数设置为了这个类视图实例的属性,接着便调用了实例的 dispatch 方法返回视图函数被要求返回的 HttpResponse 对象(注意 dispatch 方法会根据 HTTP 请求方法的不同去调用对应的处理方法)。接着把类中的一些文档字符串和函数名等更新到定义的 view 函数中,然后 as_view 方法返回这个 view 函数。

所以回过头来再看一下我们的 url 模式定义:

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'), ] 

views.IndexView.as_view() 调用后返回的就是一个在 IndexView 里通过 def 定义的视图函数 view(注意所有类视图都继承自 View 基类),是不是和你直接在这里放一个视图函数是一样的?

进一步理解 View 的逻辑

你可能对这个定义在类 View 的方法 as_view 中的函数 view 的逻辑还是不理解,这里我们通过一种分离的实现方式来加深一下对它的理解。我们假设写了如下的一个视图函数:

def view(request, *args, **kwargs): if request.method.lower() == 'get': do_something() if request.method.lower() == 'post': do_something() 

我们很快发现,在很多的视图函数中都复用了这一段代码:

if request.method.lower() == 'get': do_something() if request.method.lower() == 'post': do_something() 

但是写在函数中的代码复用起来是比较麻烦的,想到代码复用,我们立即想到了类继承,于是我们定义一个辅助类:

class View(object):
    def __init__(request, *args, **kwargs): # init def get(request, *args, **kwargs)do_something() def post(request, *args, **kwargs) do_something() 

让后我们在 view 中实例化这个类并使用它:

def view(request, *args, **kwargs): view_instance = View(request, *args, **kwargs) if request.method.lower() == 'get': view_instance.get(request, *args, **kwargs) if request.method.lower() == 'post': view_instance.post(request, *args, **kwargs) 

可以看到,这个辅助的 View 类就充当了上述所分析的类视图 View 的功能,而这个视图函数 view 则充当了定义在类视图 as_view 方法中的 view 函数的功能。这种设计思想就是把视图函数的逻辑定义到类的方法里面去,然后在函数中实例化这个类,通过调用类的方法实现函数逻辑,而把逻辑定义在类中的一个好处就是可以通过继承复用这些方法。但是像上述这种函数与类分离的实现方式很麻烦且不优雅,直接把 view 定义在类里,就是 Django 类视图的实现方式了。

总结

现在我们已经明白了类视图的基本结构,其主要功能就是根据 HTTP 请求方法的不同做出相应处理,具体的实现为 dispatch 方法。类视图的核心思想就是把视图函数的逻辑定义到类的方法里面去,然后在函数中实例化这个类,通过调用类的方法实现函数逻辑。基类 View 定义了所有类视图的基本逻辑框架,接下来我们会继续分析一系列基于这个基类 View 定义的更加具体的通用类视图。

转载于:https://www.cnblogs.com/ellisonzhang/p/10656264.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值