阅读(712) (23)

内置基于类的通用视图

2020-06-13 11:11:59 更新

内置基于类的通用视图

编写Web应用程序可能是单调的,因为我们一次又一次地重复某些模式。Django试图消除模型和模板层的某些单调性,但Web开发人员也在视图级别上遇到这种无聊的情况。

开发了Django的通用视图来缓解这种痛苦。它们采用了视图开发中发现的某些常见习语和模式,并对它们进行了抽象,以便您可以快速编写数据的通用视图而无需编写太多代码。

我们可以识别某些常见任务,例如显示对象列表,并编写显示任何对象列表的代码。然后,可以将所讨论的模型作为附加参数传递给URLconf。

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

  • 显示单个对象的列表和详细信息页面。如果我们正在创建一个用于管理会议的应用程序,则a 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 PublisherList(ListView):
  model = Publisher

最后,将该视图挂接到您的网址中:

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

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

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

注意:因此,当(例如)将 后端的APP_DIRS选项DjangoTemplates设置为True in时TEMPLATES,模板位置可以是:/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 %}

这就是全部。通用视图的所有很酷的功能都来自更改通用视图上设置的属性。该 通用视图引用文档中的所有详细的通用视图的选择; 本文档的其余部分将考虑一些您可以自定义和扩展通用视图的常用方法。

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

您可能已经注意到我们的示例发布者列表模板将所有发布者存储在名为的变量中object_list。尽管这很好用,但对模板作者并不是那么“友好”:他们必须“只是知道”他们在这里与发行人打交道。

好吧,如果您要处理模型对象,那么已经为您完成了。当您处理对象或查询集时,Django可以使用模型类名称的小写形式填充上下文。除了默认object_list条目之外,还提供了此条目,但包含完全相同的数据,即publisher_list。

如果仍然不能很好地匹配,则可以手动设置上下文变量的名称。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的东西总是一个好主意。您设计模板的同事将感谢您。

添加额外的上下文

通常,您需要提供一些超出通用视图所提供信息的额外信息。例如,考虑在每个出版商详细信息页面上显示所有书籍的列表。该DetailView 通用视图提供了出版商到上下文,但是我们如何在模板中获取更多的信息?

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

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将所有父类的上下文数据与当前类的上下文数据合并。若要在要更改上下文的自己的类中保留此行为,请务必确保调用 get_context_data超类。当没有两个类尝试定义相同的键时,这将提供预期的结果。但是,如果任何类在父类设置了键之后都尝试覆盖键(在调用super之后),则该类的所有子级也需要在super之后显式设置键,以确保覆盖所有父键。如果遇到问题,请查看视图的方法解析顺序。

另一个考虑是基于类的通用视图的上下文数据将覆盖上下文处理器提供的数据。请参阅 get_context_data()示例。

查看对象的子集

现在,让我们仔细看看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()``querysetQuerySet

举个例子,我们可能想按出版日期订购书籍清单,以最新的为准:

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'

这是一个非常小的例子,但是很好地说明了这个想法。当然,通常您不仅仅需要对对象重新排序,还需要做更多的事情。如果要显示特定出版商的书籍列表,则可以使用相同的技术:

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,我们还使用了自定义模板名称。如果我们不这样做,则通用视图将使用与“香草”对象列表相同的模板,而这可能不是我们想要的。

另请注意,这不是制作出版商特定书籍的一种非常优雅的方法。如果我们要添加另一个发布者页面,则需要在URLconf中再加上几行,并且不止几个发布者会变得不合理。我们将在下一部分中解决这个问题。

注意:如果在请求时收到404,请/books/acme/检查以确保您实际上拥有名称为'ACME Publishing'的发布商。通用视图allow_empty对此情况有一个参数。

动态过滤

另一个常见的需求是通过URL中的某个键过滤列表页面中给定的对象。之前我们在URLconf中硬编码了出版商的名称,但是如果我们想编写一个视图来显示某个任意出版商的所有书籍,该怎么办?

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

进行这项工作的关键部分是,当调用基于类的视图时,各种有用的东西都存储在self;以及request(self.request)包括根据URLconf捕获的position(self.args)和基于名称的(self.kwargs)参数。

在这里,我们有一个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当前用户或其他更复杂的逻辑进行过滤。

我们还可以同时将发布者添加到上下文中,因此我们可以在模板中使用它:

# ...

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中添加作者详细信息位以指向自定义视图:

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用于查找用于过滤查询集的主键的值。

详情参考: https://docs.djangoproject.com/en/3.0/