Django 學習小組:基於類的通用視圖詳解(一)

通過三周的時間我們開發了一個簡單的個人 Blog,教程地址:

第一周:Django 學習小組:博客開發實戰第一周教程 —— 編寫博客的 Model 和首頁面

第二周:Django 學習小組:博客開發實戰第二周教程 —— 博客詳情頁面和分類頁面

第三周:Django 學習小組:博客開發實戰第三周教程 —— 文章列表分頁和代碼語法高亮

有朋友反應說對於 Django 的 class-based-view(基於類的通用視圖)還有很多不明白的地方,因此接下來我們會出一系列文章講解幾個常用的基於類的視圖的用法,並在適當的源碼層面下講解其機理和如何按照我們的需要拓展它。

本教程首先介紹兩個 Blog 項目中遇到的通用視圖:ListViewDetailView。從名字我們可以對其功能略窺一二,ListView 用於 List(列出)一系列 Model (比如文章列表),DetailView 獲取某個 Model(比如某篇文章)以展示其細節。

提示:在閱讀教程的過程中,如有任何問題請訪問我們項目的 GithHub 或評論留言以獲取幫助,本教程的相關代碼已全部上傳在 Github。如果你對我們的教程或者項目有任何改進建議,請您隨時告知我們。更多交流請加入我們的郵件列表django_study@groups.163.com 和關注我們在 GithHub 上的項目。

本文首發於編程派微信公眾號:編程派(微信號:codingpy)是一個專註Python編程的公眾號,每天更新有關Python的國外教程和優質書籍等精選乾貨,歡迎關注。

ListView

在開發一個網站時,我們常常需要獲取存儲在資料庫中的某個 Model 的列表,比如 Blog 要獲取文章(Article)列表以顯示到首頁,通常我們都會寫如下的視圖函數來滿足我們的需求:

def index(request):n """n 獲取 Article 列表以渲染首頁模板n """n article_list = Article.objects.all() # 獲取文章列表n category_list = Category.objects.all() # 獲取分類列表n context = { article_list : article_list , category_list : category_list }n return render_to_response(blog/index.html,context)n

當然這僅僅是一個最為基本的視圖函數的例子,Django 開發者發現,如果項目里有大量的視圖都是實現類似於上面這種獲取存儲在資料庫中的某個 Model 的列表的功能的話,會不斷地重複書寫諸如下面的代碼:

article_list = Article.objects.all()ncontext = { article_list : article_list }nreturn render_to_response(blog/index.html,context)n

就是不斷地獲取 Model 列表然後渲染模板文件,Django 說寫多了開發人員就覺得無聊了,那我們何不把這些邏輯抽象出來放到一個類里?於是 Django 幫我們寫好了一個類,專門用於處理上面的情況,這就是 ListView,將上面的視圖函數轉寫成類視圖如下:

class IndexView(ListView):n template_name = "blog/index.html"n context_object_name = "article_list"nn def get_queryset(self):n article_list = Article.objects.all()n for article in article_list:n article.body = markdown2.markdown(article.body, extras=[fenced-code-blocks], )n return article_listnn def get_context_data(self, **kwargs):n kwargs[category_list] = Category.objects.all().order_by(name)n return super(IndexView, self).get_context_data(**kwargs)n

首先看看 get_queryset 方法,它完成的功能和 article_list = Article.objects.all() 這句代碼類似,獲取某個 Model 的列表(這裡是文章列表),同時我們加入了自己的邏輯,即對 article_list 中的各個 article 進行了 markdwon 拓展,假如僅僅只需要獲取 article_list ,則甚至可以不用複寫 get_queryset 方法,只需指定一個 model 屬性,告訴 Django 去獲取哪個 model 的列表就可以了,像這樣:

class IndexView(ListView):n template_name = "blog/index.html"n context_object_name = "article_list"n model = Articlenn def get_context_data(self, **kwargs):n kwargs[category_list] = Category.objects.all().order_by(name)n return super(IndexView, self).get_context_data(**kwargs)n

