Django中基于类的视图的实现原理

之前写基于类的函数,但并没有真正弄懂其实现原理,因此今天就主要分析一下基于类的视图是如何实现的, 首先我的Django博客中一个视图源码:

class IndexView(ListView):
    template_name = 'index.html'
    paginate_by = NUM_PER_PAGE
    context_object_name = 'article_list'

    def get_queryset(self):
        self.article_list =  Article.objects.filter(status=0)
        return self.article_list

    def get_context_data(self,*args,**kwargs):
        context = super(IndexView,self).get_context_data(**kwargs)
        context['article_list_length'] = len(self.article_list)
        return context

在URL配置:url(r'^$',IndexView.as_view(),name='index-view'),

当访问'/'时,就会执行IndexView.as_view()方法。那么这个流程大体上是什么样呢,现在就看下源代码。
IndexView继承自父类ListView

class ListView(MultipleObjectTemplateResponseMixin, BaseListView):

可以看出,ListView自称自BaseListView和MultipleObjectTemplateResponseMixin,先不考虑后者,它是关于如何一个基于模板的响应。

class BaseListView(MultipleObjectMixin, View):
    """
    A base view for displaying a list of objects.
    """
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if (self.get_paginate_by(self.object_list) is not None
                    and hasattr(self.object_list, 'exists')):
                is_empty = not self.object_list.exists()
            else:
                is_empty = len(self.object_list) == 0
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.")
                        % {'class_name': self.__class__.__name__})
        context = self.get_context_data()
        return self.render_to_response(context)

这个类里面有一个get方法,首先执行 self.object_list = self.get_queryset(),也就是我们在项目中定义的get_queryset(),得到查询集。 最后context = self.get_context_data(),这会返回响应上下文。
那么此时问题来了,self.object_list在这里面有什么用呢?
这时就要看父类MultipleObjectMixin:

class MultipleObjectMixin(ContextMixin):
    """
    A mixin for views manipulating multiple objects.
    """
    allow_empty = True
    queryset = None
    model = None
    paginate_by = None
    paginate_orphans = 0
    context_object_name = None
    paginator_class = Paginator
    page_kwarg = 'page'
    ordering = None

    def get_context_data(self, **kwargs):
        """
        Get the context for this view.
        """
        queryset = kwargs.pop('object_list', self.object_list)
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {
                'paginator': paginator,
                'page_obj': page,
                'is_paginated': is_paginated,
                'object_list': queryset
            }
        else:
            context = {
                'paginator': None,
                'page_obj': None,
                'is_paginated': False,
                'object_list': queryset
            }
        if context_object_name is not None:
            context[context_object_name] = queryset
        context.update(kwargs)
        return super(MultipleObjectMixin, self).get_context_data(**context)

看到这句queryset = kwargs.pop('object_list', self.object_list),这类方法会将该类加到上下文中context[context_object_name] = queryset。

看到这里应该明白了吧,我们自定义的子类中,get_queryset是为了得到object_list,get_context_data是为了额外添加上下文信息。
说到这里,还有一个问题,这个函数最初是怎么调用的呢?
在url设置中是定义IndexView.as_view(),那么咱们再看ListView的另一个父类View。

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']


    @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

    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)

as_view()会调用view()函数,最终调用dispatch方法。

handler = getattr(self, request.method.lower()
handler(request, *args, **kwargs)

如果是get请求,这个最终会调用BaseListView中的get方法。然后开始我们上面说的执行过程。

上面就是基于类的视图的一个实现原理。学了这么久,感觉Django非常适合于快速开发。

--------EOF---------
微信分享/微信扫码阅读