Django 官方推薦的姿勢:類視圖

2021-02-21 HelloGitHub

作者:

文中所涉及的示例代碼,已同步更新到

點擊本文最下方的「閱讀原文」即可獲取

在開發網站的過程中,有一些視圖函數雖然處理的對象不同,但是其大致的代碼邏輯是一樣的。比如一個博客和一個論壇,通常其首頁都是展示一系列的文章列表或者帖子列表。對處理首頁的視圖函數來說,雖然其處理的對象一個是文章,另一個是帖子,但是其處理的過程是非常類似的:首先是從資料庫取出文章或者帖子列表,然後將這些數據傳遞給模板並渲染模板。於是,django 把這些相同的邏輯代碼抽取了出來,寫成了一系列的通用視圖函數,即基於類的通用視圖(Generic Class Based View)。

使用類視圖是 django 推薦的做法,熟悉了類視圖的使用方法後,能夠減少視圖函數的重複代碼,節省開發時間。接下來就讓我們把博客應用中的視圖函數改成基於類的通用視圖。

ListView

在我們的博客應用中,有幾個視圖函數是從資料庫中獲取文章(Post)列表數據的:

blog/views.py
def index(request): def archive(request, year, month): def category(request, pk): def tag(request, pk):    

這些視圖函數都是從資料庫中獲取文章(Post)列表,唯一的區別就是獲取的文章列表可能不同。比如 index 獲取全部文章列表,category 獲取某個分類下的文章列表。

將 index 視圖函數改寫為類視圖

針對這種從資料庫中獲取某個模型列表數據(比如這裡的 Post 列表)的視圖,Django 專門提供了一個 ListView 類視圖。下面我們通過一個例子來看看 ListView  的使用方法。我們首先把 index 視圖函數改造成類視圖函數。

blog/views.py
from django.views.generic import ListView
class IndexView(ListView): model = Post template_name = 'blog/index.html'    context_object_name = 'post_list'

要寫一個類視圖,首先需要繼承 django 提供的某個類視圖。至於繼承哪個類視圖,需要根據你的視圖功能而定。比如這裡 IndexView 的功能是從資料庫中獲取文章(Post)列表,ListView 就是從資料庫中獲取某個模型列表數據的,所以 IndexView 繼承 ListView。

然後就是通過一些屬性來指定這個視圖函數需要做的事情,這裡我們指定了三個屬性:

model:將 model 指定為 Post,告訴 django 我要獲取的模型是 Post。template_name:指定這個視圖渲染的模板。context_object_name:指定獲取的模型列表數據保存的變量名,這個變量會被傳遞給模板。

如果還是有點難以理解,不妨將類視圖的代碼和 index 視圖函數的代碼對比一下:

blog/views.py
def index(request): post_list = Post.objects.all()    return render(request, 'blog/index.html', context={'post_list': post_list})

index 視圖函數首先通過 Post.objects.all() 從資料庫中獲取文章(Post)列表數據,並將其保存到 post_list 變量中。而在類視圖中這個過程 ListView  已經幫我們做了。我們只需告訴 ListView 去資料庫獲取的模型是 Post,而不是 Comment 或者其它什麼模型,即指定 model = Post。將獲得的模型數據列表保存到 post_list 裡,即指定 context_object_name = 'post_list'。然後渲染 blog/index.html 模板文件,index 視圖函數中使用 render 函數。但這個過程 ListView   已經幫我們做了,我們只需指定渲染哪個模板即可。

接下來就是要將類視圖轉換成函數視圖。為什麼需要將類視圖轉換成函數視圖呢?

我們來看一看 blog 的 URL 配置:

blog/urls.py
app_name = 'blog'urlpatterns = [ path('', views.index, name='index'), ...]

前面已經說過每一個 URL 對應著一個視圖函數,這樣當用戶訪問這個 URL 時,Django 就知道調用哪個視圖函數去處理這個請求了。在 Django 中 URL 模式的配置方式就是通過 url 函數將 URL 和視圖函數綁定。比如 path('', views.index, name='index'),它的第一個參數是 URL 模式,第二個參數是視圖函數 index。對 url 函數來說,第二個參數傳入的值必須是一個函數。而 IndexView 是一個類,不能直接替代 index 函數。好在將類視圖轉換成函數視圖非常簡單,只需調用類視圖的 as_view() 方法即可(至於 as_view 方法究竟是如何將一個類轉換成一個函數的目前不必關心,只需要在配置 URL 模式是調用 as_view 方法就可以了。具體的實現我們以後會專門開闢一個專欄分析類視圖的原始碼,到時候就能看出 django 使用的魔法了)。

