内置的基于类的通用视图

编写Web应用时,某些模式会反复出现,使开发变得单调。Django在模型层和模板层缓解了一部分重复工作,但视图层同样存在这种问题。 Django通用视图抽象出视图开发中常见的模式,帮助开发者更快实现常规数据视图,减少重复代码。

例如,把“显示对象列表”识别为通用任务后,就可以编写显示任意对象列表的代码,再将具体模型作为参数交给URLconf。

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

  • 为单个对象显示列表和详情页。如果我们创建一个管理会议的应用,那么 TalkListView 和 RegisteredUserListView 就是列表视图的例子。单个”话题页”将作为例子中的”详情页”。

  • 在年/月/日的归档页面,相关的详情和最新页面将显示基于日期的对象。

  • 处理对象的创建、更新与删除,支持带授权检查或不带授权检查的情形。

这些视图为开发者经常遇到的通用任务提供接口。

扩展通用视图

通用视图能够显著加快开发,但也有不适用的项目需求。新手经常提出的问题,就是怎样让通用视图处理更广泛的情况。

这也是通用视图在Django1.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()),
]

这就是我们需要编写的所有代码。尽管我们仍然需要编写一个模板。我们可以给视图添加 template_name 属性来告诉视图使用哪个模板,但如果没有明确的模板,Django 将从对象名称中推断一个。在这个例子中,推断模板将是 "books/publisher_list.html" —— “books” 部分来自定义模型所属 app 名称,而 “publisher” 必须是模型名称的小写。

注意:

如果TEMPLATES中的DjangoTemplates后端启用APP_DIRS=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 %}

这就是全部内容。通用视图的所有炫酷功能来自改变通用视图上的属性设置。generic views reference 文档里有所有的通用视图和选项的详细说明;这篇文档剩下的部分将介绍一些你可能需要自定义和扩展通用视图的常用办法。

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

你可能已经注意到了例子中的出版者列表模板在变量名为 object_list 里保存了所有的出版者。尽管它工作正常,但它对模板作者并不是特别友好:他们必须在这里处理出版者信息。

处理模型对象或查询集时,Django还会使用模型类名的小写形式提供上下文变量。本例的publisher_list与默认object_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会合并所有父类的上下文。覆盖该方法时,先调用超类方法可保留这一行为。没有两个类定义同一个键时,合并会得到预期结果。如果某个类在调用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"

注意,和过滤的查询结果一起,我们还要指定自定义的模板名称。如果我们不这么做,通用视图将使用与 “vanilla” 对象列表相同的模板,这可能不是我们想要的。 还需要注意,这不是一个特别优雅的获取指定出版者书籍的方法。如果你想添加其他出版者页面,我们需要在URLconf中再添加几行,但如果多个出版者,这就变得不合理了。我们将在下一个部分来处理这个问题。

注意:

访问/books/acme/时若得到404,请确认数据库中确实存在名称为ACME Publishing的Publisher。通用视图提供allow_empty参数处理这一情形;详见基于类的视图参考。

动态过滤

另一个常见需求是依据URL中的某个键过滤列表对象。前面把出版者名称硬编码在URLconf中;如果希望视图能够显示任意出版者的全部书籍,就需要动态过滤。

我们可以方便地覆盖 ListView 的 get_queryset() 方法。默认情况下,它返回 queryset 属性值,但现在我们可以用它来添加更多逻辑。

基于类的视图被调用时,常用信息会保存在self中:请求保存在self.request,URLconf捕获的位置参数在self.args,命名参数在self.kwargs。 下面的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 来过滤当前用户或其他更复杂的逻辑。

我们也可以同时添加出版者到上下文中,因此我们能在模板中使用它:

# ...


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

执行额外的任务

最后一个常见模式里,我们将看到涉及在调用通用视图前后执行一些附加任务。

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

首先,我们需要在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执行,因此可以覆盖它,在原调用周围添加操作:

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。


来源:Django6.1官方中文文档:内置的基于类的通用视图。Copyright (c) Django Software Foundation and individual contributors. All rights reserved。正文沿用官方中文译文,补译剩余英文段落,整理语句及排版;代码、模板和注释保持原文。原文版本路径为6.1。

许可依据:Django官方LICENSE。

Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be used
   to endorse or promote products derived from this software without
   specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容