1.通用视图基类
generic/base.py
class View: # 请求反射,对应到视图处理类定义的处理方法
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs
# **kwargs:来自于url中as_view()方法中的参数
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)
# cls:url中视图处理类
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):
# 请求通过反射对应到我们视图处理类内部定义的方法
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):
# 返回请求方法
# hasattr(self, m) :判断我们定义的视图处理类是否实现了http_method_names初始定义的方法
# ['GET', 'POST', 'HEAD', 'OPTIONS']
# 循环列表内允许的请求方法,如果在我们定义的类视图内部实现了该方法,则将其返回
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
- 请求进来首先调用
url
中的as_view()
方法,该方法是一个闭包,将我们视图处理类以及其属性传递给view
函数,然后将request请求对象以及这些参数传递给dispatch
方法,该方法匹配请求方式method
(get、post、delete、put…),匹配成功带着这些参数返回,我们在类试图中只需重写这些方法中我们用到的即可,需要自己实现response
方法,该类是所有通用视图的基类。 - 该方法没有对模板进行处理,在前后端不分离项目中,我们需要自己处理模板。
class ContextMixin: # 获取额外传递的变量
"""接受额外传递变量保存到全局上下文中"""
extra_context = None
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
- 该方法比较简单,主要用来接收需要额数据对象,将其保存在全局上下文
context
中,方便后续在模板中使用。
class TemplateResponseMixin: # 将全局上下文对象渲染到模板中,将其展示给前端
"""返回模板"""
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]
- 该方法重点实现了
render_to_response
方法,封装response
方法,将数据渲染到模板并返回,我们继承他后只需要给定模板名即可,不需要我们再对模板单独处理,弥补了view
视图的不足。 - 注意,
render_to_response
方法中的context
数据对象需要在调用该方法时进行传递。
class TemplateView(TemplateResponseMixin, ContextMixin, View): # 通过指定请求方式,将数据传递给TemplateResponseMixin基类中的render_to_response方法
"""
接受数据,渲染模板
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
- 该方法继承了以上三个基类
- 重写模板名称
template_name
覆盖TemplateResponseMixin
中的默认名称即可实现模板的返回 - 重写请求方法
method
(get、post、delete、update、put…)来自view
视图,处理对应请求 - 重写
get_context_data
方法来自ContextMixin
视图,实现额外变量的传递 - 最终通过
TemplateResponseMixin
模板中的render_to_response
端方法实现数据渲染并返回 - 该基类可以直接使用在
url
中,当我们只需要返回一个页面,不需要任何数据填充时,这样做时很好的选择:如下所示 path('', TemplateView.as_view(template_name='user/login.html'), name='login')
class RedirectView(View): # 对所有请求提供重定向服务
"""对所有请求提供重定向服务"""
permanent = False
url = None
pattern_name = None
query_string = False
def get_redirect_url(self, *args, **kwargs):
"""
将获取到的url参数作为查询关键字添加进url中
"""
if self.url:
url = self.url % kwargs
elif self.pattern_name:
url = reverse(self.pattern_name, args=args, kwargs=kwargs)
else:
return None
args = self.request.META.get('QUERY_STRING', '')
if args and self.query_string:
url = "%s?%s" % (url, args)
return url
def get(self, request, *args, **kwargs):
url = self.get_redirect_url(*args, **kwargs)
if url:
if self.permanent:
return HttpResponsePermanentRedirect(url)
else:
return HttpResponseRedirect(url)
else:
logger.warning(
'Gone: %s', request.path,
extra={'status_code': 410, 'request': request}
)
return HttpResponseGone()
def head(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def options(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
- 继承view重写所有请求方法,匹配成功后根据请求路径跳转到对应视图去处理
path('', RedirectView.as_view(url='index/'), name='index')
2.通用视图展示类
1. ListView
generic/list.py
class MultipleObjectMixin(ContextMixin): # 排序、分页、额外数据添加到全局上下文
"""A mixin for views manipulating multiple objects."""
allow_empty = True
queryset = None # 查询集如:book.objects.all()
model = None # 要查询的模型对象
paginate_by = None # 每页显示数据量
paginate_orphans = 0
context_object_name = None # 传递给模板全局上下文的别名,默认object_list
paginator_class = Paginator
page_kwarg = 'page' # 分页参数即:url中?后面显示内容
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
# 继承自ContextMixin,获取需要额外传递给模板的变量
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)
-
继承该类主要实现数据列表展示(
queryset
数据集对象) -
继承该类后,我们只需要传递变量
queryset
对象或者model
对象到我们的视图类中即可 -
如果要实现分页可指定
paginate_by
-
如果要实现排序可指定
ordering
-
如果要添加额外数据对象,只需重写
get_context_data
方法 -
如果要对展示数据进行过滤,只需重写
get_queryset
方法 -
**重点**
:该类不能单独使用,因为其没有实现response
方法 -
如果想实现数据和模板分离,则只需配合
view
类一起使用,重写请求方法method
,在方法内实现response
方法即可 -
如果想混合模板一起使用,可配合
TemplateResponseMixin
即可,该类中的render_to_response
实现了response
方法 -
分页使用
{% with paginator=page_obj.paginator %}
{% if paginator.count > 0 %}
<nav aria-label="Page navigation">
<ul class="pagination">
previous button
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}"
aria-label="Previous">«</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" aria-label="Previous">«</a>
</li>
{% endif %}
page button
{% for i in paginator.page_range|custom_page_range:page_obj.number %}
{% if page_obj.number == i %}
<li class="page-item active" aria-current="page">
<span class="page-link">{{ i }} <span class="sr-only">current page</span></span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
</li>
{% endif %}
{% endfor %}
next button
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">»</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">»</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endwith %}
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 = not self.object_list
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)
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""Mixin for responding with a template and list of objects."""
# template_name = None 来自TemplateResponseMixin类
template_name_suffix = '_list'
def get_template_names(self): # 继承自TemplateResponseMixin,重写了该方法
"""
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))
elif not names:
raise ImproperlyConfigured(
"%(cls)s requires either a 'template_name' attribute "
"or a get_queryset() method that returns a QuerySet." % {
'cls': self.__class__.__name__,
}
)
return names
- 该类实现了一个视图可以同时处理渲染多个模板
- 示例
TAB_SUMMARY = 'summary'
TAB_VOLUME = 'volume'
TAB_NETWORKING = 'networking'
TAB_BACKUP = 'backup'
TAB_SNAPSHOT = 'snapshot'
TABS = (TAB_SUMMARY, TAB_VOLUME, TAB_NETWORKING, TAB_BACKUP, TAB_SNAPSHOT)
class xxxxxxxx(MultipleObjectTemplateResponseMixin, ContextMixin):
def get_template_names(self):
# url请求参数,根据该参数返回对应模板,如果不在定义范围内,抛出错误
instance_tab = self.kwargs.get('instance_tab')
name = 'client20/dashboard/service/instance_detail_%s.html'
if instance_tab in TABS:
template_name = name % instance_tab
return [template_name]
else:
raise Http404
def get_context_data(self, **kwargs):
# 获取全局上下文对象
context = super().get_context_data(**kwargs)
instance_tab = self.kwargs.get('instance_tab') # url查询字符串
# 根据请求参数获取对应context数据
context = self.__getattribute__('_get_%s_context' % instance_tab)(context)
return context
def _get_volume_context(self, context):
context.update(volumes=self.object.volume_set.all().order_by('-is_sys'))
return context
def _get_summary_context(self, context):
...
return context
....
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
pass
BaseListView
基类主要负责数据处理MultipleObjectTemplateResponseMixin
基类主要负责模板渲染,和response
方法
2.DetailView
generic/detail.py
class SingleObjectMixin(ContextMixin): # 返回数据对象具体信息
"""
Provide the ability to retrieve a single object for further manipulation.
"""
model = None
queryset = None
slug_field = 'slug' # 查询字符串对应的模型字段
context_object_name = None
slug_url_kwarg = 'slug' # 查询字符串
pk_url_kwarg = 'pk' # 查询主键,以id作为过滤条件
query_pk_and_slug = False
def get_object(self, queryset=None): # 返回结果集经过条件筛选查询后得到的对象, 最好指定pk_url_kwarg而非query_pk_and_slug,值为url查询字符串
"""
Return the object the view is displaying.
Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
Subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
# 如果queryset没指定,则从model获取
if queryset is None:
queryset = self.get_queryset()
# self.kwargs:来自as_view()方法中的view函数
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError(
"Generic detail view %s must be called with either an object "
"pk or a slug in the URLconf." % self.__class__.__name__
)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_queryset(self): # 获取查询结果集
"""
Return the `QuerySet` that will be used to look up the object.
This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return 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__
}
)
return self.queryset.all()
def get_slug_field(self):
"""Get the name of a slug field to be used to look up by slug."""
return self.slug_field
def get_context_object_name(self, obj): # 查询对象绑定变量名,用于模板循环
"""Get the name to use for the object."""
if self.context_object_name:
return self.context_object_name
elif isinstance(obj, models.Model):
return obj._meta.model_name
else:
return None
def get_context_data(self, **kwargs): # 渲染模板
"""Insert the single object into the context dict."""
context = {}
if self.object:
context['object'] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
context.update(kwargs)
return super().get_context_data(**context)
- 继承该类主要实现单个数据对象展示
- 继承该类后,我们需要传递变量
queryset
对象或者model
对象到我们的视图类中 pk_url_kwarg
,主键过滤
path('backup/<pk:backup_id>/detail/', backup.BackupDetail.as_view(), name='backup_detail'),
pk_url_kwarg = 'backup_id'
- 如果指定了
slug_url_kwarg
,则以该名称作为过滤条件,通常配合slug_field
(指明slug_url_kwarg
给定的名称对应到model
中哪个字段)一起使用。
path('volume/<slug:volume_uuid>/delete/by_message/', executer.VolumeDelete.as_view()),
slug_field = 'uuid' # 根据模型中的uuid字段进行过滤,而非主键id
slug_url_kwarg = 'volume_uuid'
- 注意:以上两中方式,必须指定其中一个字段作为过滤条件,否则会抛出异常
ImproperlyConfigured
- 如果要添加额外数据对象,只需重写
get_context_data
方法 - 如果要对数据进行处理,只需重写
get_object
方法 **重点**
:该类不能单独使用,因为其没有实现response
方法- 如果想实现数据和模板分离,则只需配合
view
类一起使用,重写请求方法method
,在方法内实现response
方法即可 - 如果想混合模板一起使用,可配合
TemplateResponseMixin
即可,该类中的render_to_response
实现了response
方法
class BaseDetailView(SingleObjectMixin, View): # 在上述基础上集成View对象,反射得到请求方法,将数据返回
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
template_name_field = None
template_name_suffix = '_detail'
def get_template_names(self):
"""
Return a list of template names to be used for the request. May not be
called if render_to_response() is overridden. Return the following list:
* the value of ``template_name`` on the view (if provided)
* the contents of the ``template_name_field`` field on the
object instance that the view is operating upon (if available)
* ``<app_label>/<model_name><template_name_suffix>.html``
"""
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 self.template_name_field is set, grab the value of the field
# of that name from the object; this is the most specific template
# name, if given.
if self.object and self.template_name_field:
name = getattr(self.object, self.template_name_field, None)
if name:
names.insert(0, name)
# The least-specific option is the default <app>/<model>_detail.html;
# only use this if the object in question is a model.
if isinstance(self.object, models.Model):
object_meta = self.object._meta
names.append("%s/%s%s.html" % (
object_meta.app_label,
object_meta.model_name,
self.template_name_suffix
))
elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model):
names.append("%s/%s%s.html" % (
self.model._meta.app_label,
self.model._meta.model_name,
self.template_name_suffix
))
# If we still haven't managed to find any template names, we should
# re-raise the ImproperlyConfigured to alert the user.
if not names:
raise
return names
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
pass
BaseDetailView
基类主要负责数据处理SingleObjectTemplateResponseMixin
基类主要负责模板渲染,和response
方法- 一般情况下只需重写
get_object
方法
3.通用视图编辑类
generic/edit.py
1. FormView
class FormMixin(ContextMixin):
"""Provide a way to show and handle a form in a request."""
initial = {}
form_class = None
success_url = None
prefix = None
def get_initial(self):
"""Return the initial data to use for forms on this view."""
return self.initial.copy()
def get_prefix(self):
"""Return the prefix to use for forms."""
return self.prefix
def get_form_class(self): # 获取form验证器
"""Return the form class to use."""
return self.form_class
def get_form(self, form_class=None): # 使用form验证器对象对请求参数进行处理
"""Return an instance of the form to be used in this view."""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs())
def get_form_kwargs(self): # 获取请求参数并返回
"""Return the keyword arguments for instantiating the form."""
kwargs = {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_success_url(self): # 请求成功需要跳转的地址
"""Return the URL to redirect to after processing a valid form."""
if not self.success_url:
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
return str(self.success_url) # success_url may be lazy
def form_valid(self, form): # 钩子方法
"""If the form is valid, redirect to the supplied URL."""
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form): # 验证失败
"""If the form is invalid, render the invalid form."""
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
"""Insert the form into the context dict."""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super().get_context_data(**kwargs)
- 创建
Form
验证器,更新全局上下文
class ModelFormMixin(FormMixin, SingleObjectMixin): # 重写is_valid方法,保存请求数据,跳转链接
"""Provide a way to show and handle a ModelForm in a request."""
fields = None
def get_form_class(self): # 获取form验证器
"""Return the form class to use in this view."""
if self.fields is not None and self.form_class:
raise ImproperlyConfigured(
"Specifying both 'fields' and 'form_class' is not permitted."
)
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif getattr(self, 'object', None) is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is prohibited." % self.__class__.__name__
)
return model_forms.modelform_factory(model, fields=self.fields)
def get_form_kwargs(self): # 获取请求参数
"""Return the keyword arguments for instantiating the form."""
kwargs = super().get_form_kwargs()
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
def get_success_url(self): # 请求成功跳转url
"""Return the URL to redirect to after processing a valid form."""
if self.success_url:
url = self.success_url.format(**self.object.__dict__)
else:
try:
url = self.object.get_absolute_url()
except AttributeError:
raise ImproperlyConfigured(
"No URL to redirect to. Either provide a url or define"
" a get_absolute_url method on the Model.")
return url
def form_valid(self, form): # 保存请求数据
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
- 继承了
SingleObjectMixin
类 - 使用
Form
验证器和model
对象构建Form
表单 - 验证通过将数据保存进
model
对象,并返返回到success_url
- 不能单独使用,没有实现
response
方法
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form() # 获取form验证器
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
- 不能单独使用,需要结合
FormMixin
提供Form
验证器 - 通过请求方法
POST
和PUT
方法实现数据处理
class BaseFormView(FormMixin, ProcessFormView):
"""A base view for displaying a form."""
- 可重写
post
方法和put
方法 - 可重写验证器的所有方法
class FormView(TemplateResponseMixin, BaseFormView): # 将请求创建成功的数据渲染到模板
"""A view for displaying a form and rendering a template response."""
TemplateResponseMixin
的render_to_response
方法实现了模板数据渲染以及返回- 可重写
post
方法和put
方法以及验证器的所有方法
2. CreateView
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating a new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = None
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super().post(request, *args, **kwargs)
ModelFormMixin
可重写get_obj
、pk_url_kwargs
…,进行数据处理- 可重写
form
验证器的所有方法,对数据进行验证,然后保存 - 可重写
post
、put
、get
等方法,结合Form表单进行相关处理
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object, with a response rendered by a template.
"""
template_name_suffix = '_form'
- 一般情况下只需重写
form_valid
方法
3. UpdateView
class BaseUpdateView(ModelFormMixin, ProcessFormView):
"""
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView): # 将请求更新成功的数据渲染到模板并向应给前端
"""View for updating an object, with a response rendered by a template."""
template_name_suffix = '_form'
- 一般情况下只需重写
form_valid
方法
4. DeleteView
class DeletionMixin: # 删除对象 需要重写delete方法,找到要删除对象
"""Provide the ability to delete objects."""
success_url = None
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect to the
success URL.
"""
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
# Add support for browsers which only accept GET and POST for now.
def post(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
def get_success_url(self):
if self.success_url:
return self.success_url.format(**self.object.__dict__)
else:
raise ImproperlyConfigured(
"No URL to redirect to. Provide a success_url.")
class BaseDeleteView(DeletionMixin, BaseDetailView):
"""
Base view for deleting an object.
Using this base class requires subclassing to provide a response mixin.
"""
class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
"""
View for deleting an object retrieved with self.get_object(), with a
response rendered by a template.
"""
template_name_suffix = '_confirm_delete'
- 一般情况下只需重写
delete
方法即可