鍍金池/ 教程/ Python/ 基于類的內(nèi)建通用視圖
點擊劫持保護(hù)
安全問題歸檔
Model 類參考
將遺留數(shù)據(jù)庫整合到Django
關(guān)聯(lián)對象參考
內(nèi)建基于類的視圖的API
聚合
Django 中的用戶認(rèn)證
django.contrib.humanize
Django管理文檔生成器
分頁
使用Django輸出CSV
加密簽名
文件儲存API
安全
Django中的測試
國際化和本地化
為Django編寫首個補丁
條件表達(dá)式
日志
模型元選項
部署靜態(tài)文件
執(zhí)行查詢
使用Django認(rèn)證系統(tǒng)
基于類的視圖
中間件
編寫自定義的django-admin命令
Django 的設(shè)置
格式本地化
數(shù)據(jù)庫訪問優(yōu)化
錯誤報告
基于類的內(nèi)建通用視圖
編寫自定義存儲系統(tǒng)
編寫你的第一個 Django 程序 第3部分
編寫數(shù)據(jù)庫遷移
使用表單
編寫你的第一個 Django 程序 第2部分
編寫你的第一個 Django 程序 第1部分
如何使用會話
系統(tǒng)檢查框架
新手入門
信號
編寫視圖
如何使用WSGI 部署
編寫你的第一個Django應(yīng)用,第6部分
常見的網(wǎng)站應(yīng)用工具
Widgets
內(nèi)建的視圖
模型實例參考
視圖層
Django中的密碼管理
高級教程:如何編寫可重用的應(yīng)用
國際化和本地化
"本地特色"附加功能
TemplateResponse 和 SimpleTemplateResponse
模式編輯器
文件上傳
快速安裝指南
部署 Django
表單 API
表單素材 ( <code>Media</code> 類)
管理文件
其它核心功能
查找 API 參考
表單
Admin
數(shù)據(jù)庫函數(shù)
自定義查找
使用基于類的視圖處理表單
管理操作
開發(fā)過程
編寫你的第一個Django應(yīng)用,第5部分
進(jìn)行原始的sql查詢
模型層
多數(shù)據(jù)庫
編寫你的第一個 Django 程序 第4部分
Django安全
Django 初探
Django異常
重定向應(yīng)用
按需內(nèi)容處理
管理器
視圖裝飾器
驗證器
使用Django輸出PDF
File對象
Django 的快捷函數(shù)
基于類的通用視圖 —— 索引
為模型提供初始數(shù)據(jù)
模板層
URL調(diào)度器
中間件
模型

基于類的內(nèi)建通用視圖

編寫Web應(yīng)用可能是單調(diào)的,因為你需要不斷的重復(fù)某一種模式。 Django嘗試從model和 template層移除一些單調(diào)的情況,但是Web開發(fā)者依然會在view(視圖)層經(jīng)歷這種厭煩。

Django的通用視圖被開發(fā)用來消除這一痛苦。它們采用某些常見的習(xí)語和在開發(fā)過 程中發(fā)現(xiàn)的模式然后把它們抽象出來,以便你能夠?qū)懜俚拇a快速的實現(xiàn)基礎(chǔ)的視圖。

我們能夠識別一些基礎(chǔ)的任務(wù),比如展示對象的列表,以及編寫代碼來展示任何對象的 列表。此外,有問題的模型可以作為一個額外的參數(shù)傳遞到URLconf中。

Django通過通用視圖來完成下面一些功能:

  • 為單一的對象展示列表和一個詳細(xì)頁面。 如果我們創(chuàng)建一個應(yīng)用來管理會議,那么 一個 TalkListView (討論列表視圖)和一個 RegisteredUserListView ( 注冊用戶列表視圖)就是列表視圖的一個例子。一個單獨的討論信息頁面就是我們稱 之為 "詳細(xì)" 視圖的例子。
  • 在年/月/日歸檔頁面,以及詳細(xì)頁面和“最后發(fā)表”頁面中,展示以數(shù)據(jù)庫為基礎(chǔ)的對象。 允許用戶創(chuàng)建,更新和刪除對象 -- 以授權(quán)或者無需授權(quán)的方式。

