Django 類視圖學習筆記(三) 內建類視圖概覽


簡介

編寫Web程序可能是很單調的,因爲視圖層,模板層,模型層都可能會有大量邏輯類似的代碼,但是我們卻需要不斷的重複編寫它們,這肯定讓人受不了。爲此,Django在視圖層,對經常要編寫的,邏輯類似的事務代碼進行了抽象,並封裝在Django中,這就是內建類視圖,之後我們就可以利用內建類視圖來進行高效開發。

這些通用的內建類視圖可以處理以下事務邏輯:

  • 表示某個對象的列表頁面和詳情頁面。比如博客網站的博文的列表頁面和就某一篇博文的詳情頁面。
  • 基於時期或其它數據對某一類對象進行排序並呈現出來。
  • 給予用戶創建、更新、刪除某類對象的權限。

以上這些事務基本上是開發人員所遇到的最常見的幾類視圖。

另外要注意的是,Django的內建類視圖雖然很有用,但並不能爲我們解決所有問題,使用它們的最常見的一種方法是用我們的類來繼承這些內建類視圖,然後根據具體問題決定如何去拓展。


使用ListView

以Django內置的ListView爲例,講解如何繼承並使用它。

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

urls.py:

from django.urls import path
from books.views import PublisherList

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

以上是使用ListView的一個簡單示例,我們沒有直接使用它,而是使用它的子類PublisherList。ListView主要用於顯示數據庫中的內容,因此它具有一個很顯而易見的屬性:model,model用於指定該視圖所基於的模型。

template_name

可以看到視圖層部分的代碼十分簡單,ListView還有一個屬性:template_name,表示用於呈現前端界面的模板,但在這裏沒有指定。需要注意的是,如果我們沒有指定template_name,那麼Django將會自動推斷出一個template_name出來,在本例中,它將會推斷出template_name爲 books/publisher_list.html,其中books爲該app的名字,publisher爲模型名的小寫。

另外,讓我們想一想,如果我們使用函數視圖來實現這一步功能的話,我們還需要傳遞一個參數字典過去,但是ListView怎麼完成這一步呢?
默認情況下,ListVIew會傳遞一個object_list到模板層,因此這個模板可能是這樣子的:

{% extends "base.html" %}

{% block content %}
    <h2>Publishers</h2>
    <ul>
        {% for publisher in object_list %}
            <li>{{ publisher.name }}</li>
        {% endfor %}
    </ul>
{% endblock %}

context_object_name

看起來挺好的,但是對於開發前後端分離的項目來說,你向前端傳遞了一個object_list,前端怎麼知道這個object_list到底是哪個object?因此你最好在類視圖中定義如下屬性:context_object_name,該屬性會指定object_list的新名字。

可能如下所示:

views.py:

from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    model = Publisher
    context_object_name = 'my_favorite_publishers'

DetailView

好了,現在又有新的需求來了。

如果我們想在每一個Publisher的詳情頁面展示一個書籍的列表,那麼該怎麼做呢?

還是先思考一下傳統思路。如果用視圖函數來完成這個功能的話,我們只需要在調用detail.html頁面時把book_list傳進去就好了,如果用內建視圖的話,這需要用到DetailView

如下:

from django.views.generic import DetailView
from books.models import Book, Publisher

class PublisherDetail(DetailView):

    model = Publisher

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['book_list'] = Book.objects.all()
        return context

默認情況下,get_context_data()需要一個self.object參數,但是你可以在你的子類中重寫該方法以讓它攜帶更多的數據。在本例中,self.object自然是publisher,但是我們還需要書籍列表,因此重寫了它以使它攜帶了book_list這一參數。


queryset

在上面的例子中,我們用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,我們可以對objects進行進一步的篩選,比如:

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'

動態過濾

你可能注意到,上面的代碼有一個很明顯的問題,publisher__name被硬指定爲ACME Publishing,但其實我們想要的是對於任意的publisher,我們都能得到正確的book_list,這又該怎麼做呢?

所幸ListView提供有get_queryset()方法,它返回queryset,我們可以覆寫它以便在get_queryset()中添加更多的邏輯。

使用此方法的關鍵是需要知道,從urlconf來的所有參數,包括request,傳入類視圖中後都會被存儲在self中,比如self.request、self.args、self.kwargs。

示例如下:

urls.py

from django.urls import path
from books.views import PublisherBookList

urlpatterns = [
    path('books/<publisher>/', PublisherBookList.as_view()),
]

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)

如果我們願意,我們還可以將publisher的信息添加到上下文中,如下:

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)
        
    def get_context_data(self, **kwargs):
	    context = super().get_context_data(**kwargs)
	    context['publisher'] = self.publisher
	    return context

關於內建類視圖,這裏先說這麼多,事實上,Django的內建類視圖提供有豐富的功能,關於它們更詳盡的介紹將在之後給出。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章