Django4.0 基于類的視圖-內(nèi)置的基于類的通用視圖

2022-03-16 17:57 更新

編寫 Web 應(yīng)用程序可能很單調(diào),因為我們一次又一次地重復(fù)某些模式。 Django 試圖在模型層和模板層消除一些單調(diào),但 Web 開發(fā)人員也在視圖級別體驗到這種無聊。

Django 通用視圖是為了緩解這種情況而被開發(fā)的。他們采用在視圖開發(fā)時發(fā)現(xiàn)的某些通用的風(fēng)格和模式,并把它們抽象化,因此你可能更快的編寫公共的數(shù)據(jù)視圖,而不是編寫更多的代碼。

我們可以識別出某些通用任務(wù),比如顯示對象列表,編寫顯示任何對象列表的代碼。然后有問題的模型將被當(dāng)做附加的參數(shù)傳遞給 ?URLconf?。

Django 附帶通用視圖來執(zhí)行以下操作:

  • 為單個對象顯示列表和詳情頁。如果我們創(chuàng)建一個管理會議的應(yīng)用,那么 ?TalkListView ?和 ?RegisteredUserListView ?就是列表視圖的例子。單個"話題頁"將作為例子中的"詳情頁"。
  • 在年/月/日的歸檔頁面,相關(guān)的詳情和最新頁面將顯示基于日期的對象。
  • 運行用戶創(chuàng)建、更新和刪除對象——無論是否授權(quán)。

總之,這些視圖提供的接口來執(zhí)行開發(fā)者們遇到的最常見的通用任務(wù)。

擴展通用視圖

毫無疑問,使用通用視圖可以大大加快開發(fā)速度。然而,在很多項目中,會出現(xiàn)通用視圖不再適用。實際上,很多新手 Django 開發(fā)者問的最常見的問題是怎么讓通用視圖處理更大范圍的情況。

這是通用視圖在1.3版重新設(shè)計的原因之一——之前,它們只是具有各種選項的視圖函數(shù);現(xiàn)在,擴展通用視圖的推薦方法是將它們子類化并且覆蓋它們的屬性或方法,而不是在 ?URLconf ?中傳遞一個很龐雜的配置。

也就是說,通用視圖有一個限制。如果你發(fā)現(xiàn)很難將實現(xiàn)的視圖作為通用視圖的子類,那么你可能會發(fā)現(xiàn)使用自己的基類或函數(shù)視圖來編寫你所需的代碼會更有效率。

一些第三方應(yīng)用里提供了很多通用視圖案例,或者你可以編寫你需要的通用視圖。

對象的通用視圖

?TemplateView ?當(dāng)然也很常用,但 Django 通用視圖在呈現(xiàn)數(shù)據(jù)庫內(nèi)容視圖時確實很出色。因為這是一個很常見任務(wù),Django 附帶一些內(nèi)置的通用視圖來協(xié)助生成列表和對象的詳情視圖。
讓我們首先看一些顯示對象列表或單獨對象的例子。
我們將使用這些模型:

# 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 PublisherListView(ListView):
    model = Publisher

最后將這個視圖掛鉤到你的urls:

# urls.py
from django.urls import path
from books.views import PublisherListView

urlpatterns = [
    path('publishers/', PublisherListView.as_view()),
]

這就是我們需要編寫的所有代碼。盡管我們?nèi)匀恍枰帉懸粋€模板。我們可以給視圖添加 ?template_name ?屬性來告訴視圖使用哪個模板,但如果沒有明確的模板,Django 將從對象名稱中推斷一個。在這個例子中,推斷模板將是 books/publisher_list.html? —— books部分來自定義模型所屬 app 名稱,而 publisher必須是模型名稱的小寫。

因此,假如一個 DjangoTemplates 后端的 ?APP_DIRS ?選項在 ?TEMPLATES ?中被設(shè)為 ?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 %}

制作"友好"的模板上下文

你可能已經(jīng)注意到了例子中的出版者列表模板在變量名為 ?object_list ?里保存了所有的出版者。盡管它工作正常,但它對模板作者并不是特別友好:他們必須在這里處理出版者信息。
如果你正在處理模型對象,這已經(jīng)完成了。當(dāng)你正在處理對象或查詢,Django 使用小寫的模型類名來填充上下文。這是除了默認的 ?object_list ?類目之外提供的,但包含完全相同的數(shù)據(jù),即 ?publisher_list?。
如果仍然匹配的不好,你可以手工設(shè)置上下文變量的名稱。通用視圖上的?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'

