編寫Web應用程序可能是單調的,因爲我們一次又一次地重複某些模式。Django試圖在模型和模板層中消除一些單調,但Web開發人員也在視圖級別遇到這種無聊。
Django的通用視圖(generic views)是爲緩解這種痛苦而開發的。它們採用視圖開發中的某些常用習語和模式並對其進行抽象,以便您可以快速編寫數據的公共視圖,而無需編寫太多代碼。
我們可以識別某些常見任務,例如顯示對象列表,以及編寫顯示任何對象列表的代碼。然後,可以將相關模型作爲額外參數傳遞給URLconf。
Django附帶通用視圖來執行以下操作:
- 顯示單個對象的列表和詳細信息頁面。如果我們要創建一個管理會議的應用程序,那麼 TalkListView 和 RegisteredUserListView 將是列表視圖的示例。單個談話頁面就是我們稱之爲“詳情”視圖的一個例子。
- 在
年/月/日
歸檔頁面,相關詳細信息和“最新”頁面中顯示基於日期的對象。 - 允許用戶創建,更新和刪除對象 - 無論是否經過授權。
總之,這些視圖提供了簡單的界面來執行開發人員遇到的最常見任務。
1. 擴展通用視圖
毫無疑問,使用通用視圖可以大大加快開發速度。然而,在大多數項目中,通用視圖不再足夠。實際上,新Django開發人員提出的最常見的問題是如何使通用視圖處理更廣泛的情況。
這是爲1.3版本重新設計通用視圖的原因之一 - 之前,它們只是具有令人眼花繚亂的選項數組的視圖函數; 現在,擴展通用視圖的推薦方法不是在URLconf中傳遞大量配置,而是將它們子類化,並覆蓋它們的屬性或方法。
也就是說,通用視圖將有一個限制。如果您發現自己難以將視圖實現爲通用視圖的子類,那麼您可能會發現使用您自己的基於類或功能的視圖編寫所需的代碼會更有效。
某些第三方應用程序中提供了更多通用視圖示例,您也可以根據需要編寫自己的視圖。
2. 對象的通用視圖
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 PublisherList(ListView):
model = Publisher
最後將該視圖與你的URL關聯:
# urls.py
from django.urls import path
from books.views import PublisherList
urlpatterns = [
path('publishers/', PublisherList.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 %}
效果圖:
這就是它的全部內容。通用視圖的所有很酷的功能都來自更改通用視圖上設置的屬性。該 通用視圖參考文獻詳細描述所有通用視圖的選項; 本文檔的其餘部分將考慮一些可以自定義和擴展通用視圖的常用方法。
3. 製作“友好”的模板上下文
您可能已經注意到,我們的示例 publisher 列表模板將所有發佈者存儲在名爲的變量中object_list。雖然這很好用,但對模板作者來說並不是那麼“友好”:他們必須“只知道”他們在這裏與publisher打交道。
好吧,如果你正在處理一個模型對象,這已經爲你完成了。當您處理對象或查詢集時,Django能夠使用模型類名稱的小寫版本來填充上下文。除默認object_list含完全相同的數據的xxx_list
(這裏是 publisher_list)。
如果這仍然不是你想要的,您可以在通用視圖上設置context_object_name
屬性:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'publisherList'
提供有用context_object_name的總是一個好主意。你設計模板的同事會感謝你。
4. 添加額外的上下文
通常,您需要提供除通用視圖的信息之外的一些額外信息。例如,考慮在每個publisher詳細信息頁面上顯示所有書籍的列表。通用視圖 DetailView 在上下文中提供了publisher ,但我們如何在模板中獲取更多的信息?
答案是創建DetailView 子類並提供您自己的get_context_data
方法實現。默認實現只是將顯示的對象添加到模板中,但您可以覆蓋它以發送更多:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(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()。
譯者注:
DetailView是顯示每條數據的,所以,url裏應該這麼配置
path('views4/<int:pk>/',PublisherDetailView.as_view(),name='cbv_detail_view'),
在html中可以使用添加的額外數據:
<ul>
{% for b in book_list %}
<li>{{ b.title }} - {{ b.price }}</li>
{% endfor %}
</ul>
5. 查看對象的子集
現在讓我們仔細看看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定義過濾的對象列表,您可以更加具體地瞭解在視圖中可見的對象(請參閱 執行查詢 以獲取有關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'
請注意,除了過濾queryset之外,我們還使用了自定義模板名稱。
另請注意,這不是一種非常優雅的顯示出版商特定書籍的方式。如果我們想要添加另一個發佈者頁面,我們在URLconf中需要另外一些行,並且不止一些發佈者會變得不合理。我們將在下一節討論這個問題。
注意
如果您在請求/books/acme/
時獲得404,請檢查以確保您確實擁有名爲“ACME Publishing”的發佈者。對於此種情況,通用視圖提供了allow_empty參數。有關更多詳細信息,請參閱 基於類的視圖參考。
6. 動態過濾
另一個常見的需求是通過URL中的某個鍵過濾列表頁面中給出的對象。之前我們在URLconf中對發佈者的名稱進行了硬編碼,但是如果我們想編寫一個顯示某個任意發佈者的所有書籍的視圖呢?
很方便,我們可以覆蓋ListView中的 get_queryset()方法。以前,它剛好返回queryset屬性的值 ,但現在我們可以添加更多邏輯。
使這項工作的關鍵部分是,當調用基於類的視圖時,self中存儲各種有用的東西; 如request(self.request),它包括根據URLconf捕獲的 self.args 和 self.kwargs參數。
在這裏,我們有一個帶有單個捕獲組的URLconf:
# urls.py
from django.urls import path
from books.views import PublisherBookList
urlpatterns = [
path('books/<publisher>/', 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.kwargs['publisher'])
return Book.objects.filter(publisher=self.publisher)
如您所見,在查詢集選擇中添加更多邏輯非常容易; 如果我們想要,我們可以使用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
7. 執行額外的工作
我們將看到的最後一個常見模式涉及在調用通用視圖之前或之後做一些額外的工作。
想象一下,我們的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用於查找用於過濾查詢集的主鍵值的默認名稱。
如果要將該組調用爲其他內容,可以 在視圖上設置pk_url_kwarg,更多細節可以在 DetailView參考文獻中找到。