The Django Book

第十一章 输出非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.writercsv.writer 函数希望获得一个文件类的对象, HttpResponse 正好能达成这个目的。

  • 调用 writer.writerow ,并且传递给它一个类似 list 或者 tuple 的可迭代对象,就可以在 CSV 文件中写入一行。

  • CSV 模块考虑到了引用的问题,所以您不用担心逸出字符串中引号和逗号。只要把信息传递给 writerow() ,它会处理好所有的事情。

在任何需要返回非 HTML 内容的时候,都需要经过以下几步:创建一个 HttpResponse 响应对象(需要指定特殊的 MIME 类型)。将它作为参数传给一个需要文件的方法,然后返回这个响应。

下面是一些其它的例子

生成 PDF 文件

便携文件格式 (PDF) 是由 Adobe 开发的格式,主要用于呈现可打印的文档,包含有 pixel-perfect 格式,嵌入字体以及2D矢量图像。PDF 文件可以被认为是一份打印文档的数字等价物;实际上,PDF 文件通常用于需要将文档交付给其他人去打印的场合。

便携文件格式 (PDF) 是由 Adobe 开发的格式,主要用于呈现可打印的文档,包含有 pixel-perfect 格式,嵌入字体以及2D矢量图像。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 有许多出色并且强大的图表库用以绘制图表,按需地图,表格等。我们不可能将它们全部列出,所以下面列出的是个中的翘楚。

总之,所有可以写文件的库都可以与 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.htmlfeeds/latest_description.html ,后者是URLConf里为对应feed指定的 slug 。注意 .html 后缀是必须的。

RSS系统模板渲染每一个条目,需要给传递2个参数给模板上下文变量:

  • obj : 当前对象 ( 返回到 items() 任意对象之一 )。

  • site : 一个表示当前站点的 django.models.core.sites.Site 对象。 这对于 {{ site.domain }} 或者 {{ site.name }} 很有用。

如果你在创建模板的时候,没有指明标题或者描述信息,框架会默认使用 "{{ obj }}" ,对象的字符串表示。

你也可以通过修改 Feed 类中的两个属性 title_templatedescription_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" (区域)。聚合框架看到了后面的不同之处 06131424 ,它会提供给你一个钩子函数来描述这些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.DoesNotExistObjectDoesNotExist 异常的一个子类型。 而在 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() 方法。 在上面的例子中,它们都是简单的字符串类型的类属性,而这个例子表明,它们既可以是字符串, 也可以是 方法。对于每一个 titlelinkdescription 的组合,Django使用以下的算法:

  1. 试图调用一个函数,并且以 get_object() 返回的对象作为参数传递给 obj 参数。

  1. 如果没有成功,则不带参数调用一个方法。

  1. 还不成功,则使用类属性。

最后,值得注意的是,这个例子中的 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_urlitem_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_urlsong_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 应用程序, 按下面的步骤进行:

  1. 'django.contrib.sitemaps' 添加到您的 INSTALLED_APPS 设置中.

  1. 确保 'django.template.loaders.app_directories.load_template_source' 在您的 TEMPLATE_LOADERS 设置中。默认情况下它在那里, 所以, 如果你已经改变了那个设置的话, 只需要改回来即可。

  1. 确定您已经安装了 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 应该是一个字典,它把一个短的块标签(例如, blognews )映射到它的 Sitemap 类(例如, BlogSitemapNewsSitemap )。它也可以映射到一个 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 属性,不支持 lastmodchangefreq ,或者 priority

参见第16章获取有关flat page的更多的内容.

GenericSitemap

GenericSitemap 与所有的通用视图一同工作(详见第9章)。

你可以如下使用它,创建一个实例,并通过 info_dict 传递给通用视图。唯一的要求是字典包含 queryset 这一项。也可以用 date_field 来指明从 queryset 中取回的对象的日期域。这会被用作站点地图中的 lastmod 属性。你也可以向 GenericSitemap 的构造函数传递 prioritychangefreq 来指定所有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.indexdjango.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.xmlsitemap-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.

继续前行!

Copyright 2006 Adrian Holovaty and Jacob Kaplan-Moss.
This work is licensed under the GNU Free Document License.
Hosting graciously provided by media temple
Chinese translate hosting by py3k.cn.