現在在 URL 配置中把 index 視圖替換成類視圖 IndexView :

blog/urls.py
app_name = 'blog'urlpatterns = [ path('', views.IndexView.as_view(), name='index'), ...]

訪問一下首頁,可以看到首頁依然顯示全部文章列表,和使用視圖函數 index 時效果一模一樣。

將 category 視圖函數改寫為類視圖

category 視圖函數的功能也是從資料庫中獲取文章列表數據,不過其和 index 視圖函數不同的是,它獲取的是某個分類下的全部文章。因此 category 視圖函數中多了一步,即首先需要根據從 URL 中捕獲的分類 id 並從資料庫獲取分類,然後使用 filter 函數過濾出該分類下的全部文章。來看看這種情況下類視圖該怎麼寫:

blog/views.py
class CategoryView(ListView): model = Post template_name = 'blog/index.html' context_object_name = 'post_list'
def get_queryset(self): cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))        return super(CategoryView, self).get_queryset().filter(category=cate)

和 IndexView 不同的地方是,我們覆寫了父類的 get_queryset 方法。該方法默認獲取指定模型的全部列表數據。為了獲取指定分類下的文章列表數據,我們覆寫該方法,改變它的默認行為。

首先是需要根據從 URL 中捕獲的分類 id(也就是 pk)獲取分類,這和 category 視圖函數中的過程是一樣的。不過注意一點的是,在類視圖中,從 URL 捕獲的路徑參數值保存在實例的 kwargs 屬性(是一個字典)裡,非路徑參數值保存在實例的 args 屬性(是一個列表)裡。所以我們使了 self.kwargs.get('pk') 來獲取從 URL 捕獲的分類 id 值。然後我們調用父類的 get_queryset 方法獲得全部文章列表,緊接著就對返回的結果調用了 filter 方法來篩選該分類下的全部文章並返回。

此外我們可以看到 CategoryView 類中指定的屬性值和 IndexView  中是一模一樣的,所以如果為了進一步節省代碼,甚至可以直接繼承 IndexView:

class CategoryView(IndexView):    def get_queryset(self):        cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))        return super(CategoryView, self).get_queryset().filter(category=cate)

然後就在 URL 配置中把 category 視圖替換成類視圖 CategoryView:

blog/urls.py
app_name = 'blog'urlpatterns = [ ... path('categories/<int:pk>/', views.CategoryView.as_view(), name='category'),]

訪問以下某個分類頁面,可以看到依然顯示的是該分類下的全部文章列表,和使用視圖函數 category 時效果一模一樣。

將 archive 和 tag 視圖函數改寫成類視圖

這裡沒有什麼新東西要講了,學以致用,這個任務就交給你自己了。

DetailView

除了從資料庫中獲取模型列表的數據外,從資料庫獲取模型的一條記錄數據也是常見的需求。比如查看某篇文章的詳情,就是從資料庫中獲取這篇文章的記錄然後渲染模板。對於這種類型的需求,django 提供了一個 DetailView 類視圖。下面我們就來將 detail 視圖函數轉換為等價的類視圖 PostDetailView,代碼如下:

