内置基于类的通用视图

编写Web应用程序可能会很单调,因为我们会一次又一次地重复某些模式。Django试图在模型层和模板层消除一些单调,但Web开发人员也在视图层体验到这种无聊。

Django 通用视图 是为了减轻疼痛。它们采用视图开发中发现的某些常见习惯用法和模式,并对它们进行抽象,这样您就可以快速地编写数据的通用视图,而无需编写太多代码。

我们可以识别某些常见任务,例如显示对象列表,并编写显示 any 对象。然后,所讨论的模型可以作为额外的参数传递给urlconf。

Django附带通用视图,可执行以下操作:

  • 显示单个对象的列表和详细信息页。如果我们正在创建一个应用程序来管理会议,那么 TalkListView 和A RegisteredUserListView 将是列表视图的示例。一个单独的谈话页面就是我们称之为“细节”视图的一个例子。

  • 以年/月/日存档页面、相关详细信息和“最新”页面显示基于日期的对象。

  • 允许用户创建、更新和删除对象——无论是否经过授权。

这些视图结合起来,提供了接口来执行开发人员遇到的最常见任务。

扩展通用视图

毫无疑问,使用通用视图可以大大加速开发。然而,在大多数项目中,总有一天通用视图不再足够。实际上,新Django开发人员最常见的问题是如何使通用视图处理更广泛的情况。

这就是为1.3版本重新设计泛型视图的原因之一——以前,它们是带有一系列令人困惑的选项的视图函数;现在,扩展泛型视图的推荐方法不是在URLconf中传递大量配置,而是将其子类化,并重写其属性或方法。

也就是说,通用视图将有限制。如果您发现您正努力将视图实现为一个通用视图的子类,那么您可能会发现使用您自己的基于类的视图或功能视图来编写所需的代码更有效。

在一些第三方应用程序中可以使用更多的通用视图示例,或者您可以根据需要编写自己的视图。

对象的一般视图

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

最后将该视图挂钩到您的URL中:

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

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

这就是我们需要编写的所有python代码。但是,我们仍然需要编写一个模板。我们可以通过添加 template_name 属性,但在没有显式模板的情况下,Django将从对象的名称中推断出一个。在这种情况下,推断的模板将 "books/publisher_list.html" --“books”部分来自定义模型的应用程序的名称,而“publisher”位是模型名称的小写版本。

备注

因此,当 APP_DIRS A选项 DjangoTemplates 后端在中设置为true TEMPLATES ,模板位置可以是:/path/to/project/books/templates/books/publisher_list.html

此模板将根据包含名为 object_list 包含所有publisher对象的。模板可能如下所示:

{% extends "base.html" %}

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

这就是一切。通用视图的所有酷特性都来自于更改通用视图上的属性集。这个 generic views reference 详细记录所有通用视图及其选项;本文档的其余部分将考虑一些您可能自定义和扩展通用视图的常用方法。

创建“友好”模板上下文

您可能注意到我们的示例发布者列表模板将所有发布者存储在一个名为 object_list . 虽然这样做很好,但对模板作者来说并不是那么“友好”:他们必须“只知道”他们在这里与出版商打交道。

如果你要处理一个模型对象,这已经为你完成了。当您处理对象或查询集时,Django能够使用模型类名称的较低版本填充上下文。这是除了默认设置之外提供的 object_list 但包含完全相同的数据,即 publisher_list .

如果仍然不匹配,可以手动设置上下文变量的名称。这个 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"

提供有用的 context_object_name 总是个好主意。设计模板的同事会感谢你的。

添加额外的上下文

通常,除了常规视图提供的信息外,还需要提供一些额外的信息。例如,考虑在每个出版商的详细信息页面上显示所有书籍的列表。这个 DetailView 通用视图向上下文提供发布者,但如何在该模板中获取其他信息?

答案是子类 DetailView 并提供您自己的 get_context_data 方法。默认实现将显示的对象添加到模板中,但您可以重写它以发送更多:

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 将所有父类的上下文数据与当前类的上下文数据合并。要在要更改上下文的类中保留此行为,应确保调用 get_context_data 在超级班上。当没有两个类试图定义相同的键时,这将给出预期的结果。但是,如果任何类在父类设置了键之后(在调用super之后)尝试重写该键,那么该类的任何子类也需要在super之后显式地设置它,如果他们希望确保重写所有父类的话。如果遇到问题,请查看视图的方法解决顺序。

另一个考虑因素是,来自基于类的通用视图的上下文数据将覆盖上下文处理器提供的数据;请参见 get_context_data() 举个例子。

查看对象子集

现在让我们仔细看看 model 我们一直在使用的论点。这个 model 参数指定视图将操作的数据库模型,它可用于对单个对象或对象集合操作的所有通用视图。然而, model 参数不是指定视图将操作的对象的唯一方法--您还可以使用 queryset 论点:

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 要定义对象的筛选列表,您可以更具体地了解将在视图中可见的对象(请参见 进行查询 有关的详细信息 QuerySet 对象,并查看 class-based views reference 详细信息)。

举一个例子,我们可能需要按出版日期排序图书列表,其中最新的一个是:

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"

这是一个非常小的例子,但它很好地说明了这个想法。您通常需要做的不仅仅是重新排列对象。如果要呈现特定出版商的图书列表,可以使用相同的技巧:

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"

注意,与过滤 queryset ,我们还使用自定义模板名称。如果不这样做,通用视图将使用与“普通”对象列表相同的模板,这可能不是我们想要的。

还要注意,这不是一种非常优雅的方式来做出版商的具体书籍。如果我们想添加另一个发布者页面,我们将需要在URLConf中添加另外几行,并且超过几个发布者将变得不合理。我们将在下一节中处理这个问题。

备注

如果你请求时得到404 /books/acme/ ,检查以确保您实际拥有一个名为“acme publishing”的发布服务器。一般视图具有 allow_empty 此情况的参数。见 class-based-views reference 了解更多详细信息。

动态滤波

另一个常见的需求是通过URL中的某个键过滤列表页中给定的对象。早些时候,我们在URLConf中硬编码了出版商的名字,但是如果我们想写一个视图,显示某个任意出版商的所有书籍呢?

轻而易举地 ListView 有一个 get_queryset() 方法可以重写。默认情况下,它返回 queryset 属性,但我们可以使用它来添加更多的逻辑。

实现这一点的关键部分是,当调用基于类的视图时,各种有用的东西存储在 self 以及请求 (self.request )这包括位置 (self.args )以名称为基础 (self.kwargs )根据urlconf捕获的参数。

这里,我们有一个带有单个捕获组的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 向queryset选择添加逻辑既方便又强大。例如,如果我们愿意,我们可以使用 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

做额外的工作

最后一个常见的模式是在调用通用视图之前或之后做一些额外的工作。

假设我们有一个 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 类不会知道这个字段的任何信息,但我们可以再次编写一个自定义视图来更新该字段。

首先,我们需要在urlconf中添加一个author详细信息位,以指向自定义视图:

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 在视野中。