總的來說,這些視圖提供了一些簡單的接口來完成開發(fā)者遇到的大多數(shù)的常見任務(wù)。

擴(kuò)展通用視圖

使用通用視圖可以極大的提高開發(fā)速度,是毫無疑問的。 然而在大多數(shù)工程中, 總會遇到通用視圖無法滿足需求的時候。的確,大多數(shù)來自Django開發(fā)新手 的問題是如何能使得通用視圖的使用范圍更廣。

這是通用視圖在1.3發(fā)布中被重新設(shè)計的原因之一 - 之前,它們僅僅是一些函數(shù)視圖加上 一列令人疑惑的選項;現(xiàn)在,比起傳遞大量的配置到URLconf中,更推薦的擴(kuò)展通用視圖的 方法是子類化它們,并且重寫它們的屬性或者方法。

這就是說,通用視圖有一些限制。如果你將你的視圖實現(xiàn)為通用視圖的子類,你就會發(fā)現(xiàn)這樣能夠更有效地編寫你想要的代碼,使用你自己的基于類或功能的視圖。

在一些三方的應(yīng)用中,有更多通用視圖的示例,或者你可以自己按需編寫。

對象的通用視圖

TemplateView確實很有用,但是當(dāng)你需要 呈現(xiàn)你數(shù)據(jù)庫中的內(nèi)容時Django的通用視圖才真的會脫穎而出。因為這是如此常見 的任務(wù),Django提供了一大把內(nèi)置的通用視圖,使生成對象的展示列表和詳細(xì)視圖 的變得極其容易。

讓我們來看一下這些通用視圖中的"對象列表"視圖。

我們將使用下面的模型:

# 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()

現(xiàn)在我們需要定義一個視圖:

# 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代碼了。

注意

所以,當(dāng)(例如)DjangoTemplates后端的APP_DIRS選項在TEMPLATES中設(shè)置為True時,模板的位置應(yīng)該為:/path/to/project/books/templates/books/publisher_list.html。

這個模板將會依據(jù)于一個上下文(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文檔詳細(xì) 介紹了通用視圖以及它的選項;本篇文檔剩余的部分將會介紹自定義以及擴(kuò)展通用 視圖的常見方法。

編寫“友好的”模板上下文

你可能已經(jīng)注意到了,我們在publisher列表的例子中把所有的publisher對象 放到 object_list 變量中。雖然這能正常工作,但這對模板作者并不是 "友好的"。他們只需要知道在這里要處理publishers就行了。

因此,如果你在處理一個模型(model)對象,這對你來說已經(jīng)足夠了。 當(dāng)你處理 一個object或者queryset時,Django能夠使用你定義對象顯示用的自述名(verbose name,或者復(fù)數(shù)的自述名,對于對象列表)來填充上下文(context)。提供添加到默認(rèn)的 object_list 實體中,但是包含完全相同的數(shù)據(jù),例如publisher_list。

如果自述名(或者復(fù)數(shù)的自述名) 仍然不能很好的符合要求,你 可以手動的設(shè)置上下文(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總是個好主意。和你一起工作的設(shè)計 模板的同事會感謝你的。

添加額外的上下文

多數(shù)時候,你只是需要展示一些額外的信息而不是提供一些通用視圖。 比如,考慮到每個publisher 詳細(xì)頁面上的圖書列表的展示。DetailView通用視圖提供了一個publisher對象給context,但是我們?nèi)绾卧谀0逯刑砑痈郊有畔⒛兀?/p>

答案是派生DetailView,并且在get_context_data方法中提供你自己的實現(xiàn)。默認(rèn)的實現(xiàn)只是簡單的 給模板添加了要展示的對象,但是你這可以這樣覆寫來展示更多信息:

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會將當(dāng)前類中的上下文數(shù)據(jù),合并到所有超類中的上下文數(shù)據(jù)。要在你自己想要改變上下文的類中保持這一行為,你應(yīng)該確保在超類中調(diào)用了get_context_data。如果沒有任意兩個類嘗試定義相同的鍵,會返回異常的結(jié)果。然而,如果任何一個類嘗試在超類持有一個鍵的情況下覆寫它(在調(diào)用超類之后),這個類的任何子類都需要顯式于超類之后設(shè)置它,如果你想要確保他們覆寫了所有超類的話。如果你有這個麻煩,復(fù)查你視圖中的方法調(diào)用順序。

查看對象的子集

現(xiàn)在讓我們來近距離查看下我們一直在用的 model參數(shù)。model參數(shù)指定了視圖在哪個數(shù)據(jù)庫模型之上進(jìn)行操作,這適用于所有的需要 操作一個單獨的對象或者一個對象集合的通用視圖。然而,model參數(shù)并不是唯一能夠指明視圖要基于哪個對象進(jìn)行操作的方法 -- 你同樣可以使用queryset參數(shù)來指定一個對象列表:

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來定義一個過濾的對象列表,你可以更加詳細(xì) 的了解哪些對象將會被顯示的視圖中(參見執(zhí)行查詢來獲取更多關(guān)于查詢集對象的更對信息,以及參見 基于類的視圖參考來獲取全部 細(xì)節(jié))。

我們可能想要對圖書列表按照出版日期進(jìn)行排序來選擇一個簡單的例子,并且把 最近的放到前面:

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'

這是個非常簡單的列子,但是它很好的詮釋了處理思路。 當(dāng)然,你通常想做的不僅僅只是 對對象列表進(jìn)行排序。如果你想要展現(xiàn)某個出版商的所有圖書列表,你可以使用 同樣的手法:

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'

注意,除了經(jīng)過過濾之后的查詢集,一起定義的還有我們自定義的模板名稱。如果我們不這么做,通過視圖會使用和 "vanilla" 對象列表名稱一樣的模板,這可 能不是我們想要的。

另外需要注意,這并不是處理特定出版商的圖書的非常優(yōu)雅的方法。 如果我們 要創(chuàng)建另外一個出版商頁面,我們需要添加另外幾行代碼到URLconf中,并且再多幾個 出版商就會覺得這么做不合理。我們會在下一個章節(jié)處理這個問題。

注意

如果你在訪問 /books/acme/時出現(xiàn)404錯誤,檢查確保你確實有一個名字為“ACME Publishing”的出版商。通用視圖在這種情況下?lián)碛幸粋€allow_empty 的參數(shù)。詳見基于類的視圖參考。

