← CBV | 总目录 | 内置编辑视图 →
编写Web应用程序可能是单调的,因为我们一次又一次地重复某些模式。Django试图在模型和模板层中消除一些单调,但Web开发人员也在视图级别遇到这种无聊。
Django的通用视图(generic views)是为缓解这种痛苦而开发的。它们采用视图开发中的某些常用习语和模式并对其进行抽象,以便您可以快速编写数据的公共视图,而无需编写太多代码。
我们可以识别某些常见任务,例如显示对象列表,以及编写显示任何对象列表的代码。然后,可以将相关模型作为额外参数传递给URLconf。
Django附带通用视图来执行以下操作:
年/月/日
归档页面,相关详细信息和“最新”页面中显示基于日期的对象。总之,这些视图提供了简单的界面来执行开发人员遇到的最常见任务。
毫无疑问,使用通用视图可以大大加快开发速度。然而,在大多数项目中,通用视图不再足够。实际上,新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
最后将该视图与你的URL关联:
# 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
"- “books”部分来自定义模型的应用程序的名称,而“publisher”只是模型名称的小写版本。
注意
当(例如) 后端TEMPLATES是DjangoTemplates并且APP_DIRS选项设置为True时,模板位置可以是:/path/to/project/books/templates/books/publisher_list.html
模板将渲染包含所有出版社的object_list变量 。一个非常简单的模板可能如下所示:
{% extends "base.html" %}
{% block content %}
<h2>Publishersh2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}li>
{% endfor %}
ul>
{% endblock %}
这就是它的全部内容。通用视图的所有很酷的功能都来自更改通用视图上设置的属性。该 通用视图参考文献详细描述所有通用视图的选项; 本文档的其余部分将考虑一些可以自定义和扩展通用视图的常用方法。
您可能已经注意到,我们的示例 publisher 列表模板将所有发布者存储在名为的变量中object_list。虽然这很好用,但对模板作者来说并不是那么“友好”:他们必须“只知道”他们在这里与publisher打交道。
好吧,如果你正在处理一个模型对象,这已经为你完成了。当您处理对象或查询集时,Django能够使用模型类名称的小写版本来填充上下文。除默认object_list含完全相同的数据的xxx_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 = 'publisherList'
提供有用context_object_name的总是一个好主意。你设计模板的同事会感谢你。
通常,您需要提供除通用视图的信息之外的一些额外信息。例如,考虑在每个publisher详细信息页面上显示所有书籍的列表。通用视图 DetailView 在上下文中提供了publisher ,但我们如何在模板中获取更多的信息?
答案是创建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()。
译者注:
DetailView是显示每条数据的,所以,url里应该这么配置
path('views4//' ,PublisherDetailView.as_view(),name='cbv_detail_view'),
在html中可以使用添加的额外数据:
<ul>
{% for b in book_list %}
<li>{{ b.title }} - {{ b.price }}li>
{% endfor %}
ul>
现在让我们仔细看看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定义过滤的对象列表,您可以更加具体地了解在视图中可见的对象(请参阅 执行查询 以获取有关QuerySet对象的更多信息,并查看基于类的视图参考 以获取完整的详细信息)。
要选择一个简单的示例,我们可能希望按发布日期订购图书列表,最新的第一个:
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'
请注意,除了过滤queryset之外,我们还使用了自定义模板名称。
另请注意,这不是一种非常优雅的显示出版商特定书籍的方式。如果我们想要添加另一个发布者页面,我们在URLconf中需要另外一些行,并且不止一些发布者会变得不合理。我们将在下一节讨论这个问题。
注意
如果您在请求/books/acme/
时获得404,请检查以确保您确实拥有名为“ACME Publishing”的发布者。对于此种情况,通用视图提供了allow_empty参数。有关更多详细信息,请参阅 基于类的视图参考。
另一个常见的需求是通过URL中的某个键过滤列表页面中给出的对象。之前我们在URLconf中对发布者的名称进行了硬编码,但是如果我们想编写一个显示某个任意发布者的所有书籍的视图呢?
很方便,我们可以覆盖ListView中的 get_queryset()方法。以前,它刚好返回queryset属性的值 ,但现在我们可以添加更多逻辑。
使这项工作的关键部分是,当调用基于类的视图时,self中存储各种有用的东西; 如request(self.request),它包括根据URLconf捕获的 self.args 和 self.kwargs参数。
在这里,我们有一个带有单个捕获组的URLconf:
# urls.py
from django.urls import path
from books.views import PublisherBookList
urlpatterns = [
path('books//' , 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)
如您所见,在查询集选择中添加更多逻辑非常容易; 如果我们想要,我们可以使用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中添加一个作者详细信息位以指向自定义视图:
from django.urls import path
from books.views import AuthorDetailView
urlpatterns = [
#...
path('authors//' , 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,更多细节可以在 DetailView参考文献中找到。