經過三週的時間咱們開發了一個簡單的我的 Blog,教程地址:html
第一週:Django 學習小組:博客開發實戰第一週教程 —— 編寫博客的 Model 和首頁面python
第二週:Django 學習小組:博客開發實戰第二週教程 —— 博客詳情頁面和分類頁面git
第三週:Django 學習小組:博客開發實戰第三週教程 —— 文章列表分頁和代碼語法高亮github
有朋友反應說對於 Django 的 class-based-view(基於類的通用視圖)還有不少不明白的地方,所以接下來咱們會出一系列文章講解幾個經常使用的基於類的視圖的用法,並在適當的源碼層面下講解其機理和如何按照咱們的須要拓展它。數據庫
本教程首先介紹兩個 Blog 項目中遇到的通用視圖:ListView 和 DetailView。從名字咱們能夠對其功能略窺一二,ListView 用於 List(列出)一系列 Model (好比文章列表),DetailView 獲取某個 Model(好比某篇文章)以展現其細節。django
提示:在閱讀教程的過程當中,若有任何問題請訪問咱們項目的 GithHub 或評論留言以獲取幫助,本教程的相關代碼已所有上傳在 Github。若是你對咱們的教程或者項目有任何改進建議,請您隨時告知咱們。更多交流請加入咱們的郵件列表 django_study@groups.163.com 和關注咱們在 GithHub 上的項目。編程
本文首發於編程派微信公衆號:編程派(微信號:codingpy)是一個專一Python編程的公衆號,天天更新有關Python的國外教程和優質書籍等精選乾貨,歡迎關注。segmentfault
在開發一個網站時,咱們經常須要獲取存儲在數據庫中的某個 Model 的列表,好比 Blog 要獲取文章(Article)列表以顯示到首頁,一般咱們都會寫以下的視圖函數來知足咱們的需求:微信
def index(request): """ 獲取 Article 列表以渲染首頁模板 """ article_list = Article.objects.all() # 獲取文章列表 category_list = Category.objects.all() # 獲取分類列表 context = { 'article_list' : article_list , 'category_list' : category_list } return render_to_response('blog/index.html',context)
固然這僅僅是一個最爲基本的視圖函數的例子,Django 開發者發現,若是項目裏有大量的視圖都是實現相似於上面這種獲取存儲在數據庫中的某個 Model 的列表的功能的話,會不斷地重複書寫諸以下面的代碼:markdown
article_list = Article.objects.all() context = { 'article_list' : article_list } return render_to_response('blog/index.html',context)
就是不斷地獲取 Model 列表而後渲染模板文件,Django 說寫多了開發人員就以爲無聊了,那咱們何不把這些邏輯抽象出來放到一個類裏?因而 Django 幫咱們寫好了一個類,專門用於處理上面的狀況,這就是 ListView,將上面的視圖函數轉寫成類視圖以下:
class IndexView(ListView): template_name = "blog/index.html" context_object_name = "article_list" def get_queryset(self): article_list = Article.objects.all() for article in article_list: article.body = markdown2.markdown(article.body, extras=['fenced-code-blocks'], ) return article_list def get_context_data(self, **kwargs): kwargs['category_list'] = Category.objects.all().order_by('name') return super(IndexView, self).get_context_data(**kwargs)
首先看看 get_queryset 方法,它完成的功能和 article_list = Article.objects.all() 這句代碼相似,獲取某個 Model 的列表(這裏是文章列表),同時咱們加入了本身的邏輯,即對 article_list 中的各個 article 進行了 markdwon 拓展,假如僅僅只須要獲取 article_list ,則甚至能夠不用複寫 get_queryset 方法,只需指定一個 model 屬性,告訴 Django 去獲取哪一個 model 的列表就能夠了,像這樣:
class IndexView(ListView): template_name = "blog/index.html" context_object_name = "article_list" model = Article def get_context_data(self, **kwargs): kwargs['category_list'] = Category.objects.all().order_by('name') return super(IndexView, self).get_context_data(**kwargs)
第二個複寫的方法是 get_context_data 方法,這個方法是用來給傳遞到模板文件的上下文對象(context)添加額外的內容的(context 的概念在前面的教程中已有介紹,若是這裏不懂的話我再簡單解釋一下,咱們在模板文件中會使用 {{ }} 這樣的標籤來包裹模板變量,這些變量哪裏來的?就是視圖函數經過 context 傳遞到模板的)。咱們這裏由於首頁須要顯示分類信息,所以咱們把 category_list 經過 get_context_data 方法加入了 context 對象,視圖函數再幫咱們把 context 傳遞給模板。return super(IndexView, self).get_context_data(**kwargs) 語句的做用是添加了 category_list 到上下文中,還要把默認的一些上下文變量也返回給視圖函數,以便其後續處理。
如今有了 model 列表,context,按照視圖函數的邏輯應該是把這些傳遞給模板了,ListView 經過指定 template_name 屬性來指定須要渲染的模板,而 context_object_name 是給 get_queryset 方法返回的 model 列表從新命名的,由於默認返回的 model 列表其名字是 object_list,爲了可讀性,咱們能夠經過 context_object_name 來從新指定,例如咱們這裏指定爲 article_list。
return render_to_response('blog/index.html',context) 的功能在 ListView 中 Django 已經默認幫咱們作了,翻看其源代碼就會知道:
... 省略其餘代碼 def render_to_response(self, context, **response_kwargs): """ Returns a response, using the `response_class` for this view, with a template rendered with the given context. If any keyword arguments are provided, they will be passed to the constructor of the response class. """ response_kwargs.setdefault('content_type', self.content_type) return self.response_class( request=self.request, template=self.get_template_names(), context=context, using=self.template_engine, **response_kwargs ) ... 省略其餘代碼
若是你改變渲染模板的一些行爲,經過複寫 render_to_response 方法便可。
以上方法在類視圖調用 as_view() 方法後會被自動調用。
ListView 總結:
ListView 主要用在獲取某個 model 列表中
經過 template_name 屬性來指定須要渲染的模板,經過 context_object_name 屬性來指定獲取的 model 列表的名字,不然只能經過默認的 object_list 獲取
複寫 get_queryset 方法以增長獲取 model 列表的其餘邏輯
複寫 get_context_data 方法來爲上下文對象添加額外的變量以便在模板中訪問
前面的 ListView 用於獲取某個 model 的列表,獲取的是一系列對象,但獲取單個 mdoel 對象也是很常見的,好比 Blog 裏點擊某篇文章後進入文章的詳情頁,這裏獲取的就是點擊這篇文章。咱們一般會寫以下視圖函數:
def detail(request,article_id): article = get_object_or_404(Article,pk=article_id) context = { 'article' : article } return render_to_response('blog/detail.html',context)
一樣的,若是這種需求多的話,開發人員就須要枯燥而乏味地大量重複寫 article = get_object_or_404(Article,pk=article_id) 這樣的句子,Django 經過 DetailView 來把這種邏輯抽象出來,把上面的視圖函數轉成類視圖:
class ArticleDetailView(DetailView): model = Article template_name = "blog/detail.html" context_object_name = "article" pk_url_kwarg = 'article_id' def get_object(self, queryset=None): obj = super(ArticleDetailView, self).get_object() obj.body = markdown2.markdown(obj.body, extras=['fenced-code-blocks'], ) return obj
model 屬性告訴 Django 是獲取哪一個 model 對應的單個對象,template_name,context_object_name 屬性和 ListView 中是同樣的做用,pk_url_kwarg 至關於視圖函數中的 article_id 參數,已告訴 Django 獲取的是 id 爲多少的 model 實例。
get_object 方法默認狀況下獲取 id 爲pk_url_kwarg 的對象,若是須要在獲取過程當中對獲取的對象作一些處理,好比對文章作 markdown 拓展,經過複寫 get_object 便可實現。
以後的處理就和 ListView 相似了,已經實現了 render_to_response 方法來渲染模板。
以上方法在類視圖調用 as_view() 方法後會被自動調用。
DetailView 總結
DetailView主要用在獲取某個 model 的單個對象中
經過 template_name 屬性來指定須要渲染的模板,經過 context_object_name 屬性來指定獲取的 model 對象的名字,不然只能經過默認的 object 獲取
複寫 get_object 方法以增長獲取單個 model 對象的其餘邏輯
複寫 get_context_data 方法來爲上下文對象添加額外的變量以便在模板中訪問
經過上面的例子你可能並未體會到使用類的通用視圖的好處,畢竟咱們寫的基於函數的視圖彷佛代碼量更短,但這僅僅是由於例子簡單而已。同時別忘了,類是能夠被繼承的,假如咱們已經寫好了一個基於類的通用視圖,要對其拓展功能,只需繼承本來這個類視圖便可,而若是寫的是函數呢?拓展性就沒有這麼靈活,可能須要使用到裝飾器等高級技巧,或甚至不得不重複一段代碼到新拓展的視圖函數中。但本質上而言,基於類的通用視圖依然是一個視圖函數,由於最終調用時咱們會經過 genericview.as_view() 方法把類視圖轉換成通常的視圖,url 配置是這樣的:
url(r'^article/(?P<article_id>\d+)$', views.ArticleDetailView.as_view(), name='detail'),
所以,基於類的視圖並不是什麼新的東西,只是爲了方便而對通常的視圖另外一種形式的改寫而已,理解了通常的視圖寫法後,經過閱讀其官方文檔和類視圖的源碼,很快就能掌握如何寫好類視圖了。如下就給出其參考資料:
類視圖的源代碼位於 django/views/generic 目錄下
django學習小組是一個促進 django 新手互相學習、互相幫助的組織。
小組在一邊學習 django 的同時將一塊兒完成幾個項目,包括:
一個簡單的 django 博客,用於發佈小組每週的學習和開發文檔;
django中國社區,爲國內的 django 開發者們提供一個長期維護的 django 社區;
上面所說的這個社區相似於 segmentfault 和 stackoverflow ,但更加專一(只專一於 django 開發的問題)。
更多的信息請關注咱們的 github 組織,本教程項目的相關源代碼也已上傳到 github 上。
同時,你也能夠加入咱們的郵件列表 django_study@groups.163.com ,隨時關注咱們的動態。咱們會將每週的詳細開發文檔和代碼經過郵件列表發出。
若有任何建議,歡迎提 Issue,歡迎 fork,pr,固然也別忘了 star 哦!