動態(tài)過濾

另一個普遍的需求是在給定的列表頁面中根據(jù)URL中的關(guān)鍵字來過濾對象。 前面我們把出版 商的名字硬編碼到URLconf中,但是如果我們想要編寫一個視圖來展示任何publisher的所有 圖書,應(yīng)該如何處理?

相當(dāng)方便的是, ListView 有一個get_queryset() 方法來供我們重寫。在之前,它只是返回一個queryset屬性值,但是現(xiàn)在我們可以添加更多的邏輯。

讓這種方式能夠工作的關(guān)鍵點,在于當(dāng)類視圖被調(diào)用時,各種有用的對象被存儲在self上;同request()(self.request)一樣,其中包含了從URLconf中獲取到的位置參數(shù) (self.args)和基于名字的參數(shù)(self.kwargs)(關(guān)鍵字參數(shù))。

這里,我們擁有一個帶有一組供捕獲的參數(shù)的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區(qū)域添加更多的邏輯非常容易;如果我們想的話,我們可以 使用self.request.user來過濾當(dāng)前用戶,或者添加其他更復(fù)雜的邏輯。

同時我們可以把出版商添加到上下文中,這樣我們就可以在模板中使用它:

# ...

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

執(zhí)行額外的工作

我們需要考慮的最后的共同模式在調(diào)用通用視圖之前或者之后會引起額外的開銷。

想象一下,在我們的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類,當(dāng)然不知道關(guān)于這個字段的事情,但我們可以很容易 再次編寫一個自定義的視圖,來保持這個字段的更新。

首先,我們需要添加作者詳情頁的代碼配置到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是用來獲取對象的方法 -- 因此我們簡單的 重寫它并封裝調(diào)用:

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使用參數(shù)組的名字pk - 這個名字是DetailView用來查找主鍵的值的默認(rèn)名稱,其中主鍵用于過濾查詢集。

如果你想要調(diào)用參數(shù)組的其它方法,你可以在視圖上設(shè)置pk_url_kwarg。詳見 DetailView參考。