写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会从对象名推断出一个模板名称。在这个例子中,django推断出的模板名将会是books/publisher_list.html
,books
来自于定义模型的应用名,而publisher
是小写的模型名。
笔记
因此,当在
TEMPLATES
参数中,django
模板的APP\_DIRS
选项设置成True
时,模板的位置会是/path/to/project/books/templates/books
/
这个模板将以一个包涵名为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
能使用模型类的小写名填充上下文。例如除了默认的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 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
方法将所有父类中的上下文数据与当前类合并。为了在你想修改上下文的类中保证这样的方式,你得确保调用父类的get_context_data
方法。当没有两个类试图定义相同的键时,这将给期望的结果。但是,如果某个类试图在父类设置后(在调用super
方法之后)重写一个键,那么该类的子类们也需要显式地设置它,才能想确保覆盖它们所有的父类。如果您有问题,请查看视图的方法解析顺序。另一个考虑是类通用视图的上下文数据将会覆盖上下文处理器提供的数据;参看 get_context_data() 方法的示例
查看对象子集
现在,让我们仔细看看我们之前一直在使用的模型参数。模型参数指定视图要操作的数据库模型,它可用于所有对单个对象或对象集合进行操作的通用视图。然而,模型参数并不是指定视图操作对象的唯一方式-你也可以使用查询集参数指定对象列表:
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()
的快捷方式。然而,通过使用查询定义一个过滤列表,你可以使在视图中可见对象,更加明确。(查看 Making queries 获得更多关于 QuerySet 对象的信息, 对于完整的细节也可以看看 class-based views reference ).
举一个简单的例子,我们可能想要根据最近的出版日期排序图书列表:
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'
注意除了过滤查询集以外,我们还是用了自定义的模板名。如果我们没有那样做,通用视图将使用普通的对象列表名一样名称的模板,这可能不是我们所要的。
还要注意到,这不是处理指定出版社图书的优雅方式。如果我们想添加另一个出版社页面,需要在URLconf
中增加一行设置,然后,数量更多的出版社就会觉得不太合理吧。下一节我们来处理这个问题。
笔记
请求
/books/acme/
时如果你得到一个404错误, 检查你要确保有名字是‘ACME Publishing’的出版社。对于这个例子通用视图有一个允许为空的参数。更多细节请看 class-based-views reference。
动态过滤
另一个常用需求是通过URL中的某个键过滤列表页面中的指定的对象。之前我们在URLconf
中对出版社进行了硬编码,但是如果我们想写一个视图,显示任意一个出版商的所有书籍呢?
ListView
有一个get_queryset()
方法,我们可以方便地重写。之前,它仅仅返回查询集属性的值,但现在我们可以添加更多的逻辑。
这项工作的关键是当类视图被调用时,很多有用的信息都被 self
保存着;除了 request(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)
如你所见,它很容易向查询选择添加更多的逻辑;如果我们需要,可以用 self.request.user
去过滤当前使用的用户, 或者更复杂的逻辑。
同时,我们可以添加publisher
到上下文中,让我们可以在模板中使用它:
# ...
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
完成额外的工作
最后一个日常模式是,我们会考虑在调用通用视图的前后做一些额外的工作。
设想有一个最近登录(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()
通用详细视图(generic DetailView class
)类当然不知道这个字段,但是我们可以再简单地写一个自定义视图来更新那个字段。
首先,我们需要在URLconf
中增加一条作者信息的url条目对应自定义视图:
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
- 详细视图使用这个默认名寻找用于筛选查询集的主键的值。如果你想调用别的组,你可以在视图上设置
pk_url_kwarg
参数。更多信息可以在 DetailView 中找到。