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 中的精華,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/565.html
標籤:其他
