內(nèi)置基于類的通用視圖

2021-10-19 19:31 更新

內(nèi)置基于類的通用視圖

編寫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í)行以下操作:

  • 顯示單個對象的列表和詳細(xì)信息頁面。如果我們正在創(chuàng)建一個用于管理會議的應(yīng)用程序,則a TalkListView和a RegisteredUserListView將是列表視圖的示例。單個對話頁就是所謂的“詳細(xì)”視圖的示例。
  • 在年/月/日歸檔頁面,關(guān)聯(lián)的詳細(xì)信息和“最新”頁面中顯示基于日期的對象。
  • 允許用戶創(chuàng)建,更新和刪除對象(無論有無授權(quán))。

這些視圖加在一起提供了執(zhí)行開發(fā)人員遇到的最常見任務(wù)的界面。

擴(kuò)展通用視圖

毫無疑問,使用通用視圖可以大大加快開發(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ù)。

動態(tài)過濾

另一個常見的需求是通過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

執(zhí)行額外的工作

我們將看到的最后一個常見模式涉及在調(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/


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號