django 類視圖解析 -FormView一文中有小伙伴提問將POST來的數(shù)據(jù)寫入模型中的業(yè)務(wù)邏輯,這樣就有了本文拯啦,這里感謝這位小伙伴@huangkewen
Django編輯內(nèi)容的通用視圖包括
django.views.genetic.edit.FormView —與模型無關(guān)
django.views.genetic.edit.CreateView —與模型有關(guān)澡匪,創(chuàng)建模型實例
django.views.genetic.edit.UpdateView —與模型有關(guān),修改模型實例
django.views.genetic.edit.DeleteView --與模型有關(guān)提岔,刪除模型實例
我們可以理解 CreateView仙蛉、UpdateView、DeleteView 這三個通用視圖是在 FormView 的基礎(chǔ)上增加了對創(chuàng)建碱蒙、修改荠瘪、刪除模型實例的方法夯巷。
那么,我們就先以 CreateView 為例一起來詳細(xì)看看哀墓,它是怎么基于 FormView 來實現(xiàn)創(chuàng)建模型實例的趁餐,也就是怎么將 Post來的數(shù)據(jù)寫入模型的。
CreateView 與 FormView 的區(qū)別
流程分析的差異
CreateView 與 FormView 的 流程相似篮绰,只是在 Get 和 Post 請求中定義了 object后雷。那么 CreateView 如何實現(xiàn)數(shù)據(jù)與模型的聯(lián)系呢?
原因在于 CreateView 依賴的 BaseCreateView 覆蓋了FormView 依賴的 Base FormView 的 get_form_class() 方法吠各,從而在 Form 實例化的過程中加入了與模型的交互臀突。
對于 BaseFromView 而言,其 get_form_class() 函數(shù)為:
def get_form_class(self):
"""
Returns the form class to use in this view
"""
return self.form_class
對于 BaseCreateView 而言贾漏,其 get_form_class() 函數(shù)為:
def get_form_class(self):
"""
Returns 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 hasattr(self, 'object') and self.object 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)
這里要求要么設(shè)置 form_class候学,要么設(shè)置 fields,兩者不能同時設(shè)置纵散。對于處理模型的梳码,要設(shè)置 fields,然后分別通過設(shè)置的 model伍掀、object掰茶、get_queryset() 獲取使用的模型,這里蜜笤,如果第一個滿足條件則不會進(jìn)行第二濒蒋、第三個,如果有 model 屬性則不再關(guān)心后兩個瘩例。然后調(diào)用 modelforms的modelform_factory 創(chuàng)建模型實例啊胶。
代碼結(jié)構(gòu)的差異
既然前面說 CreateView 基于FormView甸各,那么我們先來看看 CreateView 和 FormView 的繼承關(guān)系:
上圖中垛贤,黑色字為 CreateView 類及它繼承的類,藍(lán)色字為 FormView 繼承的類趣倾,從圖中可以看到 CreateView繼承了 FormView 集成的所有類聘惦,此外還額外增加了一些內(nèi)容。下面我們來具體看下:
SingleObjectTemplateResponseMixin 與 TemplateResponseMixin
這里儒恋,SingleObjectTemplateResponseMixin 繼承 TemplateResponseMixin 善绎,也就說在 CreateView 在模板響應(yīng)這塊兒與 FormView 更加了功能,那么诫尽,增加的是什么呢禀酱?
我們先來看看 TemplateResponseMixin 的代碼,分析它實現(xiàn)了什么功能:
class TemplateResponseMixin(object):
"""
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):
"""
Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed 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):
"""
Returns 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]
TemplateResponseMixin
TemplateResponseMixin 這個類 定義了template_name牧嫉、template_engine剂跟、response_class减途、content_type 四個屬性以及 render_to_response 和 get_template_names 兩個方法。
template_name 和 get_template_names 都用于返回模板名稱曹洽,這里要么設(shè)置 template_name的名稱鳍置,要么重新定義 get_template_names() 返回模板名稱列表;其它類在獲取模型名稱時調(diào)用 get_template_names() 獲取模板名稱列表送淆。
g
template_engine税产、response_class、content_type 三個屬性以及 get_template_names() 都用于render_to_response() 偷崩,分別用于設(shè)定響應(yīng)的 模板引擎辟拷、響應(yīng)類型(TemplateResponse)、內(nèi)容類型阐斜,render_to_response 用于渲染響應(yīng)梧兼。
SingleObjectTemplateResponseMixin
我們再來看看 SingleObjectTemplateResponseMixin 的代碼,分析它增加了什么功能:
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. Returns 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(SingleObjectTemplateResponseMixin, self).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):
names.append("%s/%s%s.html" % (
self.object._meta.app_label,
self.object._meta.model_name,
self.template_name_suffix
))
elif hasattr(self, 'model') and self.model 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
SingleObjectTemplateResponseMixin 在 TemplateResponseMixin 的基礎(chǔ)上增加了template_name_field智听、template_name_suffix 用于設(shè)定 模板名稱字段和模板名稱后綴羽杰,用于定義 get_template_names()。
get_template_names() 重寫 TemplateResponseMixin 的 get_template_names() 到推,這時如果類不定義模板名稱不會像 TemplateResponseMixin 一樣引發(fā)異常考赛,而是嘗試使用模型實例的名稱定義模板名稱。
BaseCreateView與BaseFormView
BaseCreateView 與 BaseFormView 相比增加了 object 屬性的定義莉测,object 用于定義模型實例颜骤。
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an 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(BaseCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
BaseCreateView 繼承 ModelFormMixin,BaseFormView繼承 FormMixin捣卤。
class BaseFormView(FormMixin, ProcessFormView):
"""
A base view for displaying a form
"""
ModelFormMixin 與 FormMixin
ModelFormMixin 繼承 FormMixin忍抽,此外還繼承了 SingleObjecMixin ,SingleObjectMixin 定義了 model董朝、queryset鸠项、context_object_name 等數(shù)據(jù)庫查詢的屬性,此外還定義了 get_objetct()函數(shù)用于確定模型實例子姜。
class SingleObjectMixin(ContextMixin):
"""
Provides 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'
query_pk_and_slug = False
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg, None)
slug = self.kwargs.get(self.slug_url_kwarg, None)
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."
% 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.
Note that 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(SingleObjectMixin, self).get_context_data(**context)
這里的方法主要供 get_form_class() 方法調(diào)用祟绊。