內建的基於類別的通用視圖¶
編寫網頁應用程式可能會很單調,因為我們會不斷重複某些模式。Django 嘗試在模型和樣板層級消除一些單調感,但網頁開發人員在視圖層級也會遇到這種情況。
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):
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):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField("Author")
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
現在我們需要定義一個視圖
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
最後將該視圖掛鉤到您的 urls 中
# urls.py
from django.urls import path
from books.views import PublisherListView
urlpatterns = [
path("publishers/", PublisherListView.as_view()),
]
這就是我們需要編寫的所有 Python 程式碼。但是,我們仍然需要編寫一個樣板。我們可以透過將 template_name
屬性新增到視圖來明確告知視圖要使用哪個樣板,但在沒有明確樣板的情況下,Django 會從物件的名稱推斷出一個樣板。在這種情況下,推斷出的樣板將是 "books/publisher_list.html"
– 「books」部分來自定義模型的應用程式名稱,而「publisher」部分是模型名稱的小寫版本。
注意
因此,當(例如)TEMPLATES
中 DjangoTemplates
後端的 APP_DIRS
選項設定為 True 時,樣板位置可以是:/path/to/project/books/templates/books/publisher_list.html
此樣板將針對包含名為 object_list
的變數的內容進行轉譯,該變數包含所有發行商物件。樣板可能如下所示
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
這就是全部。通用視圖的所有酷炫功能都來自變更通用視圖上設定的屬性。通用視圖參考 詳細記錄了所有通用視圖及其選項;本文的其餘部分將考量一些您可能自訂和擴充通用視圖的常見方式。
建立「友善」樣板內容¶
您可能已經注意到,我們的範例發行商列表樣板將所有發行商儲存在名為 object_list
的變數中。雖然這很好用,但對樣板作者來說並不是那麼「友善」:他們必須「知道」他們在這裡處理的是發行商。
好吧,如果您正在處理模型物件,這已經為您完成了。當您正在處理物件或查詢集時,Django 能夠使用模型類別名稱的小寫版本來填入內容。除了預設的 object_list
項目之外,還提供此項目,但包含完全相同的資料,即 publisher_list
。
如果這仍然不太合適,您可以手動設定內容變數的名稱。通用視圖上的 context_object_name
屬性指定要使用的內容變數
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
context_object_name = "my_favorite_publishers"
提供有用的 context_object_name
始終是一個好主意。您的設計樣板的同事會感謝您。
新增額外內容¶
通常,您需要呈現超出通用視圖提供的額外資訊。例如,想想在每個發行商詳細資訊頁面上顯示所有書籍的列表。DetailView
通用視圖向內容提供發行商,但是我們如何在該樣板中取得其他資訊?
答案是子類化 DetailView
並提供您自己的 get_context_data
方法的實作。預設實作會將正在顯示的物件新增到樣板中,但您可以覆寫它以傳送更多
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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
。當沒有兩個類別嘗試定義相同的鍵時,這將產生預期的結果。但是,如果任何類別在父類別設定它之後(在呼叫 super 之後)嘗試覆寫鍵,則該類別的任何子系也需要在 super 之後明確設定它,以確保覆寫所有父系。如果您遇到問題,請檢查您視圖的方法解析順序。
另一個考量是,基於類別的通用視圖中的內容資料將覆寫內容處理器提供的資料;如需範例,請參閱 get_context_data()
。
檢視物件的子集¶
現在讓我們仔細看看我們一直在使用的 model
引數。model
引數指定視圖將在其上運作的資料庫模型,在所有在單一物件或物件集合上運作的通用視圖中都可用。但是,model
引數並不是指定視圖將在其上運作的物件的唯一方式 – 您也可以使用 queryset
引數指定物件列表
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetailView(DetailView):
context_object_name = "publisher"
queryset = Publisher.objects.all()
指定 model = Publisher
是說 queryset = Publisher.objects.all()
的簡寫。但是,透過使用 queryset
來定義已篩選的物件列表,您可以更明確地說明視圖中將可見的物件(如需有關 QuerySet
物件的詳細資訊,請參閱 製作查詢,如需完整詳細資訊,請參閱 基於類別的視圖參考)。
舉例來說,我們可能想要按出版日期排序書籍列表,最近的優先顯示
from django.views.generic import ListView
from books.models import Book
class BookListView(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 AcmeBookListView(ListView):
context_object_name = "book_list"
queryset = Book.objects.filter(publisher__name="ACME Publishing")
template_name = "books/acme_list.html"
請注意,除了已篩選的 queryset
之外,我們還在使用自訂樣板名稱。如果我們不使用,通用視圖將使用與「普通」物件列表相同的樣板,這可能不是我們想要的。
另請注意,這不是一種非常優雅的方式來執行發行商特定的書籍。如果我們想要新增另一個發行商頁面,我們將需要在 URLconf 中新增另一組程式碼行,而超過幾個發行商會變得不合理。我們將在下一節中處理這個問題。
注意
如果您在請求 /books/acme/
時收到 404 錯誤,請檢查您是否確實有一個名稱為「ACME Publishing」的出版社。通用視圖針對這種情況有一個 allow_empty
參數。詳情請參閱基於類別的視圖參考。
動態篩選¶
另一個常見的需求是根據 URL 中的某個鍵來篩選列表頁面中顯示的物件。先前我們在 URLconf 中硬編碼了出版社的名稱,但如果我們想編寫一個視圖來顯示某個任意出版社的所有書籍呢?
幸好,ListView
有一個 get_queryset()
方法可以讓我們覆寫。預設情況下,它會傳回 queryset
屬性的值,但我們可以利用它來新增更多邏輯。
要使其運作的關鍵部分是,當呼叫基於類別的視圖時,各種有用的資訊會儲存在 self
上;除了請求 (self.request
) 之外,還包括根據 URLconf 擷取的位置參數 (self.args
) 和基於名稱的參數 (self.kwargs
)。
在這裡,我們有一個帶有單個擷取群組的 URLconf
# urls.py
from django.urls import path
from books.views import PublisherBookListView
urlpatterns = [
path("books/<publisher>/", PublisherBookListView.as_view()),
]
接下來,我們將編寫 PublisherBookListView
視圖本身
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
template_name = "books/books_by_publisher.html"
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)
使用 get_queryset
向 queryset 選擇添加邏輯既方便又強大。例如,如果我們想要,我們可以使用 self.request.user
來使用目前使用者進行篩選,或其他更複雜的邏輯。
我們也可以同時將出版社加入到上下文中,以便我們可以在模板中使用它
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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.urls import path
from books.views import AuthorDetailView
urlpatterns = [
# ...
path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
]
然後我們將編寫新的視圖 – get_object
是檢索物件的方法 – 因此我們將覆寫它並包裝呼叫
from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
注意
此處的 URLconf 使用具名群組 pk
- 這個名稱是 DetailView
用來尋找用於篩選 queryset 的主鍵值的預設名稱。
如果您想將群組命名為其他名稱,您可以在視圖上設定 pk_url_kwarg
。