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非常适合于快速开发。
微信分享/微信扫码阅读