第十一章 输出非 HTML 内容
通常当我们谈到开发网站时,主要谈论的是 HTML。当然,Web 远不只有 HTML,我们在 Web 上用多种格式来发布数据:RSS、PDF、图片等。
到目前为止,我们的注意力都是放在常见 HTML 代码生成上,但是在这一章中,我们将会对使用 Django 生成其它格式的内容进行简要介绍。
Django 拥有一些便利的内建工具帮助你生成常见的非 HTML 内容:
RSS/Atom 聚合文件
站点地图 (一个 XML 格式文件,最初由 Google 开发,用于给搜索引擎提示线索)我们稍后会逐一研究这些工具,不过首先让我们来了解些基础原理。
基础: 视图和 MIME 类型
还记得第三章的内容吗?
一个视图函数(view function),或者简称 view ,只不过是一个可以处理一个 Web 请求并且返回 一个 Web 响应的 Python 函数。这个响应可以是一个 Web 页面的 HTML 内容,或者一个跳转,或者一个 404 错误,或者一个 XML 文档,或者一幅图片,或者映射到任何东西上。
更正式的说,一个 Django 视图函数 必须
接受一个 HttpRequest 实例作为它的第一个参数
返回一个 HttpResponse 实例
从一个视图返回一个非 HTML 内容的关键是在构造一个 HttpResponse 类时,需要指定
mimetype 参数。通过改变 MIME 类型,我们可以告知浏览器将要返回的数据是另一种不同的类型。
下面我们以返回一张 PNG 图片的视图为例。为了使事情能尽可能的简单,我们只是读入一张存储在磁盘上的图片:
from django.http import HttpResponse def my_image(request):
image_data = open("/path/to/my/image.png", "rb").read()
return HttpResponse(image_data, mimetype="image/png")
就是这么简单。如果改变 open() 中的图片路径为一张真实图片的路径,那么就可以使用这个十分简单的视图来提供一张图片,并且浏览器可以正确的显示它。
另外我们必须了解的是”HttpResponse”对象应用了 Python 标准的文件应用程序接口(API)。这就是说你可以在 Python(或第三方库)任何用到文件的地方使用”HttpResponse”实例。
下面将用 Django 生成 CSV 文件为例,说明它的工作原理。
生成 CSV 文件
CSV 是一种简单的数据格式,通常为电子表格软件所使用。它主要是由一系列的表格行组成, 每行中单元格之间使用逗号(CSV 是 逗号分隔数值(comma-separated values) 的缩写)隔开。例如,下面是以 CSV 格式记录的一些违规航班乘客的数据。
Year,Unruly Airline Passengers 1995,146
1996,184
1997,235
1998,200
1999,226
2000,251
2001,299
2002,273
2003,281
2004,304
2005,203
备注
前面的列表是真实的数据,数据由美国联邦航空管理处提供。具体内容请参见 http://www.faa.gov/data_statistics/passengers_cargo/unruly_passengers/.
虽然 CSV 看上去简单,以至于简单到这个格式甚至都没有正式的定义。但是不同的软件会生成和使用不同的 CSV 的变种,在使用上会有一些不便。幸运的是,Python 使用的是标准 CSV库, csv ,所以它更通用。
因为 csv 模块操作的是类似文件的对象,所以可以使用 HttpResponse 替换: import csv
from django.http import HttpResponse
# Number of unruly passengers each year 1995 - 2005. In a real application
# this would likely come from a database or some other back-end data store. UNRULY_PASSENGERS = [146,184,235,200,226,251,299,273,281,304,203]
def unruly_passengers_csv(request):
# Create the HttpResponse object with the appropriate CSV header. response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment; filename=unruly.csv'
# Create the CSV writer using the HttpResponse as the "file" writer = csv.writer(response)
writer.writerow(['Year', 'Unruly Airline Passengers'])
for (year, num) in zip(range(1995, 2006), UNRULY_PASSENGERS): writer.writerow([year, num])
return response
代码和注释可以说是很清楚,但还有一些事情需要特别注意:
响应返回的是 text/csv MIME 类型(而非默认的 text/html )。这会告诉浏览器,返回的文档是 CSV 文件。
响应会有一个附加的 Content-Disposition 头部,它包含有 CSV 文件的文件名。这个头部(或者说,附加部分)会指示浏览器弹出对话框询问文件存放的位置(而不仅仅是显示)。这个文件名是任意的,它会用在浏览器的另存为对话框中。
与创建 CSV 的应用程序界面(API)挂接是很容易的:只需将 response 作为第一个变量传递给 csv.writer 。 csv.writer 函数希望获得一个文件类的对象,
HttpResponse 正好能达成这个目的。
调用 writer.writerow ,并且传递给它一个类似 list 或者 tuple 的可迭代对象,就可以在 CSV 文件中写入一行。
CSV 模块考虑到了引用的问题,所以您不用担心逸出字符串中引号和逗号。只要把信息传递给 writerow() ,它会处理好所有的事情。
在任何需要返回非 HTML 内容的时候,都需要经过以下几步:创建一个 HttpResponse 响应对象(需要指定特殊的 MIME 类型)。将它作为参数传给一个需要文件的方法,然后返回这个响应。
下面是一些其它的例子
生成 PDF 文件
便携文件格式 (PDF) 是由 Adobe 开发的格式,主要用于呈现可打印的文档,包含有
pixel-perfect 格式,嵌入字体以及 2D 矢量图像。PDF 文件可以被认为是一份打印文档的数字等价物;实际上,PDF 文件通常用于需要将文档交付给其他人去打印的场合。
可以方便的使用 Python 和 Django 生成 PDF 文档需要归功于一个出色的开源库, ReportLab (http://www.reportlab.org/rl_toolkit.html) 。动态生成 PDF 文件的好处是在不同的情况下,如不同的用户或者不同的内容,可以按需生成不同的 PDF 文件。
下面的例子是使用 Django 和 ReportLab 在 KUSports.com 上生成个性化的可打印的 NCAA赛程表 (tournament brackets) 。
安装 ReportLab
在生成 PDF 文件之前,需要安装 ReportLab 库。这通常是个很简单的过程:从
http://www.reportlab.org/downloads.html 下载并且安装这个库即可。
使用手册(原始的只有 PDF 格式)可以从 http://www.reportlab.org/rsrc/userguide.pdf下载,其中包含有一些其它的安装指南。
注意
如果使用的是一些新的 Linux 发行版,则在安装前可以先检查包管理软件。多数软件包仓库中都加入了 ReportLab 。
比如,如果使用(杰出的)Ubuntu 发行版,只需要简单的 apt-get install python-reportlab一行命令即可完成安装。
在 Python 交互环境中导入这个软件包以检查安装是否成功。
>>> import reportlab
如果刚才那条命令没有出现任何错误,则表明安装成功。
编写视图
和 CSV 类似,由 Django 动态生成 PDF 文件很简单,因为 ReportLab API 同样可以使用类似文件对象。
下面是一个 Hello World 的示例:
from reportlab.pdfgen import canvas from django.http import HttpResponse
def hello_pdf(request):
# Create the HttpResponse object with the appropriate PDF headers. response = HttpResponse(mimetype='application/pdf') response['Content-Disposition'] = 'attachment; filename=hello.pdf'
# Create the PDF object, using the response object as its "file." p = canvas.Canvas(response)
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality. p.drawString(100, 100, "Hello world.")
# Close the PDF object cleanly, and we're done. p.showPage()
p.save() return response
需要注意以下几点:
这里我们使用的 MIME 类型是 application/pdf 。这会告诉浏览器这个文档是一个
PDF 文档,而不是 HTML 文档。如果忽略了这个参数,浏览器可能会把这个文件看成
HTML 文档,这会使浏览器的窗口中出现很奇怪的文字。
使用 ReportLab 的 API 很简单:只需要将 response 对象作为 canvas.Canvas 的第一个参数传入。 Canvas 类需要一个类似文件的对象, HttpResponse 对象可以满足这个要求。
所有后续的 PDF 生成方法需要由 PDF 对象调用(在本例中是 p ),而不是 response对象。
最后需要对 PDF 文件调用 showPage() 和 save() 方法(否则你会得到一个损坏的
PDF 文件)。
复杂的 PDF 文件
如果您在创建一个复杂的 PDF 文档(或者任何较大的数据块),请使用 cStringIO 库存放临时生成的 PDF 文件。 cStringIO 提供了一个用 C 编写的类似文件对象的接口,从而可以使系统的效率最高。
下面是使用 cStringIO 重写的 Hello World 例子: from cStringIO import StringIO
from reportlab.pdfgen import canvas
from django.http import HttpResponse
def hello_pdf(request):
# Create the HttpResponse object with the appropriate PDF headers. response = HttpResponse(mimetype='application/pdf') response['Content-Disposition'] = 'attachment; filename=hello.pdf'
temp = StringIO()
# Create the PDF object, using the StringIO object as its "file." p = canvas.Canvas(temp)
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality. p.drawString(100, 100, "Hello world.")
# Close the PDF object cleanly. p.showPage()
p.save()
# Get the value of the StringIO buffer and write it to the response. response.write(temp.getvalue())
return response
其它的可能性
使用 Python 可以生成许多其它类型的内容,下面介绍的是一些其它的想法和一些可以用以实现它们的库。
ZIP 文件 :Python 标准库中包含有 zipfile 模块,它可以读和写压缩的 ZIP 文件。它可以用于按需生成一些文件的压缩包,或者在需要时压缩大的文档。如果是 TAR 文件则可以使用标准库 tarfile 模块。
动态图片 : Python 图片处理库 (PIL; http://www.pythonware.com/products/pil/) 是极好的生成图片(PNG, JPEG, GIF 以及其它许多格式)的工具。它可以用于自动为图片生成缩略图,将多张图片压缩到单独的框架中,或者是做基于 Web 的图片处理。
图表 : Python 有许多出色并且强大的图表库用以绘制图表,按需地图,表格等。我们不可能将它们全部列出,所以下面列出的是个中的翘楚。
matplotlib (http://matplotlib.sourceforge.net/) 可以用于生成通常是由
matlab 或者 Mathematica 生成的高质量图表。
pygraphviz (https://networkx.lanl.gov/wiki/pygraphviz) 是一个 Graphviz 图形布局的工具 (http://graphviz.org/) 的 Python 接口,可以用于生成结构化的图表和网络。
总之,所有可以写文件的库都可以与 Django 同时使用。请相信一切皆有可能。
我们已经了解了生成“非 HTML”内容的基本知识,让我们进一步总结一下。Django 拥有很多用以生成各类“非 HTML”内容的内置工具。
内容聚合器应用框架
Django 带来了一个高级的聚合生成框架,它使得创建 RSS 和 Atom feeds 变得非常容易。什么是 RSS?什么是 Atom?
RSS 和 Atom 都是基于 XML 的格式,你可以用它来提供有关你站点内容的自动更新的 feed。了解更多关于 RSS 的可以访问 http://www.whatisrss.com/, 更多 Atom 的信息可以访问 http://www.atomenabled.org/.
想创建一个联合供稿的源(syndication feed),所需要做的只是写一个简短的 python 类。你可以创建任意多的源(feed)。
高级feed 生成框架是一个默认绑定到/feeds/的视图,Django 使用URL 的其它部分(在/feeds/之后的任何东西)来决定输出 哪个 feed
要创建一个 feed, 您将创建一个 Feed 类, 并在您的 URLconf 中指向它. ( 查看第 3 章和第 8 章, 可以获取更多有关 URLconfs 的更多信息 )
初始化
为了在您的 Django 站点中激活 syndication feeds, 添加如下的 URLconf:
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}
),
这一行告诉 Django 使用 RSS 框架处理所有的以 "feeds/" 开头的 URL. ( 你可以修改
"feeds/" 前缀以满足您自己的要求. )
URLConf 里有一行参数:``{‘feed_dict’: feeds}``,这个参数可以把对应 URL 需要发布的 feed 内容传递给 syndication framework
特别的,feed_dict 应该是一个映射 feed 的 slug(简短 URL 标签)到它的 Feed 类的字典 你可以在 URL 配置本身里定义 feed_dict,这里是一个完整的例子
from django.conf.urls.defaults import *
from myproject.feeds import LatestEntries, LatestEntriesByCategory feeds = {
'latest': LatestEntries, 'categories': LatestEntriesByCategory,
}
urlpatterns = patterns('',
# ...
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
# ...
)
前面的例子注册了两个 feed:
LatestEntries``表示的内容将对应到``feeds/latest/ .
LatestEntriesByCategory``的内容将对应到 ``feeds/categories/ .以上的设定完成之后,接下来需要自己定义 Feed 类
一个 Feed 类是一个简单的 python 类,用来表示一个 syndication feed. 一个 feed 可能是简单的 (例如一个站点新闻 feed,或者最基本的,显示一个 blog 的最新条目),也可能更加复杂(例如一个显示 blog 某一类别下所有条目的 feed。这里类 别 category 是个变量).
Feed 类必须继承 django.contrib.syndication.feeds.Feed,它们可以在你的代码树的任何位置
一个简单的 Feed
例子来自于 chicagocrime.org,描述最近 5 项新闻条目的 feed: from django.contrib.syndication.feeds import Feed
from chicagocrime.models import NewsItem
class LatestEntries(Feed):
title = "Chicagocrime.org site news" link = "/sitenews/"
description = "Updates on changes and additions to chicagocrime.org."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]要注意的重要的事情如下所示:
子类 django.contrib.syndication.feeds.Feed .
title , link , 和 description 对应一个标准 RSS 里的 <title> , <link> , 和
<description> 标签.
items() 是一个方法,返回一个用以包含在包含在 feed 的 <item> 元素里的 list 虽然例子里用 Djangos database API 返回的 NewsItem 对象, items() 不一定必须返回 model 的实例
你可以利用 Django models 免费实现一定功能,但是 items() 可以返回你想要的任意类型的对象.
还有一个步骤,在一个 RSS feed 里,每个(item)有一个(title),(link)和(description),我们需要告诉框架 把数据放到这些元素中
如果要指定 <title> 和 <description> ,可以建立一个 Django 模板(见 Chapter 4)名字叫 feeds/latest_title.html 和 feeds/latest_description.html ,后者是 URLConf 里为对应 feed 指定的 slug 。注意 .html 后缀是必须的。
RSS 系统模板渲染每一个条目,需要给传递 2 个参数给模板上下文变量:
obj : 当前对象 ( 返回到 items() 任意对象之一 )。
site : 一个表示当前站点的 django.models.core.sites.Site 对象。 这对于
{{ site.domain }} 或者 {{ site.name }} 很有用。
如果你在创建模板的时候,没有指明标题或者描述信息,框架会默认使用 "{{ obj }}" ,对象的字符串表示。
你也可以通过修改 Feed 类中的两个属性 title_template 和 description_template 来改变这两个模板的名字。
你有两种方法来指定 <link> 的内容。 Django 首先执行 items() 中每一项的
get_absolute_url() 方法。 如果该方法不存在,就会尝试执行 Feed 类中的 item_link()方法,并将自身作为 item 参数传递进去。
get_absolute_url() 和 item_link() 都应该以 Python 字符串形式返回 URL。对于前面提到的 LatestEntries 例子,我们可以实现一个简单的 feed 模板。
latest_title.html 包括:
{{ obj.title }}
并且 latest_description.html 包含:
{{ obj.description }}这真是 太 简单了!
一个更复杂的 Feed
框架通过参数支持更加复杂的 feeds。
举个例子,chicagocrime.org 提供了一个 RSS 源以跟踪每一片区域的犯罪近况。如果为每一个单独的区域建立一个 Feed 类就显得很不明智。这样做就违反了 DRY 原则了,程序逻辑也会和数据耦合在一起。
取而代之的方法是,使用聚合框架来产生一个通用的源,使其可以根据 feeds URL 返回相应的信息。
在 chicagocrime 这个例子中,区域信息可以通过这样的 URL 方式来访问:
http://www.chicagocrime.org/rss/beats/0613/ :返回 0613 号地区的犯罪数据
http://www.chicagocrime.org/rss/beats/1424/ :返回 1424 号地区的犯罪数据
固定的那一部分是 "beats" (区域)。聚合框架看到了后面的不同之处 0613 和 1424 ,它会提供给你一个钩子函数来描述这些 URL 的意义,以及会对 feed 中的项产生的影响。
举个例子会澄清一切。下面是每个地区特定的 feeds:
from django.core.exceptions import ObjectDoesNotExist class BeatFeed(Feed):
def get_object(self, bits):
# In case of "/rss/beats/0613/foo/bar/baz/", or other such
# clutter, check that bits has only one member. if len(bits) != 1:
raise ObjectDoesNotExist
return Beat.objects.get(beat exact=bits[0])
def title(self, obj):
return "Chicagocrime.org: Crimes for beat %s" % obj.beat
def link(self, obj):
return obj.get_absolute_url()
def description(self, obj):
return "Crimes recently reported in police beat %s" % obj.beat
def items(self, obj):
crimes = Crime.objects.filter(beat__id__exact=obj.id) return crimes.order_by('-crime_date')[:30]
以下是 RSS 框架的基本算法,我们假设通过 URL /rss/beats/0613/ 来访问这个类:
框架获得了 URL /rss/beats/0613/ 并且注意到 URL 中的 slug 部分后面含有更多的信息。它将斜杠("/" )作为分隔符,把剩余的字符串分割开作为参数,调用 Feed 类的 get_object()方法。
在这个例子中,添加的信息是 ['0613'] 。对于 /rss/beats/0613/foo/bar/ 的一个 URL 请求, 这些信息就是 ['0613', 'foo', 'bar'] 。
get_object() 就根据给定的 bits 值来返回区域信息。
在这个例子中,它使用了 Django 的数据库 API 来获取信息。注意到如果给定的参数不合法,
get_object() 会抛出 django.core.exceptions.ObjectDoesNotExist 异常。在
Beat.objects.get() 调用中也没有出现 try /except 代码块。函数在出错时抛出
Beat.DoesNotExist 异常,而 Beat.DoesNotExist 是 ObjectDoesNotExist 异常的一个子类型。 而在 get_object()
System Message: WARNING/2 (<string>, line 798)
Block quote ends without a blank line; unexpected unindent.中抛出 ObjectDoesNotExist 异常又会使得 Django 引发 404 错误。
为产生 <title> ,<link> ,和 <description> 的 feeds,Django 使用 title() , link() ,和 description() 方法。 在上面的例子中,它们都是简单的字符串类型的类属性,而这个例子表明,它们既可以是字符串, 也可以是 方法。对于每一个 title , link 和
description 的组合,Django 使用以下的算法:
试图调用一个函数,并且以 get_object() 返回的对象作为参数传递给 obj 参数。
如果没有成功,则不带参数调用一个方法。
还不成功,则使用类属性。
最后,值得注意的是,这个例子中的 items() 使用 obj 参数。对于 items 的算法就如同上面第一步所描述的那样,首先尝试 items(obj) , 然后是 items() ,最后是 items 类属性
(必须是一个列表)。
Feed 类所有方法和属性的完整文档,请参考官方的 Django 文档
(http://www.djangoproject.com/documentation/0.96/syndication_feeds/) 。
指定 Feed 的类型
默认情况下, 聚合框架生成 RSS 2.0. 要改变这样的情况, 在 Feed 类中添加一个
feed_type 属性.
from django.utils.feedgenerator import Atom1Feed
class MyFeed(Feed): feed_type = Atom1Feed
注意你把 feed_type 赋值成一个类对象,而不是类实例。目前合法的 Feed 类型如表 11-1 所示。
表 11-1. Feed 类型
Feed 类
类型
django.utils.feedgenerator.Rss201rev2Feed
RSS 2.01 (default)
django.utils.feedgenerator.RssUserland091Feed
RSS 0.91
django.utils.feedgenerator.Atom1Feed
Atom 1.0
闭包
为了指定闭包(例如,与 feed 项比方说 MP3 feeds 相关联的媒体资源信息),使用 item_enclosure_url , item_enclosure_length , 以及 item_enclosure_mime_type ,比如
from myproject.models import Song class MyFeedWithEnclosures(Feed):
title = "Example feed with enclosures"
link = "/feeds/example-with-enclosures/"
def items(self):
return Song.objects.all()[:30]
def item_enclosure_url(self, item): return item.song_url
def item_enclosure_length(self, item): return item.song_length
item_enclosure_mime_type = "audio/mpeg"
当然,你首先要创建一个包含有 song_url 和 song_length (比如按照字节计算的长度)域的 Song 对象。
语言
聚合框架自动创建的 Feed 包含适当的 <language> 标签(RSS 2.0) 或 xml:lang 属性(Atom).他直接来自于您的 LANGUAGE_CODE 设置.
URLs
link 方法/属性可以以绝对 URL 的形式(例如, "/blog/" )或者指定协议和域名的 URL 的形式返回(例如 "http://www.example.com/blog/" )。如果 link 没有返回域名,聚合框架会根据 SITE_ID 设置,自动的插入当前站点的域信息。
Atom feeds 需要 <link rel="self"> 指明 feeds 现在的位置。聚合框架根据 SITE_ID 的设置,使用站点的域名自动完成这些功能。
同时发布 Atom and RSS
一些开发人员想 同时 支持 Atom 和 RSS。这在 Django 中很容易实现:只需创建一个你的 feed类的子类,然后修改 feed_type ,并且更新 URLconf 内容。下面是一个完整的例子:
from django.contrib.syndication.feeds import Feed from chicagocrime.models import NewsItem
from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed):
title = "Chicagocrime.org site news" link = "/sitenews/"
description = "Updates on changes and additions to chicagocrime.org."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
class AtomSiteNewsFeed(RssSiteNewsFeed): feed_type = Atom1Feed
这是与之相对应那个的 URLconf:
from django.conf.urls.defaults import *
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
feeds = {
'rss': RssSiteNewsFeed, 'atom': AtomSiteNewsFeed,
}
urlpatterns = patterns('',
# ...
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': feeds}),
# ...
)
Sitemap 框架
sitemap 是你服务器上的一个 XML 文件,它告诉搜索引擎你的页面的更新频率和某些页面相对于其它页面的重要性。这个信息会帮助搜索引擎索引你的网站。
例如,这是 Django 网站(http://www.djangoproject.com/sitemap.xml)sitemap 的一部分:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.djangoproject.com/documentation/</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>http://www.djangoproject.com/documentation/0_90/</loc>
<changefreq>never</changefreq>
<priority>0.1</priority>
</url>
...
</urlset>
需要了解更多有关 sitemaps 的信息, 请参见 http://www.sitemaps.org/.
Django sitemap 框架允许你用 Python 代码来表述这些信息,从而自动创建这个 XML 文件。要创建一个 sitemap,你只需要写一个 Sitemap 类然后配置你的 URLconf 指向它。
安装
要安装 sitemap 应用程序, 按下面的步骤进行:
将 'django.contrib.sitemaps' 添加到您的 INSTALLED_APPS 设置中.
确保 'django.template.loaders.app_directories.load_template_source' 在您的
TEMPLATE_LOADERS 设置中。默认情况下它在那里, 所以, 如果你已经改变了那个设置的话, 只需要改回来即可。
确定您已经安装了 sites 框架 (参见第 14 章).
备注
sitemap 应用程序没有安装任何数据库表. 它需要加入到 INSTALLED_APPS 中的唯一原因是:这样 load_template_source 模板加载器可以找到默认的模板.
初始化
要在您的 Django 站点中激活 sitemap 生成, 请在您的 URLconf 中添加这一行:
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
这一行告诉 Django, 当客户访问 /sitemap.xml 的时候, 构建一个 sitemap.
sitemap 文件的名字无关紧要,但是它在服务器上的位置却很重要。搜索引擎只索引你的
sitemap 中当前 URL 级别及其以下级别的链接。用一个实例来说,如果 sitemap.xml 位于你的根目录,那么它将引用任何的 URL。然而,如果你的 sitemap 位于 /content/sitemap.xml ,那么它只引用以 /content/ 打头的 URL。
sitemap 视图需要一个额外的必须的参数: {'sitemaps': sitemaps} 。 sitemaps 应该是一个字典,它把一个短的块标签(例如, blog 或 news )映射到它的 Sitemap 类(例如,
BlogSitemap 或 NewsSitemap )。它也可以映射到一个 Sitemap 类的实例(例如, BlogSitemap(some_var) )。
Sitemap 类
Sitemap 类展示了一个进入地图站点简单的 Python 类片断.例如,一个 Sitemap 类能展现所有日志入口,而另外一个能够调度所有的日历事件。
在最简单的例子中,所有部分可以全部包含在一个 sitemap.xml 中,也可以使用框架来产生一个站点地图,为每一个独立的部分产生一个单独的站点文件。
Sitemap 类必须是 django.contrib.sitemaps.Sitemap 的子类. 他们可以存在于您的代码树的任何地方。
例如假设你有一个 blog 系统,有一个 Entry 的 model,并且你希望你的站点地图包含所有连到你的 blog 入口的超链接。你的 Sitemap 类很可能是这样的:
from django.contrib.sitemaps import Sitemap from mysite.blog.models import Entry
class BlogSitemap(Sitemap): changefreq = "never" priority = 0.5
def items(self):
return Entry.objects.filter(is_draft=False)
def lastmod(self, obj): return obj.pub_date
声明一个 Sitemap 和声明一个 Feed 看起来很类似;这都是预先设计好的。
如同 Feed 类一样,Sitemap 成员也既可以是方法,也可以是属性。想要知道更详细的内容,请参见上文 《一个复杂的例子》章节。
一个 Sitemap 类可以定义如下 方法/属性:
items (必需 ):提供对象列表。框架并不关心对象的 类型 ;唯一关心的是这些对象会传递给 location() , lastmod() , changefreq() ,和 priority() 方法。
location (可选):给定对象的绝对 URL。绝对 URL 不包含协议名称和域名。下面是一些例子:
好的: '/foo/bar/'
差的: 'example.com/foo/bar/'
差的: 'http://example.com/foo/bar/'
如果没有提供 location , 框架将会在每个 items() 返回的对象上调用
get_absolute_url() 方法.
lastmod (可选): 对象的最后修改日期, 作为一个 Python datetime 对象. changefreq (可选):对象变更的频率。可选的值如下(详见 Sitemaps 文档):
'always'
'hourly'
'daily'
'weekly'
'monthly'
'yearly'
'never'
priority (可选):取值范围在 0.0 and 1.0 之间,用来表明优先级。默认值为 0.5 ;请详见 http://sitemaps.org 文档。
快捷方式
sitemap 框架提供了一些常用的类。在下一部分中会看到。 FlatPageSitemap
django.contrib.sitemaps.FlatPageSitemap 类涉及到站点中所有的 flat page,并在
sitemap 中建立一个入口。但仅仅只包含 location 属性,不支持 lastmod , changefreq ,或者 priority 。
参见第 16 章获取有关 flat page 的更多的内容. GenericSitemap
GenericSitemap 与所有的通用视图一同工作(详见第 9 章)。
你可以如下使用它,创建一个实例,并通过 info_dict 传递给通用视图。唯一的要求是字典包含 queryset 这一项。也可以用 date_field 来指明从 queryset 中取回的对象的日期域。这会被用作站点地图中的 lastmod 属性。你也可以向 GenericSitemap 的构造函数传递
priority 和 changefreq 来指定所有 URL 的相应属性。
下面是一个使用 FlatPageSitemap and GenericSiteMap (包括前面所假定的 Entry 对象)的 URLconf:
from django.conf.urls.defaults import *
from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap from mysite.blog.models import Entry
info_dict = {
'queryset': Entry.objects.all(), 'date_field': 'pub_date',
}
sitemaps = {
'flatpages': FlatPageSitemap,
'blog': GenericSitemap(info_dict, priority=0.6),
}
urlpatterns = patterns('',
# some generic view using info_dict
# ...
# the sitemap (r'^sitemap.xml$',
'django.contrib.sitemaps.views.sitemap',
{'sitemaps': sitemaps})
)
创建一个 Sitemap 索引
sitemap 框架同样可以根据 sitemaps 字典中定义的单独的 sitemap 文件来建立索引。用法区别如下:
您在您的 URLconf 中使用了两个视图: django.contrib.sitemaps.views.index 和 django.contrib.sitemaps.views.sitemap .
django.contrib.sitemaps.views.sitemap 视图需要带一个 section 关键字参数.这里是前面的例子的相关的 URLconf 行看起来的样子:
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.index',
{'sitemaps': sitemaps}),
(r'^sitemap-(?P<section>.+).xml$', 'django.contrib.sitemaps.views.sitemap',
{'sitemaps': sitemaps})
这将自动生成一个 sitemap.xml 文件, 它同时引用 sitemap-flatpages.xml 和 sitemap-blog.xml . Sitemap 类和 sitemaps 目录根本没有更改.
通知 Google
当你的 sitemap 变化的时候,你会想通知 Google,以便让它知道对你的站点进行重新索引。框架就提供了这样的一个函数: django.contrib.sitemaps.ping_google() 。
备注
在本书写成的时候, 只有 Google 可以响应 sitemap 更新通知。然而,Yahoo 和 MSN 可能很快也会支持这些通知。
到那个时候,把“ping_google()”这个名字改成 “ping_search_engines()”会比较好。所以还是到 http://www.djangoproject.com /documentation/0.96/sitemaps/ 去检查一下最新的站点地图文档。
ping_google() 有一个可选的参数 sitemap_url ,它应该是你的站点地图的 URL 绝对地址
(例如:/sitemap.xml )。如果不提供该参数,ping_google() 将尝试通过反查你的URLconf来找到你的站点地图。
如果不能够确定你的 sitemap URL, ping_google() 会引发
django.contrib.sitemaps.SitemapNotFound 异常。
我们可以通过模型中的 save() 方法来调用 ping_google() : from django.contrib.sitemaps import ping_google
class Entry(models.Model):
# ...
def save(self):
super(Entry, self).save() try:
ping_google() except Exception:
# Bare 'except' because we could get a variety
# of HTTP-related exceptions. pass
一个更有效的解决方案是用 cron 脚本或任务调度表来调用 ping_google() ,该方法使用
Http 直接请求 Google 服务器,从而减少每次调用 save() 时占用的网络带宽。
接下来?
下面, 我们要继续深入挖掘所有的 Django 给你的很好的内置工具。 在第 12 章,您将看到提供用户自定义站点所需要的所有工具: sessions, users 和 authentication.
继续前行!