添加額外的上下文

通常,你只需要提供通用視圖所提供的信息之外的一些附加的信息。比如,打算在每一個出版者詳情頁上顯示所有的書籍列表。?DetailView ?通用視圖提供出版者至上下文,但是怎么在模板里獲取更多的信息呢?
答案是子類化 ?DetailView ?,并提供你實現(xiàn)的 ?get_context_data? 方法。默認的實現(xiàn)只是將正在顯示的對象增加到模板,但你需要覆蓋它來發(fā)送更多信息:

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 ?將合并當(dāng)前類的所有父類的上下文數(shù)據(jù)。要在你想要改變上下文的類中保留此行為,你應(yīng)該確保在超類上調(diào)用了 ?get_context_data ?。當(dāng)沒有兩個類嘗試去定義相同的鍵是,會給出正確的結(jié)果。然而,如果任何類打算在父類已經(jīng)設(shè)置鍵(調(diào)用super后)后覆蓋鍵,如果任何子類想確保覆蓋了所有父類,那么就需要在調(diào)用super后顯式地設(shè)置它。

查看對象的子集

現(xiàn)在讓我們仔細觀察我們一直在使用的 ?model ?參數(shù)。?model ?參數(shù)指定了視圖將對其進行操作的數(shù)據(jù)模型,可用于對單個對象或?qū)ο蠹线M行操作的所有通用視圖上。然而,?model ?參數(shù)不僅僅用來指定視圖操作對象,還可以使用 ?queryset ?參數(shù)指定對象列表。

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 ?定義過濾的對象列表,你可以更加具體的了解在視圖中可見的對象。
舉一個例子,我們想通過出版日期排序一個書籍列表,最新的排第一:

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'

這是一個很簡單的例子,但很好的說明了問題。你通常會想做比重新排序?qū)ο蟾嗟牟僮鳌H绻阆腼@示特定出版者的書籍列表,你可以使用相同技術(shù):

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'

注意,和過濾的查詢結(jié)果一起,我們還要指定自定義的模板名稱。如果我們不這么做,通用視圖將使用與 "vanilla" 對象列表相同的模板,這可能不是我們想要的。

還需要注意,這不是一個特別優(yōu)雅的獲取指定出版者書籍的方法。如果你想添加其他出版者頁面,我們需要在URLconf中再添加幾行,但如果多個出版者,這就變得不合理了。我們將在下一個部分來處理這個問題。

如果你在請求 ?/books/acme/ ?時得到了404頁面,請檢查確保有叫 '?ACME Publishing?' 的出版者。通用視圖有一個 ?allow_empty ?參數(shù)來解決這個問題。

動態(tài)過濾

其他常見需求是通過URL中的某個鍵來過濾列表頁面里的對象。之前我們在URLconf中硬編碼了出版者的名字,但如果我們想編寫一個顯示任意出版者書籍的視圖呢?
我們可以方便地覆蓋 ?ListView ?的 ?get_queryset()? 方法。默認情況下,它返回 ?queryset ?屬性值,但現(xiàn)在我們可以用它來添加更多邏輯。
這項工作的關(guān)鍵部分是當(dāng)基于類的視圖被調(diào)用的時候,各種常用的東西被存儲在 ?self ?上,而且請求 ?self.request?根據(jù) ?URLconf ?抓取位置?self.args?和基于名稱?self.kwargs? 的參數(shù)。
現(xiàn)在,我們有個帶有單個抓取組的?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 ?來給查詢集添加邏輯。比如,我們可以使用 ?self.request.user? 來過濾當(dāng)前用戶或其他更復(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

執(zhí)行額外的任務(wù)

最后一個常見模式里,我們將看到涉及在調(diào)用通用視圖前后執(zhí)行一些附加任務(wù)。
想象在 ?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 ?類不知道關(guān)于這個字段的任何信息,但我們可以再次編寫自定義視圖來保持字段更新。
首先,我們需要在?URLconf?中添加一個作者詳情的url指向自定義視圖:

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



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號