天天看點

django 1.8 官方文檔翻譯: 3-4-2 内建顯示視圖基于類的内建通用視圖

Django 文檔協作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質。

交流群:467338606

網站:

http://python.usyiyi.cn/django/index.html

基于類的内建通用視圖

編寫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參考。