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 中的精华。

猜你喜欢

转载自www.cnblogs.com/monkey-code/p/13130053.html
今日推荐