blog/views.py
from django.views.generic import ListView, DetailView
class PostDetailView(DetailView): model = Post template_name = 'blog/detail.html' context_object_name = 'post'
def get(self, request, *args, **kwargs): response = super(PostDetailView, self).get(request, *args, **kwargs)
self.object.increase_views()
return response
def get_object(self, queryset=None): post = super().get_object(queryset=None) md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', TocExtension(slugify=slugify), ]) post.body = md.convert(post.body)
m = re.search(r'<div>\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S) post.toc = m.group(1) if m is not None else ''
        return post

PostDetailView 稍微複雜一點,主要是等價的 detail 視圖函數本來就比較複雜,下面來一步步對照 detail 視圖函數中的代碼講解。

首先我們為 PostDetailView 類指定了一些屬性的值,這些屬性的含義和 ListView 中是一樣的,這裡不再重複講解。

緊接著我們覆寫了 get 方法。這對應著 detail 視圖函數中將 post 的閱讀量 +1 的那部分代碼。事實上,你可以簡單地把 get 方法的調用看成是 detail 視圖函數的調用。

接著我們又複寫了 get_object 方法。這對應著 detail 視圖函數中根據文章的 id(也就是 pk)獲取文章,然後對文章的 post.body 進行 Markdown 解析的代碼部分。

你也許會被這麼多方法搞亂,為了便於理解,你可以簡單地把 get 方法看成是 detail 視圖函數,至於其它的像 get_object、get_context_data 都是輔助方法,這些方法最終在 get 方法中被調用,這裡你沒有看到被調用的原因是它們隱含在了 super(PostDetailView, self).get(request, *args, **kwargs) 即父類 get 方法的調用中。最終傳遞給瀏覽器的 HTTP 響應就是 get 方法返回的 HttpResponse 對象。

還是無法理解麼?在不涉及源碼的情況下我也只能講這麼多了。要想熟練掌握並靈活運用類視圖必須仔細閱讀類視圖的源碼,我當時也是啃源碼啃了很久很久,以後我會專門開闢一個專題分析類視圖的源碼,到時候你就會對類視圖有更深的理解了。此外,這裡是 django 官方文檔對類視圖的講解,儘管我覺得這部分文檔對類視圖也講得不是很清楚,不過也值得作為參考吧 。

文章詳情的類視圖也寫好了,同樣的,你需要在 urls.py 中進行配置,將原來的函數視圖 detail 改為類視圖,相信你應該已經知道如何做了。

配置好詳情頁視圖之後,訪問一下文章的詳情,可以看到頁面返回的結果和函數視圖是一模一樣的,至此,類視圖就改造完畢。因為類視圖和函數視圖是完全等價的,而且類視圖具有代碼復用等很多好處,所以以後一旦涉及視圖,我們都會使用類視圖來實現。

[1]HelloGitHub-追夢人物: https://www.zmrenwu.com

[2]HelloGitHub-Team 倉庫: https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial

[3]基於類的視圖概述: https://docs.djangoproject.com/en/2.2/topics/class-based-views/

關注公眾號加入交流群,一起討論有趣的技術話題

『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟著我們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎聯繫我(微信:xueweihan  備註:講解)加入我們,讓更多人愛上開源、貢獻開源~

相關焦點

  • 最淺顯易懂的Django系列教程(1)-URL與視圖
    從今天開始,我們進行第一輪分享,今天分享的是視圖與URL分發器,讓我們一起學習起來吧!一、視圖:視圖一般都寫在app的views.py中。並且視圖的第一個參數永遠都是request(一個HttpRequest)對象。這個對象存儲了請求過來的所有信息,包括攜帶的參數以及一些頭部信息等。在視圖中,一般是完成邏輯相關的操作。
  • Django分頁完整示例
    Django具有內置的分頁類,可管理分頁數據。所有分頁方法都使用Paginator類。它實際上是承擔將QuerySet拆分為Page對象的工作。在django中可以使用兩種方法進行分頁,第一種方法是使用基於函數的視圖,第二種方法是使用基於類的視圖。現在,首先,需要使用此命令創建一個新的django項目。
  • Django官方為什麼沒有標準項目結構
    Django官方並沒有提供標準的項目結構,於是網上眾說紛紜,百花齊放,一千個讀者有一千個哈姆雷特。那我們該怎麼設計項目結構呢?在回答這個問題之前,先了解一下Django原生的目錄和文件都是幹嘛的。startproject的完整格式為django-admin startproject name [directory],可以在後面追加一個目錄參數:...\> django-admin startproject helloworld hello-world就可以了。根目錄是hello-world,裡面的project是helloworld。
  • Django官方文檔終於出中文版了
    之前對於 Django 的學習我一直推薦看官方文檔,但不得不加上一句「如果你英語水平允許的話……」。現在總算是等來好日子了。各位想向網站/伺服器開發方向進階的同學不要錯過,這份官方文檔的價值絕對超過市面上任何一本 Django 教材。
  • 向Django Admin添加圖表
    然而,它沒有提供顯示摘要或歷史趨勢的探索性視圖,而這正是你期望從管理儀錶板中看到的。幸運的是,django後臺管理應用程式是可擴展的,通過一些調整,我們可以將交互式Javascript圖表添加到後臺管理中。問題我想獲得findwork.dev上電子郵件訂閱用戶隨時間變化的圖表預覽。就電子郵件用戶而言,該網站是在增長還是停滯不前?
  • python程式設計師嘔心瀝血整理 Django 優秀資源大全
    django-compat, star:91 - 為官方支持的 Django 版本提供向前和向後兼容層。django-compat-lint, star:36 - 為你的代理檢查 Django 兼容性(2 年未更新)。儀錶盤用於創建信息儀錶盤以可視化數據的包。
  • 10道題教你使用python Django框架來實現web應用,值得收藏
    關於django升級:django1.5開始支持python3。同時django1.11是支持python2的最後的版本。如果使用的django版本大於等於1.5,則django版本可以不升級。如果django版本低於1.5,則需要升級django版本。升級django版本後,新版本不兼容的老的API都需要修改。這個工作量比較大。
  • 8個能提高Django開發效率的Python包
    創建出色的管理命令:django-clickDjango-click,基於Click模塊(我們之前推薦過兩次),可以用來幫助您編寫Django管理命令。這個庫沒有大量的文檔,但是它的存儲庫中有一個測試命令的目錄,非常有用。
  • Django基礎(10): URL重定向的HttpResponseDirect, redirect和reverse的用法詳解
    在視圖views.py中利用HttpResponse重新定向至不含參數的URLfrom .models import Article, from django.http import HttpResponseRedirectfrom django.shortcuts import renderfrom .forms import ArticleForm
  • Django REST Framework教程(6): 認證詳解及如何使用Token認證
    DRF的每個認證方案實際上是一個類。你可以在視圖中使用一個或多個認證方案類。REST framework 將嘗試使用列表中的每個類進行身份驗證,並使用成功完成驗證的第一個類的返回的元組設置 request.user 和request.auth。用戶通過認證後request.user返回Django的User實例,否則返回AnonymousUser的實例。
  • 如果你是完美主義者,那麼Django就是你的菜!
    通過源碼理解HttpResponse對象在視圖views.py文件中,對於http兩個核心對象的使用方法是:from django.shortcuts import HttpResponsedjango.shortcuts
  • django中遇到錯誤:Forbidden CSRF cookie not set
    表示django全局發送post請求均需要字符串驗證功能:防止跨站請求偽造的功能工作原理:客戶端訪問伺服器端,在伺服器端正常返回給客戶端數據的時候,而外返回給客戶端一段字符串,等到客戶端下次訪問伺服器端時,伺服器端會到客戶端查找先前返回的字符串
  • 一個完整的Django入門指南
    現在來寫我們的第一個視圖(view)。我們將在下一篇教程中詳細探討它。但現在,讓我們試試看看如何用Django創建一個新頁面。打開boards應用程式中的views.py文件,並添加以下代碼:from django.http import HttpResponsedef home(request):    return HttpResponse('Hello, World!')
  • Django Form表單API詳解
    Form表單檢查數據綁定在《Django表單系統初體驗》中我們使用類的方式創建了一個登陸表單,並在視圖函數中,通過是實例化類對象,成功的創建了一張用戶登錄表單。8)在實際的開發工作中使用類的方式是非常方便,可以幫助開發者減少編寫重複的代碼的工作,而且代碼也顯得更為整潔,所以這種方式也是我們推薦使用的。
  • 如何使用Django發送電子郵件和附件
    我們已經創建了一個名為test_email的應用程式,並且已從該應用程式導入了視圖,並且不要忘記在已安裝的應用程式中包含test_email。from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)send_mail參數fail_silently:選項:True / False設置為True時,錯誤將顯示在視圖中
  • Python——用 Django 寫 restful api 接口
    Django 目錄結構urls.py (https://code.ziqiangxuetang.com/django/django-views-urls.html)—— 網址入口,關聯到對應的 views.py 中的一個函數(或者 generic 類),訪問網址就對應一個函數。
  • 好程式設計師Python培訓分享Django中間件基礎用法詳解
    好程式設計師Python培訓分享Django中間件基礎用法詳解,首先django的中間件可以在視圖函數執行前執行,比如登陸驗證、日誌記錄等,下面簡單說明一下中間件的基礎用法吧。
  • Django限制表單中ForeignKey對應下拉菜單選項數量的兩種經典方法
    如果不出意外,Django模型models.py應該是如下所示:from django.db import modelsfrom django.contrib.auth.models import Userclass Article(models.Model): """文章模型""" title = models.CharField
  • Django 3.1異步視圖實例學習
    新發布得到Django 3.1中,提供了對步視圖的支持。在附帶的官方教程提供了一個有關Django異步視圖示例演示在調用時的異步執行asyncio.sleep。但是對此很多人會疑惑,這個sleep能幹什麼呢?本文我們就一起來學習一下 Django中的異步視圖就能幹啥。
  • django 自帶 user 欄位擴展及頭像上傳
    import adminfrom django.contrib.auth.admin import UserAdminfrom django.contrib.auth.models import Userclass ProfileInline(admin.StackInlin):    model = UserProfile    can_delete