django的View类是如何工作的

时间:2013-02-24 19:47:09

标签: python django python-2.7 django-views django-generic-views

我正在深入研究Django的通用视图,弄清楚它们如何返回一个简单的HttpResponse对象,就像一个简单的视图函数一样。
我编写了一个简单的测试项目,并在文件django / views / generic / base.py中定义的基本View类中添加了一些日志记录命令,以便我可以跟踪引擎盖下发生的情况。

我在研究过程中遇到了一些问题 我一直在努力保持这篇文章的简短,但是,为了完全理解我认为必须包含代码片段和日志。
我会非常感谢那些花时间发表一些有益评论的人,可能会回答我的一些问题

urls.py

from django.conf.urls import patterns, url
from views import WelcomeView

urlpatterns = patterns('',
    url(r'^welcome/(?P<name>\w+)/$', WelcomeView.as_view()),
)


views.py

from django.http import HttpResponse
from django.views.generic import View

class WelcomeView(View):
    def get(self, request, name):
        return HttpResponse('What is up, {0}?'.format(name))


的django /视图/通用/ base.py

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', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):

        #####logging
        logging.error('*** View.__init__ is started with kwargs: {0} ***'.format(
            repr(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.iteritems():
            setattr(self, key, value)

        #####logging
        logging.error('*** View.__init__ reached its end. No return value. ***')

    @classonlymethod
    def as_view(cls, **initkwargs):

        #####logging
        logging.error('*** View.as_view is started with initkwargs: {0} ***'.format(
            repr(initkwargs)))

        """
        Main entry point for a request-response process.
        """

        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError(u"You tried to pass in the %s method name as a "
                                u"keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError(u"%s() received an invalid keyword %r" % (
                    cls.__name__, key))

        def view(request, *args, **kwargs):

            #####logging
            logging.error('*** View.as_view.view is called with args: {0};\
                and kwargs: {1} ***'.format(
                repr(args),
                repr(kwargs)))

            self = cls(**initkwargs) 
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get

            #####logging
            logging.error('*** View.as_view.view reached its end.\ 
                Now calls dispatch() and returns the return value.')

            return self.dispatch(request, *args, **kwargs)

        # 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=())

        #####logging
        logging.error('*** View.as_view reached its end. Now returns view. ***')
        return view



    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.

        #####logging
        logging.error('*** View.dispatch called, with args: {0};\
            and kwargs: {1} ***'.format(
            repr(args),
            repr(kwargs)))

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

        #####logging
        logging.error('*** View.dispatch reached its end.\ 
            Now calls handler and returns the return value. ***')
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
        logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
            extra={
                'status_code': 405,
                'request': self.request
            }
        )
        return http.HttpResponseNotAllowed(allowed_methods)


从某些测试请求中记录

Django version 1.4.5, using settings 'try1.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

ERROR:root:*** View.as_view is started with initkwargs: {} ***
ERROR:root:*** View.as_view reached its end. Now returns view. ***
ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:19] "GET /welcome/Dude/ HTTP/1.1" 200 17

ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:32] "GET /welcome/Dude/ HTTP/1.1" 200 17

[24/Feb/2013 12:44:43] "GET /welcome/ HTTP/1.1" 404 1939

ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:44:59] "GET /welcome/Bro/ HTTP/1.1" 200 16


毕竟,我的问题

1
根据日志,在View.init之前调用as_view 是否意味着它甚至在创建View实例之前调用了View方法?
2
为什么第一次调用执行后没有调用as_view()? 我还不是Python的导入,编译和内存使用方面的专家 但我觉得他们在这里扮演了一些角色
第3
在view()的定义中,以下代码片段做了什么?

self = cls(**initkwargs)

根据日志,它会触发View.init 它是否使用initkwargs创建一个新的View实例并将其分配给正在使用的实例(self)?
如果是这样,为什么需要呢?
4
我们如何使用initkwargs(as_view的参数)?

2 个答案:

答案 0 :(得分:22)

这些视图的底层实现涉及一些相当高级的Python,所以如果你是一个相对初学者,如果你发现这些代码有些混乱就不足为奇了。

  1. 您应该了解的主要内容是@classmethod装饰器对as_view()定义的作用。这意味着此方法不是普通方法,它在类的实例上调用(并将实例作为self参数),但是类方法,在类本身上调用< / em>(并将作为cls参数)。有些语言将其称为静态方法,尽管在Python中这是我们不需要进入的第三种方法。

  2. 这是因为在urlconf中定义视图的方式。您正确地放置了WelcomeView.as_view() - 这样做的目的是在导入urlconf时调用as_view classmethod

  3. 正如我们从第1点所知,cls是视图类本身。正如一个类一样,当你调用它时,你会得到一个对象。所以,正如你所说,我们在这里做的是实例化类,然后将该实例分配给一个名为self的变量,就像我们在该实例的方法中一样。这里的要点是,正如我上面所说,as_view在导入时被调用,它返回一个函数 - view - 当浏览器请求该URL时,它又被URL调度程序调用。因此,在该函数中,我们构造并调用构成基于类的视图的类的其余部分。至于为什么需要它,见下文。

  4. __init__方法负责将initargs的每个成员设置为实例属性,您可以通过常用的self.whatever语法在视图代码中访问该属性。 / p>

  5. 那么,为什么这一切都必要?

    基于类的视图带来了巨大的潜在问题,即任何直接在URLconf中实例化的类(或模块级别的任何其他类)都将在整个生命周期中持续存在。 Django通常部署的方式 - 通过WSGI - 通常意味着一个进程可以持续许多请求。如果你有多个请求持续存在的东西,你可能会遇到一些非常讨厌的线程安全漏洞 - 如果你在一个请求中设置了一个实例属性,例如,它将在后续请求中可见。

    因此,这段代码不仅可以确保每个请求都获得一个新实例,而且每次在视图函数中动态构造实例时,也很难打破这种请求隔离。

答案 1 :(得分:2)

1。 首先as_view()是class method。这是一个可以在类而不是类的实例上调用的方法。在这种情况下,您可以看到它是View上的呼叫,它是一个类而不是一个实例。

2。 加载url.conf模块时调用as_view() - 它返回函数view()。每次请求视图时都会调用此函数 - 不需要再次调用as_view

3。 在view()函数的范围内,cls变量是View类(例如DetailViewListViewView的任何子项正在调用该函数)。将类方法的第一个参数称为cls是来自PEP8的编码样式规范。它类似于我们将实例方法的第一个参数称为self的方式。所以

self = cls(**initkwargs)

基本相同

self = View(**initkwargs)self = DetailView(**initkwargs)

(取决于继承此函数的类)。

正如您所说,这是实例化类的新实例。到目前为止,还没有实例化View对象。

4。 最后,在创建类的实例时使用initkwargs。它就像添加每个键,值对作为新视图对象的属性一样简单 -

for key, value in kwargs.iteritems():
    setattr(self, key, value)