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 中的精華。
相關文章
相關標籤/搜索