Django 源码阅读笔记(详细视图)

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() # 获取上下文字典数据

    render_to_response() 返回响应体
    
    """
    
# 全部的事情都在父类中完成 尽量的理解 MRO 以及每个类 实现的方法,深入的体会Mixin 拆分的精髓 我以为这是django 中的精华。
相关文章
相关标签/搜索