編寫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通過通用視圖來完成下面一些功能:
總的來說,這些視圖提供了一些簡單的接口來完成開發(fā)者遇到的大多數(shù)的常見任務(wù)。
使用通用視圖可以極大的提高開發(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ù)。詳見基于類的視圖參考。
另一個普遍的需求是在給定的列表頁面中根據(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
我們需要考慮的最后的共同模式在調(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參考。