Django-深度分析Django基於類的檢視(1)(翻譯)
時間有限,只能簡單翻譯,略去一些無關緊要的細節。
本文是基於Django1.5版的,但是原理分析依舊非常有意義。
什麼是基於類的檢視?(CBV)
Django類也就是Python類。一個Django檢視就是就是用來處理HTTP請求並返回HTTP響應的一段程式碼。不多不少,就這麼簡單。
略去關於Python類的解釋。
Django 檢視是有限狀態機的完美例子。它接收請求,然後在內部經過一系列的處理步驟,最後產生一個響應,返還給使用者。
下面讓我們深入到一個實際的例子中去:
from django.views.generic.list import ListView from articles.models import Article class ArticleListView(ListView): model = Article
這是一個關於ListView的例子。ListView是一個通用檢視,用來處理一列物件(來自資料庫)
例子中,假設articles是你的應用,Article是其中的一個model。
此段簡單程式碼充分使用了繼承的概念。這是怎麼工作的呢?這個類是如何處理進入的請求並輸出響應的呢?官方文件陳述:“當此檢視執行時,self.object_list將會包含一個物件列表(一般是queryset,但不必是queryset),並且檢視對其進行處理。這段話非常不易理解,尤其是對一個新手來說,已經不知所云了。
由於ArticleListView繼承自ListView,你可以看看ListView的原始碼瞭解其處理流程。當然看程式碼不是一件很容易的事兒,下面是我看程式碼所習得的內容,希望能幫助你理解。
URL分發器和檢視
CBV是不能在URL分發器裡直接使用的,所以你需要使用as_view()。as_view()基本上定義了一個方法,此方法用來將一個類(CBV)例項化並且呼叫方法dispatch(),然後as_view被返回給URL的分發器。作為使用者,我們只關心此類的入口在哪裡,也就是當一個請求來到了url連結,哪個方法被呼叫了。它就是diapatch()。
如果我們重新定義ArticleListView如下
此類並沒有改變什麼行為,它只是通過呼叫super()方法過載了dispatch()。這裡理解*args和**kwargs這兩個引數很重要。由於檢視是被系統框架自動呼叫的,因此框架本身需要這些方法遵守一套特別的API,因此在在過載這些方法的時候,需要遵循一套原則。具體可以參考官方文件中關於CBV機理的文件。from django.views.generic.list import ListView from articles.models import Article class ArticleListView(ListView): model = Article def dispatch(self, request, *args, **kwargs): return super(ArticleListView, self).dispatch(request, *args, **kwargs)
dispatch()接收一個request引數,型別是HttpRequest,這裡我們通過print將其列印到控制檯。
from django.views.generic.list import ListView
from articles.models import Article
class ArticleListView(ListView):
model = Article
def dispatch(self, request, *args, **kwargs):
print(request)
return super(ArticleListView, self).dispatch(request, *args, **kwargs)
簡而言之,這就是一個標準框架類的處理過程,當然也是Django CBV的標準流程:先繼承一個已經定義好的類,找出哪個方法需要修改,根據規則對其進行過載,在過載程式碼中呼叫父類的程式碼。
GET請求
回到ArticleListView的例子。父類的dispatch()需要request物件的method的屬性並且選擇響應的處理器去處理這個請求。意思就是說,如果request.method是’GET‘,用HTTP的語言說就是,我需要讀取一個資源,那麼dispatch()就需要呼叫類的get()方法。
ListView的get()方法來自於BaseListView。它就是我們熟悉的函式檢視。此函式的基本功能就是用self.queryset填充self.object_list屬性。同時通過呼叫self.get_context_data()建立一個上下文(context),然後呼叫類版本的render_to_response(),也就是self.render_to_response()。
方法self.get_queryset()來自ListVIew的祖先MultipleObjectMixin,只是簡單地通過呼叫queryset = self.model._default_manager.all()來獲取。其中self.model的值就是來自model=Article。
這就是全部的過程!ArticleListView類從資料庫中獲取所有的Article物件,然後呼叫一個template同時給其提供一個上下文,其中包含了一個變數,object_list,其通過獲取的物件列表進行例項化。
模版和上下文(template/context)
首先,當類呼叫self.render_to_response()時,它使用來在其父類TemplateResponseMixin的程式碼;此方法還呼叫其他的函式,但是其主要工作是通過模版和上下文建立一個響應。再一次,通過一系列的呼叫,模版來自self.template_name;儘管TemplateResponseMixin將其定義為None,但是ListView卻通過其父類們神奇地返回了一個模版,並且其名字來自於指定的那個model。簡單講,ArticleListView定義了一個叫Article的model,然後就自動使用一個叫做article_list.html的模版。
我們可以改變這一行為嗎?當然可以。這正是用類取代函式檢視的原因:定製化檢視的行為。下面我們更改類的定義如下:
from django.views.generic.list import ListView
from articles.models import Article
class ArticleListView(ListView):
model = Article
template_name = 'sometemplate.html'
這段程式碼含義是什麼?當self.render_to_response()方法尋找self.template_name時,發現這個屬性已經有了一個值。因此沒有必要呼叫預先定義的模版,而sometemplate.html就用來渲染響應了。這是一種非常有用的面向物件的設計模式。
至於上下文,實際上它只是包含了一些值的一個字典,在你編譯渲染模版時希望能夠訪問這些值。在上下文中的變數(同時也在模版中),具體什麼格式和內容則完全取決於你自己。在使用CBV時,你會發現上下文中已經包含了由父類建立的一些變數,比如本例子中的object_list。如果你想用一個頁面來顯示所有的文章列表,同時希望新增一個值到上下文,該怎麼辦?
很容易!你要做的只是過載那個能產生上下文的函式並更改它的行為。比方說,我們想顯示文章列表的同時,能夠顯示網站所有的讀者的數量。假設Reader model已經存在了。
from django.views.generic.list import ListView
from articles.models import Article, Reader
class ArticleListView(ListView):
model = Article
def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
context['readers'] = Reader.objects.count()
return context
當我們過載一個方法的時候,總是先呼叫父類的對應的方法,這樣我們就能夠獲得我們期望的此方法的正常行為,然後再加上我們的定製。結論
在本帖子中,我通過揭示一個請求如何通過get()一步一步到達檢視,來揭開Django CBV和CBGV的神祕面紗。希望對你有所幫助。
在接下來的帖子中,我會探討DetailView,用來顯示物件詳細資訊的通用檢視,如何建立定製化CBV,以及如何使用CBV處理表單,也就是POST請求。