• 基类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 定义的更加具体的通用类视图。

  • 相关阅读:
    jQuery选择器
    有关ssh的理解
    前端WEB开发人员
    @antdesign/charts 解决echarts图宽高自适应问题,设置100%宽高显示异常
    npm run lintstaged:js报错
    node版本工具 nvm
    nginx域名隐性(地址栏域名不变)跳转
    mysql本地指定loginpath免密登录(mysql_config_editor的用法)
    Git管理查看自己是从那个分支建的分支(什么时间创建的)
    隐藏java代码中 连接数据库、redis等含密码信息方法【配置文件信息安全加密】
  • 原文地址:https://www.cnblogs.com/ellisonzhang/p/10656264.html
Copyright © 2020-2023  润新知