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 视作模型类的主键<id>字段 需要在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 <SignalObjectMixin 中 context.update(kwargs)> # 这样 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字段的内容 视图正在操作的对象实例(如果有) *``<app_label> / <model_name> <template_name_suffix> .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() # 获取上下文字典数据 get() # get 方法 render_to_response() 返回响应体 """ # 所有的事情都在父类中完成 尽可能的理解 MRO 以及每一个类 实现的方法,深刻的体会Mixin 拆分的精髓 我觉得这是django 中的精华。
Django 源码阅读笔记(详细实图)
内容版权声明:除非注明,否则皆为本站原创文章。