SingleObjectMixin
class SingleObjectMixin(ContextMixin):
"""
提供检索单个对象,并对该对象操作的一些功能
"""
model = None # 模型类 eg:User
queryset = None # 查询集 eg: User.object.filter(active=True)
# model 和 queryset 指定一个就行 不允许同时指定
# queryset是具有可变值的类属性,因此在直接使用它时必须小心。在使用它之前,要么调用它的all()
# 方法,要么使用它的方法进行检索 get_queryset(),以处理后台传回的拷贝。
slug_field = 'slug' # 模型中包含该字段的名称
context_object_name = None # 指定在模版的上下文中使用的变量的名称,所有的字段信息都会被包含
# 在名为 context_object_name 的对象中,
# 例如 context_object_name = forms
# 假设 forms 类似这样 {'name': 'monkey'}
# 在模版中 {{ forms.name }} 将会渲染出 name 的 值
slug_url_kwarg = 'slug' # 也是用来检索唯一的对象,但是它是为了安全而存在的,默认为slug
# 用来和pk 一起获取唯一对象
pk_url_kwarg = 'pk' # 用来检索唯一的对象的关键信息,它默认的是pk 视作模型类的主键字段 需要在URL中传入
query_pk_and_slug = False # 如果为 True 则确定唯一的对象时 会同时使用pk 和 字段 来确定 默认是False
def get_object(self, queryset=None):
"""
返回视图要显示的对象的信息
默认情况下会从URL中获取pk或slug 参数来确定唯一的对象
并将这个对象返回 只要返回的是一个具体的对象就可以 无论是谁的对象
并不会被 model或query_set属性约束,在子类中可以覆盖这个方法返
回任何的对象都可以
"""
if queryset is None:
# 如果没有定义 query_set 属性 执行 get_queryset 方法 该方法使用 model 属性返回
# 一个指定 model 所有实例的查询集 如果 get_queryset 方法没有在子类中被重写
queryset = self.get_queryset()
pk = self.kwargs.get(self.pk_url_kwarg) # 获取主键id值
slug = self.kwargs.get(self.slug_url_kwarg) # 获取slug 值
# 如果pk 不为空,通过pk获取查询集 保存在 queryset中
# 如果slug 不为空且 pk 也不为空 使用slug 过滤queryset的结果保存在queryset中
# 如果都为空 爆抛出错误 无法找到唯一的对象
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:
# 从过滤后的查询集中获取唯一的对象 成功则返回这个对象 失败 报 404 错误 页面不存在
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):
"""
通过 model 或 queryset 属性确定查询集 成功返回查询集 失败主动抛出错误
"""
if self.queryset is None:
if self.model: # 如果 queryset 为None 且 model 属性存在 返回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() # 如果 queryset 被子类重写了 则直接返回.all() 所有的对象集合
def get_slug_field(self):
"""
获取将由slug用于查找的slug字段的名称。
"""
return self.slug_field
def get_context_object_name(self, obj):
"""
获取在上下文模版中使用的 用于对象的名称。
用户指定了context_object_name 属性 则使用其值
没有则使用 model 的名字 全部小写
源码< self.model_name = self.object_name.lower() >
"""
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):
"""
将单个对象 插入上下文字典中,以便于在模版中使用.
子类如果覆盖此方法,一定要返回上下文字典 否则将无法在Template中组织上下文
也就是没法渲染模版了
"""
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
# 将原有的kwargs 传入字典中
context.update(kwargs)
return super(SingleObjectMixin, self).get_context_data(**context)
BaseDetailView
class BaseDetailView(SingleObjectMixin, View):
"""
用于显示单个对象的基本视图
因为继承View 因此 它必须实现View 约束的方法中的某个 一般来说是 get
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object) # 覆盖object
# 这样 context 原有的object 被更新为 传入的 self.object 事实上他们是一致的
return self.render_to_response(context) # 将模版和上下文字典渲染成响应对象 并返回
SingleObjectTemplateResponseMixin
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
"""
绝大多数的功能都在父类中实现的,参看父类的源码解析
该类的作用我认为有一下几点
1 解耦合 将模版和上下文字典 结合生成响应对象的方法继承自父类的 render_to_response() 而该方法调用
了确定 模版名称的方法 用来确定使用的 模版列表。该类可不依赖 父类 来获取模版
2 允许不显示的给 template_name 值 来自己推断模版
# 这样的设计 希望约束使用者 编写通用风格的模版名而减少代码量 提升代码的可读性和可维护行
# 但是 往往不利于让使用者知道他在干嘛~
"""
template_name_field = None # 默认的参数
template_name_suffix = '_detail' # django 主动的推断模版名时需要的后缀
def get_template_names(self):
"""
重写了 父类的方法
作用 推断模版名、解耦
返回用于请求的模板名称列表。 如果render_to_response被覆盖,则可能不会被调用。 返回以下列表:
*视图上``template_name''的值(如果提供)
*模板上的template_name_field字段的内容
视图正在操作的对象实例(如果有)
*`` / .html``
"""
try:
# 尝试获取 模版的 文件名列表 get_template_names() 被父类的render_to_response方法调用
names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
except ImproperlyConfigured:
# 如果没有指定 template_name 就实现自己的获取 方法 以解耦对父类的依赖
# 初始化一个 列表
names = []
# 如果设置了self.template_name_field,则获取该字段的值 用作模版的名字
if self.object and self.template_name_field:
name = getattr(self.object, self.template_name_field, None)
if name:
names.insert(0, name)
# 最不明确的选项是默认的 < app >/< model >_detail.html;
# _detail 是 template_name_suffix 的值
# 仅在有关对象是模型时才使用此功能。
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 hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model):
# 不指定 模版时 django 试图拼接出一个模版名,我不觉得这是一个很好的设计
# 虽然它使得框架更为的聪明,最重要的是 希望使用者使用 统一风格的模版名称
# 但是这不可避免的加重了 负担 同时 使用者 可能会不清楚他们做了什么
names.append("%s/%s%s.html" % (
self.model._meta.app_label,
self.model._meta.model_name,
self.template_name_suffix
))
# 如果 我们最终还是没有得到期望的 一个可用的模版名称的话 就只能抛出异常
if not names:
raise
return names
DetailView
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
"""
渲染对象的“详细”视图。默认情况下,这是一个从 self.queryset 中查找的模型实例,但是
视图将通过覆盖 self.get_object() 来渲染任意的对象
方法的流程
dispatch() 请求分发
http_method_not_allowed() 方法过滤
get_template_names() 获取模版名
get_slug_field() 获取用于确定对象的字段
get_queryset() 获取查询集
get_object() 使用 pk slug 等获取唯一的对象
get_context_object_name() 获取模版中使用的 上下文字典的名称
get_context_data() # 获取上下文字典数据
render_to_response() 返回响应体
"""
# 所有的事情都在父类中完成 尽可能的理解 MRO 以及每一个类 实现的方法,深刻的体会Mixin 拆分的精髓 我觉得这是django 中的精华。