继承方式
- ListView(MultipleObjectTemplateResponseMixin, BaseListView)
- MultipleObjectTemplateResponseMixin
- TemplateResponseMixin
- BaseListView
- MultipleObjectMixin
- ContextMixin
- View
- MultipleObjectMixin
- MultipleObjectTemplateResponseMixin
graph TD
ListView[ListView] --> |依赖其生成模板名称的函数| MultipleObjectTemplateResponseMixin(MultipleObjectTemplateResponseMixin)
ListView --> |依赖其中的get方法以及返回的查询信息| BaseListView(BaseListView)
MultipleObjectTemplateResponseMixin --> |依赖其返回渲染模板的函数| TemplateResponseMixin(TemplateResponseMixin)
BaseListView --> |依赖其中返回的查询信息| MultipleObjectMixin(MultipleObjectMixin)
BaseListView --> |依赖其中初始化函数以及访问方法的调用|View(View)
MultipleObjectMixin -->|依赖及重写其中的获取数据函数| ContextMixin(ContextMixin)
- 第一次发博客,不知道这个里面的关系图语法是怎么样的,笔记里粘过来就这样了。哪位大神会用麻烦告诉我一下,谢谢~
ListView处理过程
url会将request对象和其他用户定义参数传入ListView,View父类负责执行初始化方法,将用户定义变量添加到类属性中,并调用as_view方法查找对应请求方式的处理函数(BaseListView中的get方法)。get方法执行是否分页操作的判断语句,并调用MultipleObjectMixin类中的get_context_data方法(父类ContextMixin)获取查询数据,然后调用TemplateResponseMixin中的render_to_response方法返回渲染后的模板。 注意,TemplateResponseMixin中需要的模板信息是在MultipleObjectTemplateResponseMixin类中定义的。
ListView类使用了多重继承,每重继承定义了对应父类的参数信息,极大的降低了耦合性。
使用方法
常用的参数:
- model,queryset
指定查询的模块或查询集 - context_object_name
指定返回的上下文名称 - template_name
指定需要渲染的模板文件
返回结果
返回指定的查询集,并渲染模板
代码解析
ContextMixin
主要参数
- extra_context 添加额外的自定义数据,格式为字典
功能函数
- get_context_data 返回上下文数据。添加’view’关键字,内容为self中所有数据(参数),并追加extra_context内容
代码
class ContextMixin:
"""
A default context mixin that passes the keyword arguments received by
get_context_data() as the template context.
"""
extra_context = None
def get_context_data(self, **kwargs):
if 'view' not in kwargs:
kwargs['view'] = self
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
MultipleObjectMixin
继承
继承自ContextMixin
主要参数
- 定义了查询对象(queryset,model)
- 定义分页(paginate_by,paginate_orphans)
- 定义了上下文返回的名字(context_object_name)
- 定义了排序标准(ordering)
- 定义是否渲染模板(get_allow_empty)(此处不确认)
功能函数
- get_queryset
返回数据查询集,并排序(默认不排序)。此处queryset与model必须定义一个,否则会引发异常。如果指定了model,那么使用默认manager进行查询(models中自定义的第一个manager为默认manager,如未指定,默认为object) - get_ordering
返回指定的排序,ordering的值 - paginate_queryset get_paginate_by get_paginator get_paginate_orphans
分页相关 - get_allow_empty
返回get_allow_empty的值,True或False。默认为True - get_context_object_name
返回上下文的名称,context_object_name的值。默认为’数据对象’_list - get_context_data
数返回数据,调用(重写get_context_data)父类’ContextMixin’,并根据参数执行分页。数据包含了“数据库查询数据”和分页相关信息(是否分页,分页数等)。
代码
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_queryset(self):
"""
Return the list of items for this view.
The return value must be an iterable and may be an instance of
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
"""
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, str):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)
return queryset
def get_ordering(self):
"""Return the field or fields to use for ordering the queryset."""
return self.ordering
def paginate_queryset(self, queryset, page_size):
"""Paginate the queryset, if needed."""
paginator = self.get_paginator(
queryset, page_size, orphans=self.get_paginate_orphans(),
allow_empty_first_page=self.get_allow_empty())
page_kwarg = self.page_kwarg
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
try:
page_number = int(page)
except ValueError:
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
try:
page = paginator.page(page_number)
return (paginator, page, page.object_list, page.has_other_pages())
except InvalidPage as e:
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
'page_number': page_number,
'message': str(e)
})
def get_paginate_by(self, queryset):
"""
Get the number of items to paginate by, or ``None`` for no pagination.
"""
return self.paginate_by
def get_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs):
"""Return an instance of the paginator for this view."""
return self.paginator_class(
queryset, per_page, orphans=orphans,
allow_empty_first_page=allow_empty_first_page, **kwargs)
def get_paginate_orphans(self):
"""
Return the maximum number of orphans extend the last page by when
paginating.
"""
return self.paginate_orphans
def get_allow_empty(self):
"""
Return ``True`` if the view should display empty lists and ``False``
if a 404 should be raised instead.
"""
return self.allow_empty
def get_context_object_name(self, object_list):
"""Get the name of the item to be used in the context."""
if self.context_object_name:
return self.context_object_name
elif hasattr(object_list, 'model'):
return '%s_list' % object_list.model._meta.model_name
else:
return None
def get_context_data(self, *, object_list=None, **kwargs):
"""Get the context for this view."""
queryset = object_list if object_list is not None else 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().get_context_data(**context)
View
主要参数
- http_method_names http访问方式。包含了GET、POST等
功能函数
- init 使用setattr将所有初始化参数设置为类属性
- as_view 使用python闭包实现,添加了request对象和传入参数到类属性中,闭包内调用dispatch函数。并手动调用了update_wrapper函数,以保持函数签名不变。
- dispatch 返回处理请求的函数,此函数为继承后用户定义,匹配http_method_names中声明的名称。如果对应请求类型的函数不存在,则触发http_method_not_allowed函数,返回状态吗405
- http_method_not_allowed 拒绝请求函数。打印错误日志,返回状态吗405
- options 为response添加头信息。调用_allowed_methods函数添加’ALLOW‘标签。
- _allowed_methods 将http_method_names列表中的请求方式改为大写字母
代码
class View:
"""
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 kwargs.items():
setattr(self, key, value)
@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)
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 HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
BaseListView
继承
继承自 MultipleObjectMixin 和 View
功能函数
- get 此视图仅响应GET请求,返回查询数据。判断时候允许展示,如不允许返回状态码404。
代码
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)
MultipleObjectTemplateResponseMixin
继承
继承自TemplateResponseMixin
主要参数
- template_name_suffix 定义模板名称的后缀
功能函数
- get_template_names 以列表形式返回模板名称。该函数首先尝试调用父方法,以兼容用户声明template_name的情况。后追加此函数定义的模板名,即“app名称”+“模块名”+“模板名称后缀”.html
代码
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""Mixin for responding with a template and list of objects."""
template_name_suffix = '_list'
def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response is overridden.
"""
try:
names = super().get_template_names()
except ImproperlyConfigured:
# If template_name isn't specified, it's not a problem --
# we just start with an empty list.
names = []
# If the list is a queryset, we'll invent a template name based on the
# app and model name. This name gets put at the end of the template
# name list so that user-supplied names override the automatically-
# generated ones.
if hasattr(self.object_list, 'model'):
opts = self.object_list.model._meta
names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
return names
TemplateResponseMixin
主要参数
- template_name
定义模板名称 - template_engine 定义模板引擎
- response_class 定义返回函数,默认为TemplateResponse
- content_type 定义返回类型
功能函数
- render_to_response 调用response_class返回渲染后的模板。
- get_template_names 返回模板名称。如用户未指定则触发异常
代码
class TemplateResponseMixin:
"""A mixin that can be used to render a template."""
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
def render_to_response(self, context, **response_kwargs):
"""
Return a response, using the `response_class` for this view, with a
template rendered with the given context.
Pass response_kwargs to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)
def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response() is overridden.
"""
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]