編寫Web應(yīng)用程序可能是單調(diào)的,因為我們一次又一次地重復(fù)某些模式。Django試圖消除模型和模板層的某些單調(diào)性,但Web開發(fā)人員也在視圖級別上遇到這種無聊的情況。
開發(fā)了Django的通用視圖來緩解這種痛苦。它們采用了視圖開發(fā)中發(fā)現(xiàn)的某些常見習(xí)語和模式,并對它們進(jìn)行了抽象,以便您可以快速編寫數(shù)據(jù)的通用視圖而無需編寫太多代碼。
我們可以識別某些常見任務(wù),例如顯示對象列表,并編寫顯示任何對象列表的代碼。然后,可以將所討論的模型作為附加參數(shù)傳遞給URLconf。
Django附帶了通用視圖以執(zhí)行以下操作:
這些視圖加在一起提供了執(zhí)行開發(fā)人員遇到的最常見任務(wù)的界面。
毫無疑問,使用通用視圖可以大大加快開發(fā)速度。但是,在大多數(shù)項目中,有時通用視圖不再足夠了。確實,新Django開發(fā)人員提出的最常見問題是如何使通用視圖處理更廣泛的情況。
這是為1.3版本重新設(shè)計通用視圖的原因之一-以前,它們是帶有令人困惑的選項列表的視圖函數(shù);現(xiàn)在,與其在URLconf中傳遞大量配置,不如建議擴(kuò)展常規(guī)視圖的方法是將其子類化并覆蓋其屬性或方法。
也就是說,通用視圖將受到限制。如果您發(fā)現(xiàn)自己很難將視圖實現(xiàn)為通用視圖的子類,則可能會發(fā)現(xiàn)使用自己的基于類或功能的視圖來只編寫所需的代碼會更有效。
某些第三方應(yīng)用程序中提供了更多通用視圖的示例,或者您可以根據(jù)需要編寫自己的視圖。
TemplateView當(dāng)然是有用的,但是當(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):
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()
現(xiàn)在我們需要定義一個視圖:
# views.py
from django.views.generic import ListView
from books.models import Publisher
?
class PublisherList(ListView):
model = Publisher
最后,將該視圖掛接到您的網(wǎng)址中:
# urls.py
from django.urls import path
from books.views import PublisherList
?
urlpatterns = [
path('publishers/', PublisherList.as_view()),
]
這就是我們需要編寫的所有Python代碼。但是,我們?nèi)匀恍枰帉懸粋€模板。我們可以通過在視圖中添加一個template_name屬性來明確地告訴視圖使用哪個模板 ,但是在沒有顯式模板的情況下,Django將從對象名稱中推斷出一個模板。在這種情況下,推斷的模板將是"books/publisher_list.html"-“書”部分來自定義模型的應(yīng)用程序的名稱,而“發(fā)布者”位是模型名稱的小寫版本。
注意:因此,當(dāng)(例如)將 后端的APP_DIRS選項DjangoTemplates設(shè)置為True in時TEMPLATES,模板位置可以是:/path/to/project/books/templates/books/publisher_list.html
將針對包含名為的變量的上下文呈現(xiàn)此模板,該變量 object_list包含所有發(fā)布者對象。模板可能如下所示:
{% extends "base.html" %}
?
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
這就是全部。通用視圖的所有很酷的功能都來自更改通用視圖上設(shè)置的屬性。該 通用視圖引用文檔中的所有詳細(xì)的通用視圖的選擇; 本文檔的其余部分將考慮一些您可以自定義和擴(kuò)展通用視圖的常用方法。
您可能已經(jīng)注意到我們的示例發(fā)布者列表模板將所有發(fā)布者存儲在名為的變量中object_list。盡管這很好用,但對模板作者并不是那么“友好”:他們必須“只是知道”他們在這里與發(fā)行人打交道。
好吧,如果您要處理模型對象,那么已經(jīng)為您完成了。當(dāng)您處理對象或查詢集時,Django可以使用模型類名稱的小寫形式填充上下文。除了默認(rèn)object_list條目之外,還提供了此條目,但包含完全相同的數(shù)據(jù),即publisher_list。
如果仍然不能很好地匹配,則可以手動設(shè)置上下文變量的名稱。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è)計模板的同事將感謝您。
通常,您需要提供一些超出通用視圖所提供信息的額外信息。例如,考慮在每個出版商詳細(xì)信息頁面上顯示所有書籍的列表。該DetailView 通用視圖提供了出版商到上下文,但是我們?nèi)绾卧谀0逯蝎@取更多的信息?
答案是子類化DetailView 并提供您自己的get_context_data方法實現(xiàn)。默認(rèn)實現(xiàn)將要顯示的對象添加到模板中,但是您可以覆蓋它以發(fā)送更多內(nèi)容:
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將所有父類的上下文數(shù)據(jù)與當(dāng)前類的上下文數(shù)據(jù)合并。若要在要更改上下文的自己的類中保留此行為,請務(wù)必確保調(diào)用 get_context_data超類。當(dāng)沒有兩個類嘗試定義相同的鍵時,這將提供預(yù)期的結(jié)果。但是,如果任何類在父類設(shè)置了鍵之后都嘗試覆蓋鍵(在調(diào)用super之后),則該類的所有子級也需要在super之后顯式設(shè)置鍵,以確保覆蓋所有父鍵。如果遇到問題,請查看視圖的方法解析順序。
另一個考慮是基于類的通用視圖的上下文數(shù)據(jù)將覆蓋上下文處理器提供的數(shù)據(jù)。請參閱 get_context_data()示例。
現(xiàn)在,讓我們仔細(xì)看看model我們一直使用的參數(shù)。該model參數(shù)指定了將對視圖進(jìn)行操作的數(shù)據(jù)庫模型,該參數(shù)可用于對單個對象或?qū)ο蠹线M(jìn)行操作的所有通用視圖。但是,model參數(shù)不是指定視圖將操作的對象的唯一方法–您還可以使用queryset參數(shù)指定對象列表:
from django.views.generic import DetailView
from books.models import Publisher
?
class PublisherDetail(DetailView):
?
context_object_name = 'publisher'
queryset = Publisher.objects.all()
指定是簡短的說法。但是,通過使用定義對象的過濾列表,您可以更詳細(xì)地了解視圖中將顯示的對象(有關(guān)對象的更多信息,請參見進(jìn)行查詢,有關(guān)完整的詳細(xì)信息 ,請參見 基于類的視圖參考)。model = Publisher``queryset = Publisher.objects.all()``querysetQuerySet
舉個例子,我們可能想按出版日期訂購書籍清單,以最新的為準(zhǔ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)然,通常您不僅僅需要對對象重新排序,還需要做更多的事情。如果要顯示特定出版商的書籍列表,則可以使用相同的技術(shù):
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'
請注意,除了filter之外queryset,我們還使用了自定義模板名稱。如果我們不這樣做,則通用視圖將使用與“香草”對象列表相同的模板,而這可能不是我們想要的。
另請注意,這不是制作出版商特定書籍的一種非常優(yōu)雅的方法。如果我們要添加另一個發(fā)布者頁面,則需要在URLconf中再加上幾行,并且不止幾個發(fā)布者會變得不合理。我們將在下一部分中解決這個問題。
注意:如果在請求時收到404,請/books/acme/檢查以確保您實際上擁有名稱為'ACME Publishing'的發(fā)布商。通用視圖allow_empty對此情況有一個參數(shù)。
另一個常見的需求是通過URL中的某個鍵過濾列表頁面中給定的對象。之前我們在URLconf中硬編碼了出版商的名稱,但是如果我們想編寫一個視圖來顯示某個任意出版商的所有書籍,該怎么辦?
方便地,我們ListView有一個get_queryset()可以覆蓋的 方法。默認(rèn)情況下,它返回queryset屬性的值,但是我們可以使用它添加更多的邏輯。
進(jìn)行這項工作的關(guān)鍵部分是,當(dāng)調(diào)用基于類的視圖時,各種有用的東西都存儲在self;以及request(self.request)包括根據(jù)URLconf捕獲的position(self.args)和基于名稱的(self.kwargs)參數(shù)。
在這里,我們有一個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)
使用get_queryset向查詢集選擇添加邏輯既方便又強大。例如,如果需要的話,我們可以使用 self.request.user當(dāng)前用戶或其他更復(fù)雜的邏輯進(jìn)行過濾。
我們還可以同時將發(fā)布者添加到上下文中,因此我們可以在模板中使用它:
# ... 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
我們將看到的最后一個常見模式涉及在調(diào)用通用視圖之前或之后做一些額外的工作。
想象一下,我們last_accessed在Author模型上有一個字段,用于跟蹤任何人上次查看該作者的時間:
# 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)然,泛型類對此字段一無所知,但是我們可以再次輕松編寫一個自定義視圖以使該字段保持更新。
首先,我們需要在URLconf中添加作者詳細(xì)信息位以指向自定義視圖:
from django.urls import path from books.views import AuthorDetailView urlpatterns = [ #... path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'), ]
然后,我們將編寫新視圖– get_object是檢索對象的方法–因此我們將其覆蓋并包裝調(diào)用:
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-該名稱是默認(rèn)名稱,DetailView用于查找用于過濾查詢集的主鍵的值。
詳情參考: https://docs.djangoproject.com/en/3.0/
更多建議: