Chinaunix首页 | 论坛 | 博客
  • 博客访问: 29957056
  • 博文数量: 2065
  • 博客积分: 10377
  • 博客等级: 上将
  • 技术积分: 21525
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-04 17:50
文章分类

全部博文(2065)

文章存档

2012年(2)

2011年(19)

2010年(1160)

2009年(969)

2008年(153)

分类: Python/Ruby

2009-08-14 17:55:50

这里仍是本书中一个重复的主题:最坏情况下,Web开发是单调乏味的。到目前为止,我们已经讲解了 Django 是怎样视图去除模型和模板层面的单调乏味的,但是Web开发者们在视图层面还经历着乏味。

    Django的通用视图被开发出来缓解那种痛苦。它提取了在视图开发中一般的习惯和模式并进行抽象,使你可以无需写多少代码就可以快速地写出一般的视图。事实上,前面章节中的几乎所有例子都可以在通用视图的帮助下进行重写。

    Django的通用模板做以下事情:

  • 实施一般的“简单”工作:重定向到不同的页面并渲染给定的模板。
  • 显示列表和一个对象的详细信息页面。第8章中的event_listentry_list视图是列表视图的例子。单一事件页面是我们称之为"细节"视图的一个例子。
  • 显示基于日期的按照年/月/日排列的存档页面,关联的细节,和“最新”页面。Django的博客(http://www.djangoproject.com/weblog/)年、月、日存档就是用这个创建的,是一种典型的报纸存档。
  • 允许用户创建,更新和删除对象——使用或不使用认证。

    总而言之,这些视图提供简单的接口来实施开发者遇到的大多数一般的工作。

使用通用视图

    所有这些视图都是通过在你的UTLconf文件中创建配置字典并把这些字典作为给定模式的URLconf元组的第三个成员来使用的。

    例如,这里是一个简单的用来显示一个静态的"关于"页面的URLconf:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template

urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
})
)

    虽然第一眼看起来这有点像“魔法”——看,没有代码的视图!——它实际上和第8章的例子一模一样:direct_to_template 视图简单地从额外的参数字典中抓取信息并在渲染视图的时候使用这些信息。

    因为这个通用视图——其他的也一样——是同其他任何视图一样的正常视图,我们可以在我们自己的视图中重用它们。作为例子,让我们扩展我们的“关于”例子来把/about//表单映射到静态渲染的about/.html。我们首先修改URLconf使其指向一个视图函数:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from mysite.books.views import about_pages

urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
}),
('^about/(w+)/$', about_pages),
)

    接下来,我们写出about_pages视图:

from django.http import Http404
from django.template import TemplateDoesNotExist
from django.views.generic.simple import direct_to_template

def about_pages(request, page):
try:
return direct_to_template(request, template="about/%s.html" % page)
except TemplateDoesNotExist:
raise Http404()

    这里我们把direct_to_template与其他任何函数同等对待。因为它返回一个HttpResponse,我们可以简单地原样返回它。这里唯一稍微麻烦点的工作是处理找不到模板的情况。我们不想一个不存在的模板导致服务器错误,所以我们捕获TemplateDoesNotExist异常并返回一个404错误页面代替。

这里存在安全漏洞么?

    目光敏锐的读者可能已经注意到了一个安全漏洞:我们正直接插入来自浏览器的内容(template="about/%s.html" % page)构造模板名。咋一看,这好显示一个经典的目录遍历漏洞(将在第19章详细讨论)。但真的是么?

    并不确切。是的,恶意的 page 值可以引起目录遍历,但是所有的 page 值都是同请求URL中获取的,并不是任何值都可以被接受。关键在于URLconf中:我们使用正则表达式\w+来匹配URL的 page 部分,\w 只接受字母和数字。因此,任何恶意字符(这里是点和斜杠)都会在到达视图之前被URL解析器拒绝。

对象的通用视图

    direct_to_template显然很有用,但是Django的通用视图在你用以显示数据库中的内容的时候尤其耀眼。因为这是一项很通常的工作,Django内建的几个通用视图使得生成列表和对象的详细视图令人难以置信地简单。

    让我们看看这些通用视图中的一个:“对象列表”视图。我们将使用第5章中的Publisher对象:

class Publisher(models.Model):
name = models.CharField(maxlength=30)
address = models.CharField(maxlength=50)
city = models.CharField(maxlength=60)
state_province = models.CharField(maxlength=30)
country = models.CharField(maxlength=50)
website = models.URLField()

def __str__(self):
return self.name

class Meta:
ordering = ["-name"]

class Admin:
pass

    为构建一个所有书籍的列表,我们使用以下的URLconf行:

from django.conf.urls.defaults import *
from django.views.generic import list_detail
from mysite.books.models import Publisher

publisher_info = {
"queryset" : Publisher.objects.all(),
}

urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)

    这些就是我们需要写的所有 Python 代码。然而,我们还需要写一个模板。我们可以通过在额外参数字典中包含一个template_name关键字明确地告诉object_list视图使用哪 一个模板,但在没有明确指定的时候Django会从对象的名字推导出一个。这种情况下,推导出的模板名是"books/publisher_list.html"—— “books”部分来自于定义模型的应用的名字,“publisher”部分仅仅是模型名的小写形式。

    这个模板会在一个包含所有书籍对象的称为object_list的变量的上下文环境里被渲染。一个很简单的模板看起来如下:

{% extends "base.html" %}

{% block content %}

Publishers



    {% for publisher in object_list %}
  • {{ publisher.name }}

  • {% endfor %}

{% endblock %}

    这就是所有要做的事情了。通用视图的所有很酷的特性都来自于改变传递给通用视图的"info"字典。附录D有所有的通用视图和它们的选项的详细文档;本章剩余部分会考虑你或许会用来定制和扩展通用视图的几种通常的方式。

扩展通用视图

    没人怀疑使用通用视图可以充分地加快开发速度。但是,在许多项目中,总有一些地方通用视图不能够满足需要。确实,Django开发新手们最常问的问题就是如何使通用视图处理范围更广的情况。

    幸运的是,在几乎所有的情况中,都有一种简单方式来扩展通用视图以处理更广范围的用例。这些情况通常会落在接下来的小节处理的几种模式中。

构造“友好的”模板上下文

    你或许已经注意到那个简单的出版社列表模板把所有的书籍保存在一个 object_list 变量中。虽然这工作得很好,但是它对模板的作者来说是不“友好”的:他们“只需要知道”他们在这里正在处理书籍。那个变量的一个更好的名字是 publisher_list;这样变量的内容就一目了然了。

    我们可以使用template_object_name参数容易地改变那个变量的名字:

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
}

urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)

    提供一个有用的 template_object_list 参数总是一个好主意。你的设计模板的合作者会感谢你。

添加额外的上下文

    通常你需要使用通用视图显示一些提供的之外的额外的信息。例如,考虑在每个出版商详细信息页面显示一个所有其他出版商列表。object_ldetail通用视图提供publisher对象给上下文,但是看起来没有办法在那个模板中得到一个所有出版商列表。

    但是还有一条:所有的通用视图都接受一个额外的可选参数,extra_context。这是一个添加给模板上下文的额外对象的字典。因此,为给详细信息视图提供一个所有出版社列表,我们需要使用一个像这样的信息字典:

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : Book.objects.all()}
}

    这将在模板上下文中添加一个{{ book_list }}变量。这个模式可以用来传递任何信息给通用视图的模板。很方便。

    但是,这里有一个小Bug——你能看出来么?

    问题发生在extra_context的查询被执行时。因为这个例子中把Publisher.objects.all()放 在了URLconf中,它只被执行一次(当URLconf第一次载入时)。一旦你添加或删除出版社,你会注意到通用视图并不会反映那些改变,直到你重新载 入Web服务器(查看附录C中的“Caching and QuerySets”,得到有关查询集被缓存和获取时的更多信息)。

注意

    这个问题不应归罪于queryset 通用视图参数。因为Django知道特定的查询结果集永远不应该被缓存,通用视图负责在每一个视图都被渲染之后清理缓存。

    解决方案是在extra_context中使用回调而不是值。任何传递给extra_context的可调用的东西(例如,一个函数)都会在视图被渲染的时候执行(而不是只执行一次)。你可以使用一个显式定义的函数:

def get_books():
return Book.objects.all()

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : get_books}
}

    或者你可以使用一个不太明显但是更短的依赖于Publisher.objects.all本身就是可调用的这个事实的版本:

publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : Book.objects.all}
}

    注意Book.objects.all后面没有了圆括号;这里引用了这个函数而并没有真正调用它(通用视图之后会调用它)。

对象子集的视图

    现在让我们更仔细地看看这个我们一直在使用的queryset关键字。大多数的通用视图使用这些queryset参数之一——它告诉视图显示哪一个对象集合(查看第5章有关“对象的选择”的小节获取对查询结果集的介绍,附录C有完整的详细信息)。

    举一个简单的例子,我们或许想要得到一个按出版日期排列的清单,最新出版的在第一位:

book_info = {
"queryset" : Book.objects.all().order_by("-publication_date"),
}

urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/$', list_detail.object_list, book_info),
)

    这确实是个简单的例子,但是它很好地阐述了这个想法。当然,你通常想要做比记录对象更多的事情。如果你想要显示特定出版商的一个书籍列表,你可以使用同样的技术:

apress_books = {
"queryset": Book.objects.filter(publisher__name="Apress Publishing"),
"template_name" : "books/apress_list.html"
}

urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/apress/$', list_detail.object_list, apress_books),
)

    注意除了使用经过过滤的queryset,我们还使用了自定义的模板名。如果我们没有,通用视图会使用与“原始”对象列表相同的模板,或许不是我们想要的。

    还要注意这不是解决指定出版商书籍列表问题的非常优雅的方式。如果我们想要添加另一个出版社页面,我们需要在URLconf中定义更多行,并且多个出版社会得到不合理的结果。我们会在接下来的小节处理这个问题。

注意

    如果你在请求/books/apress/时得到一个404错误,检查你是否确实有一个叫做‘Apress Publishing’的Publisher对象。通用视图对此情况有一个allow_empty参数。查看附录D获取更详细的信息。

使用封装的函数进行复合过滤

    另一个通常的需求是在列表页面中使用URL中的关键字过滤对象。先前我们把出版社的名字硬编码在URLconf中,但是当我们想要写一个能够显示任意出版 社的所有书籍的视图的时候该怎么办呢?我们可以“包装”通用视图来避免手工写大量的代码。通常,我们从写URLcof开始:

urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/(w+)/$', books_by_publisher),
)

    接下来,我们写books_by_publisher视图本身:

from django.http import Http404
from django.views.generic import list_detail
from mysite.books.models import Book, Publisher

def books_by_publisher(request, name):

# Look up the publisher (and raise a 404 if it can't be found).
try:
publisher = Publisher.objects.get(name__iexact=name)
except Publisher.DoesNotExist:
raise Http404

# Use the object_list view for the heavy lifting.
return list_detail.object_list(
request,
queryset = Book.objects.filter(publisher=publisher),
template_name = "books/books_by_publisher.html",
template_object_name = "books",
extra_context = {"publisher" : publisher}
)

    这会工作,因为通用视图实在没有什么特殊的——它们仅仅是Python函数。像任何视图函数一样,通用视图需要一组特定的参数集合并返回一个 HttpResponse对象。因此,通过一个小函数包装一个通用视图来在通用视图之前(或之后;见下一节)做一些附加的工作令人难以置信地简单。

注意

    注意在前面的例子中我们在extra_context中传递了当前正在显示的出版社。这在这种风格的包装中通常是个好主意;它让模板知道哪个“父”对象当前正在被浏览。

实施额外的工作

    我们要查看的最后一个通用模式是在调用通用视图之前或之后做一些额外的工作。

    假想我们在Author对象中有一个last_accessed域用来保存任何最后一个访问该作者的人。当然,通用视图object_detail不会知道有关该域的事情,但是我们仍然可以很容易地写一个自定义视图来保持此域被更新。

    首先,我们需要在URLconf加入作者细节部分,使其指向自定义视图:

from mysite.books.views import author_detail

urlpatterns = patterns('',
#...
(r'^authors/(?Pd+)/$', author_detail),
)

    然后我们写出我们的包装函数:

import datetime
from mysite.books.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404

def author_detail(request, author_id):
# Look up the Author (and raise a 404 if she's not found)
author = get_object_or_404(Author, pk=author_id)

# Record the last accessed date
author.last_accessed = datetime.datetime.now()
author.save()

# Show the detail page
return list_detail.object_detail(
request,
queryset = Author.objects.all(),
object_id = author_id,
)

注意

    如果你没有在Author模型中添加last_accessed域并创建books/author_detail.html模板,这段代码不会自动工作。

    我们可以使用类似的用法来改变通用视图返回的响应。如果我们想要提供此作者列表的一个可下载的纯文本版本,我们可以使用如下视图:

def author_list_plaintext(request):
response = list_detail.object_list(
request,
queryset = Author.objects.all(),
mimetype = "text/plain",
template_name = "books/author_list.txt"
)
response["Content-Disposition"] = "attachment; filename=authors.txt"
return response

    这个视图可以工作,因为该通用视图返回了简单的可被作为字典对待来设置HTTP头的HttpResponse对象。顺便说一下,Content-Disposition逻辑,告诉浏览器下载并保存此页面而不是在浏览器中显示它。

接下来是什么?

    本章中我们只涉及了几个通用视图,但是这里所展示的主要思想可以漂亮地应用到任何通用视图。附录D涵盖了所有可用视图的细节,如果你想要了解这个强大的特性的大部分细节,建议阅读这部分内容。

    在下一章我们深入Django模板的内部工作细节,展示它们可以被扩展的所有很酷的方式。到现在为止,我们大部分时间是把模板作为你渲染内容的静态工具来使用的。

===================================================================

基于2008年5月11日所见之版本翻译。

阅读(774) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~