本文是对 Django 官网文档的翻译。
官网链接:https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/
适用 Django 1.11
实现 Web 应用程序可能是单调的,因为总是重复某些模式。 Django 试图在模型层和模板层减少一些单调,但 Web 开发人员在视图层也将体会到无聊。
Django 通用视图希望可以减轻这种痛苦。他们从视图开发过程中中总结一些常用习语和模式,将其抽象出来,以便无需编写太多代码就可以快速实现数据的常用视图。
我们可以识别某些常见任务,例如显示对象列表并编写显示任意对象列表的代码。 然后,将模型以额外参数的形式传入 URLconf 。
Django 发布通用视图来实现以下功能:
展示列表页面和单个对象的详细页面。如果我们创建一个管理会议的应用,TalkListView 和 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): # __unicode__ on Python 2
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): # __unicode__ on Python 2
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 :
# urls.py
from django.conf.urls import url
from books.views import PublisherList
urlpatterns = [
url(r'^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>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
这真的是所有的工作了。通用视图的所有酷炫功能来自于更改通用视图的属性。 通用视图参考文档详细记录了所有通用视图及其选项; 本文档的其余部分是自定义和扩展通用视图的一些常见方法。
制作“友好地”模板内容
我们可能注意到上面publisher列表模板在object_list中保存了所有的publisher。虽然这样做很好,但这对于模板作者并不是那么“友好”:他们必须“只知道”在这里处理publishers。
如果你正在处理一个模型对象,那么你的工作已经完成了。 当处理对象或queryset时,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通用视图为publisher提供内容,但是如何在模板中获取其他信息呢?
答案是使用DetailView子类,并提供get_context_data方法。 默认实现只是将显示的对象添加到模板中,但可以重写从而发送更多内容:
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
注意
通常来说,get_context_data将融合所有父类的内容数据到当前类的内容数据中。要在希望更改内容的类中保留父类行为需要在super类中调用get_context_data。当不存在两个类尝试定义相同的键的情况时,这将给出预期的结果。 但是,如果任何类尝试重写父类设置的键(在调用super之后),如果要确保覆盖所有父类,该类的任何子类也需要在super之后显式设置它。 如果遇到问题,请查看视图的方法解析顺序。另一个考虑因素是,类通用视图的内容数据将重写内容处理器提供的数据,详见get_context_data()提供的例子。
对象子集视图
现在,让我们近距离看一下一直使用的模型参数。指定视图操作数据模型的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我们还使用了自定义模板名称。 如果我们没有定义自定义模板名称,通用视图将使用与“vanilla”对象列表相同的模板(这可能不是我们想要的)。
还要注意,这不是一个优雅的列出出版商专有书籍的方式。 如果我们要添加另一个出版商页面,那么我们在URLconf中添加另外一行,如果有很多出版社那么将很难处理。 我们将在下一节中处理这个问题。
注意:
如果请求 /book/acme/ 时返回 404 ,需要检查是否存在名为“ACME Publishing'的出版商。通用视图有一个 allow_empty 参数用于这种情况,详见类视图参考。
动态过滤
另一个常见的需求是通过URL中的某些主键过滤列表页面中给出的对象。 以前我们在URLconf中硬编码出版商的名字,但是如果我们想写一个显示任意出版社的所有书籍的视图呢?
很方便,ListView有一个可以覆盖的get_queryset()方法。 之前,它仅仅返回了queryset属性的值,但现在我们可以添加更多的逻辑。
实现这个工作的关键在于调用类视图时,有用的内容都被存储到self中; 它包括请求(self.request)、根据URLconf得到的位置参数(self.args)和名称参数(self.kwargs)。
我们有一个获得单个组的URLconf:
# urls.py
from django.conf.urls import url
from books.views import PublisherBookList
urlpatterns = [
url(r'^books/([\w-]+)/$', 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.args[0])
return Book.objects.filter(publisher=self.publisher)
正如我们看到的,为queryset添加更多逻辑非常简单。如果我们愿意,我们将使用self.request.user来过滤当前用户,或者使用更多的复杂逻辑。
我们也可以同时将出版社添加到内容中,这样我们就可以在模板中使用它们:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookList, self).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.conf.urls import url
from books.views import AuthorDetailView
urlpatterns = [
#...
url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
]
然后我们将写一个新的视图(get_object是获取对象的方法),我们只需重写并封装调用:
from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = timezone.now()
object.save()
# Return the object
return object
注意:这里的URLconf使用命名组pk-这是DetailView用于查找筛选查询集的主键的默认名称。
如果想调用组的其他内容,可以在视图中设置pk_url_kwarg。 详细信息可以在DetailView中找到。