第二個複寫的方法是 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 已經默認幫我們做了,翻看其源代碼就會知道:

... 省略其他代碼ndef render_to_response(self, context, **response_kwargs):n """n Returns a response, using the `response_class` for thisn view, with a template rendered with the given context.nn If any keyword arguments are provided, they will ben passed to the constructor of the response class.n """n response_kwargs.setdefault(content_type, self.content_type)n return self.response_class(n request=self.request,n template=self.get_template_names(),n context=context,n using=self.template_engine,n **response_kwargsn )n... 省略其他代碼n

如果你改變渲染模板的一些行為,通過複寫 render_to_response 方法即可。

以上方法在類視圖調用 as_view() 方法後會被自動調用。

ListView 總結

  • ListView 主要用在獲取某個 model 列表中
  • 通過 template_name 屬性來指定需要渲染的模板,通過 context_object_name 屬性來指定獲取的 model 列表的名字,否則只能通過默認的 object_list 獲取
  • 複寫 get_queryset 方法以增加獲取 model 列表的其他邏輯
  • 複寫 get_context_data 方法來為上下文對象添加額外的變數以便在模板中訪問

DetailView

前面的 ListView 用於獲取某個 model 的列表,獲取的是一系列對象,但獲取單個 mdoel 對象也是很常見的,比如 Blog 里點擊某篇文章後進入文章的詳情頁,這裡獲取的就是點擊這篇文章。我們通常會寫如下視圖函數:

def detail(request,article_id):n article = get_object_or_404(Article,pk=article_id)n context = { article : article }n return render_to_response(blog/detail.html,context)n

同樣的,如果這種需求多的話,開發人員就需要枯燥而乏味地大量重複寫 article = get_object_or_404(Article,pk=article_id) 這樣的句子,Django 通過 DetailView 來把這種邏輯抽象出來,把上面的視圖函數轉成類視圖:

class ArticleDetailView(DetailView):n model = Articlen template_name = "blog/detail.html"n context_object_name = "article"n pk_url_kwarg = article_idnn def get_object(self, queryset=None):n obj = super(ArticleDetailView, self).get_object()n obj.body = markdown2.markdown(obj.body, extras=[fenced-code-blocks], )n return objn

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),n

因此,基於類的視圖並非什麼新的東西,只是為了方便而對一般的視圖另一種形式的改寫而已,理解了一般的視圖寫法後,通過閱讀其官方文檔和類視圖的源碼,很快就能掌握如何寫好類視圖了。以下就給出其參考資料:

  • 類視圖的官方文檔
  • 類視圖的官方文檔中文翻譯版(可能不全)
  • 類視圖的源代碼位於 django/views/generic 目錄下

Django學習小組簡介

django學習小組是一個促進 django 新手互相學習、互相幫助的組織。

小組在一邊學習 django 的同時將一起完成幾個項目,包括:

  • 一個簡單的 django 博客,用於發布小組每周的學習和開發文檔;
  • django中國社區,為國內的 django 開發者們提供一個長期維護的 django 社區;

上面所說的這個社區類似於 segmentfault 和 stackoverflow ,但更加專註(只專註於 django 開發的問題)。

更多的信息請關注我們的 github 組織,本教程項目的相關源代碼也已上傳到 github 上。

同時,你也可以加入我們的郵件列表 django_study@groups.163.com ,隨時關注我們的動態。我們會將每周的詳細開發文檔和代碼通過郵件列表發出。

如有任何建議,歡迎提 Issue,歡迎 fork,pr,當然也別忘了 star 哦!


推薦閱讀:

進程間通信和線程間通信的區別?
金融民工已承認文章不實,請VNPY對Quicklib不實文章已經造成惡劣影響,請公開做出歉意說明吧 不實文章還有
大數據全棧式開發語言 – Python

TAG:Django框架 | Python | 编程入门 |