{% raw %}
<!--
譯者:wrongwat.cn
1.8更新:Github@wizardforcel
-->
# 基于類的內建通用視圖 #
編寫Web應用可能是單調的,因為你需要不斷的重復某一種模式。 Django嘗試從model和 template層移除一些單調的情況,但是Web開發者依然會在view(視圖)層經歷這種厭煩。
Django的通用視圖被開發用來消除這一痛苦。它們采用某些常見的習語和在開發過 程中發現的模式然后把它們抽象出來,以便你能夠寫更少的代碼快速的實現基礎的視圖。
我們能夠識別一些基礎的任務,比如展示對象的列表,以及編寫代碼來展示任何對象的 列表。此外,有問題的模型可以作為一個額外的參數傳遞到URLconf中。
Django通過通用視圖來完成下面一些功能:
+ 為單一的對象展示列表和一個詳細頁面。 如果我們創建一個應用來管理會議,那么 一個 TalkListView (討論列表視圖)和一個 RegisteredUserListView ( 注冊用戶列表視圖)就是列表視圖的一個例子。一個單獨的討論信息頁面就是我們稱 之為 "詳細" 視圖的例子。
+ 在年/月/日歸檔頁面,以及詳細頁面和“最后發表”頁面中,展示以數據庫為基礎的對象。
允許用戶創建,更新和刪除對象 -- 以授權或者無需授權的方式。
總的來說,這些視圖提供了一些簡單的接口來完成開發者遇到的大多數的常見任務。
## 擴展通用視圖 ##
使用通用視圖可以極大的提高開發速度,是毫無疑問的。 然而在大多數工程中, 總會遇到通用視圖無法滿足需求的時候。的確,大多數來自Django開發新手 的問題是如何能使得通用視圖的使用范圍更廣。
這是通用視圖在1.3發布中被重新設計的原因之一 - 之前,它們僅僅是一些函數視圖加上 一列令人疑惑的選項;現在,比起傳遞大量的配置到URLconf中,更推薦的擴展通用視圖的 方法是子類化它們,并且重寫它們的屬性或者方法。
這就是說,通用視圖有一些限制。如果你將你的視圖實現為通用視圖的子類,你就會發現這樣能夠更有效地編寫你想要的代碼,使用你自己的基于類或功能的視圖。
在一些三方的應用中,有更多通用視圖的示例,或者你可以自己按需編寫。
## 對象的通用視圖 ##
TemplateView確實很有用,但是當你需要 呈現你數據庫中的內容時Django的通用視圖才真的會脫穎而出。因為這是如此常見 的任務,Django提供了一大把內置的通用視圖,使生成對象的展示列表和詳細視圖 的變得極其容易。
讓我們來看一下這些通用視圖中的"對象列表"視圖。
我們將使用下面的模型:
```
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __str__(self): # __unicode__ on Python 2
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
```
現在我們需要定義一個視圖:
```
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
```
最后將視圖解析到你的url上:
```
# urls.py
from django.conf.urls import url
from books.views import PublisherList
urlpatterns = [
url(r'^publishers/$', PublisherList.as_view()),
]
```
上面就是所有我們需要寫的Python代碼了。
> 注意
>
> 所以,當(例如)DjangoTemplates后端的APP_DIRS選項在TEMPLATES中設置為True時,模板的位置應該為:/path/to/project/books/templates/books/publisher_list.html。
這個模板將會依據于一個上下文(context)來渲染,這個context包含一個名為object_list 包含所有publisher對象的變量。一個非常簡單的模板可能看起來像下面這樣:
```
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
```
這確實就是全部代碼了。 所有通用視圖中有趣的特性來自于修改被傳遞到通用視圖中的"信息" 字典。generic views reference文檔詳細 介紹了通用視圖以及它的選項;本篇文檔剩余的部分將會介紹自定義以及擴展通用 視圖的常見方法。
## 編寫“友好的”模板上下文 ##
你可能已經注意到了,我們在publisher列表的例子中把所有的publisher對象 放到 object_list 變量中。雖然這能正常工作,但這對模板作者并不是 "友好的"。他們只需要知道在這里要處理publishers就行了。
因此,如果你在處理一個模型(model)對象,這對你來說已經足夠了。 當你處理 一個object或者queryset時,Django能夠使用你定義對象顯示用的自述名(verbose name,或者復數的自述名,對于對象列表)來填充上下文(context)。提供添加到默認的 object_list 實體中,但是包含完全相同的數據,例如publisher_list。
如果自述名(或者復數的自述名) 仍然不能很好的符合要求,你 可以手動的設置上下文(context)變量的名字。在一個通用視圖上的context_object_name屬性指定了要使用的定了上下文變量:
```
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
```
提供一個有用的context_object_name總是個好主意。和你一起工作的設計 模板的同事會感謝你的。
## 添加額外的上下文 ##
多數時候,你只是需要展示一些額外的信息而不是提供一些通用視圖。 比如,考慮到每個publisher 詳細頁面上的圖書列表的展示。DetailView通用視圖提供了一個publisher對象給context,但是我們如何在模板中添加附加信息呢?
答案是派生DetailView,并且在get_context_data方法中提供你自己的實現。默認的實現只是簡單的 給模板添加了要展示的對象,但是你這可以這樣覆寫來展示更多信息:
```
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
```
> 注意
>
> 通常來說,get_context_data會將當前類中的上下文數據,合并到所有超類中的上下文數據。要在你自己想要改變上下文的類中保持這一行為,你應該確保在超類中調用了get_context_data。如果沒有任意兩個類嘗試定義相同的鍵,會返回異常的結果。然而,如果任何一個類嘗試在超類持有一個鍵的情況下覆寫它(在調用超類之后),這個類的任何子類都需要顯式于超類之后設置它,如果你想要確保他們覆寫了所有超類的話。如果你有這個麻煩,復查你視圖中的方法調用順序。
## 查看對象的子集 ##
現在讓我們來近距離查看下我們一直在用的 model參數。model參數指定了視圖在哪個數據庫模型之上進行操作,這適用于所有的需要 操作一個單獨的對象或者一個對象集合的通用視圖。然而,model參數并不是唯一能夠指明視圖要基于哪個對象進行操作的方法 -- 你同樣可以使用queryset參數來指定一個對象列表:
```
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetail(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
```
指定model = Publisher等價于快速聲明的queryset = Publisher.objects.all()。然而,通過使用queryset來定義一個過濾的對象列表,你可以更加詳細 的了解哪些對象將會被顯示的視圖中(參見執行查詢來獲取更多關于查詢集對象的更對信息,以及參見 基于類的視圖參考來獲取全部 細節)。
我們可能想要對圖書列表按照出版日期進行排序來選擇一個簡單的例子,并且把 最近的放到前面:
```
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
```
這是個非常簡單的列子,但是它很好的詮釋了處理思路。 當然,你通常想做的不僅僅只是 對對象列表進行排序。如果你想要展現某個出版商的所有圖書列表,你可以使用 同樣的手法:
```
from django.views.generic import ListView
from books.models import Book
class AcmeBookList(ListView):
context_object_name = 'book_list'
queryset = Book.objects.filter(publisher__name='Acme Publishing')
template_name = 'books/acme_list.html'
```
注意,除了經過過濾之后的查詢集,一起定義的還有我們自定義的模板名稱。如果我們不這么做,通過視圖會使用和 "vanilla" 對象列表名稱一樣的模板,這可 能不是我們想要的。
另外需要注意,這并不是處理特定出版商的圖書的非常優雅的方法。 如果我們 要創建另外一個出版商頁面,我們需要添加另外幾行代碼到URLconf中,并且再多幾個 出版商就會覺得這么做不合理。我們會在下一個章節處理這個問題。
> 注意
>
> 如果你在訪問 /books/acme/時出現404錯誤,檢查確保你確實有一個名字為“ACME Publishing”的出版商。通用視圖在這種情況下擁有一個allow_empty 的參數。詳見基于類的視圖參考。
## 動態過濾 ##
另一個普遍的需求是在給定的列表頁面中根據URL中的關鍵字來過濾對象。 前面我們把出版 商的名字硬編碼到URLconf中,但是如果我們想要編寫一個視圖來展示任何publisher的所有 圖書,應該如何處理?
相當方便的是, ListView 有一個get_queryset() 方法來供我們重寫。在之前,它只是返回一個queryset屬性值,但是現在我們可以添加更多的邏輯。
讓這種方式能夠工作的關鍵點,在于當類視圖被調用時,各種有用的對象被存儲在self上;同request()(self.request)一樣,其中包含了從URLconf中獲取到的位置參數 (self.args)和基于名字的參數(self.kwargs)(關鍵字參數)。
這里,我們擁有一個帶有一組供捕獲的參數的URLconf:
```
# urls.py
from django.conf.urls import url
from books.views import PublisherBookList
urlpatterns = [
url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
]
```
接著,我們編寫了PublisherBookList視圖::
```
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookList(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.args[0])
return Book.objects.filter(publisher=self.publisher)
```
如你所見,在queryset區域添加更多的邏輯非常容易;如果我們想的話,我們可以 使用self.request.user來過濾當前用戶,或者添加其他更復雜的邏輯。
同時我們可以把出版商添加到上下文中,這樣我們就可以在模板中使用它:
```
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookList, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return context
```
## 執行額外的工作 ##
我們需要考慮的最后的共同模式在調用通用視圖之前或者之后會引起額外的開銷。
想象一下,在我們的Author對象上有一個last_accessed字段,這個字段用來 跟蹤某人最后一次查看了這個作者的時間。
```
# models.py
from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
last_accessed = models.DateTimeField()
```
通用的DetailView類,當然不知道關于這個字段的事情,但我們可以很容易 再次編寫一個自定義的視圖,來保持這個字段的更新。
首先,我們需要添加作者詳情頁的代碼配置到URLconf中,指向自定義的視圖:
```
from django.conf.urls import url
from books.views import AuthorDetailView
urlpatterns = [
#...
url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
]
```
然后,編寫我們新的視圖 -- get_object是用來獲取對象的方法 -- 因此我們簡單的 重寫它并封裝調用:
```
from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = timezone.now()
object.save()
# Return the object
return object
```
> 注意
>
> 這里URLconf使用參數組的名字pk - 這個名字是DetailView用來查找主鍵的值的默認名稱,其中主鍵用于過濾查詢集。
>
> 如果你想要調用參數組的其它方法,你可以在視圖上設置pk_url_kwarg。詳見 DetailView參考。
{% endraw %}
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架