Wednesday, March 18, 2009

高级查询和浏览功能

给应用程序提供更多的方式来浏览数据无疑会吸引更多的用户。不同的用户可以通过不同的方式获得他们想要的内容。一些用户喜欢通过浏览分类目录来获得他们想要的信息,而其他用户可能希望搜索特定的主题。有些用户甚至希望只要有新的信息出现就立即收到这些信息。

如今内容推送技术越来越流行,很多用户都在利用这种技术来接收信息。本章我们就学习如何使用Django提供的feed框架给我们的应用程序增加内 容推送功能。我们将为用户提供内容订阅服务,来接收信息。接下来我们还要改进应用程序的查询功能,让查询的结果更准确,这期间还将学到更多Django数 据库API的知识。最后我们还将改进书签列表显示的方式,我们会将列表分多页显示。你将发现本章讲解了很多有意思的技术,在本章你将学到以下内容:

  • 增加RSS订阅功能。
  • 高级搜索功能。
  • 增加列表分页功能。

增加RSS订阅功能

如今很多流行的站点诸如:博客、Wiki 和SNS等等,如果用户使用这些服务就必须时常浏览这些站点以便查看最新的更新内容。如果你必须跟踪很多站点的内容更新,这种方式效率很差。幸运的是一种 称为网络推送服务的技术可以让用户及时的获得最新的更新内容。从概念上讲网络推送技术包含以下几个方面:

  • 一个包含站点最近更新内容的XML文档,这个文档称为 web feed
  • 通过称为feed reader或者aggregater的程序用户可以利用这个文档来订阅最新的更新。
  • 这个程序会将站点最新的更新推送给订阅它的用户。

网络订阅技术成为了一种方便而高效的获取最新更新的技术,因此它迅速在网站和用户之中流行了起来。如今提供内容订阅功能已经称为了Web2.0应用 的标准功能。有很多类型的订阅,比如针对最近更新的订阅、针对最首欢迎内容的订阅和针对某些特定主题的订阅等等。而且内容订阅程序也集成在了许多浏览器和 邮件客户端中。

由此可见,给我们的项目中增加订阅功能有很多好处。比如我们给特定用户提供最新的书签信息或者推送最新的书签评论信息,又或者我们可以给用户提供某 个标签下的书签信息。还可以实现很多功能,并且在Django中增加订阅功能很简单。不管我们准备增加多少或者多少种订阅方法都是一样的。

Django提供了功能强大的框架来创建订阅功能,要创建一个订阅之只需要实现一个Python类就可以了,剩下的工作交给Django就可以了。 在本节你将通过创建两种类型的订阅来学习Django的订阅框架,一个用于订阅某个特定用户的书签,另一用于订阅最近增加到站点的书签。本节结束后你就可 以通过Django的订阅框架创建任何你想要的订阅功能,让我们开始吧。

订阅最近发布的书签

我们创建的第一个订阅将显示最近发布的十个书签列表。我们在前面的章节中已经介绍了如何获得最近发布的十条书签的功能:

Bookmark.objects.order_by('-id')[:10]

上面的方法通过对书签的ID进行逆向排序后取出前面的十条的方式来获得结果列表,我们会看到在订阅功能中实现这个功能只需要几行代码。

我们要作的第一步就是定义一个类并让它继承Feed类,这个Feed类是订阅框架的一部分,它位于 django.contrib.syndication包中。为了更好的组织代码,让我们来创建于一个新的文件来包含这个类,在bookmarks文件夹 下创建一个名为feeds.py文件并加入下面的代码:

from django.contrib.syndication.feeds import Feed
from bookmarks.models import Bookmark
class RecentBookmarks(Feed):
title = 'Django Bookmarks | Recent Bookmarks'
link = '/feeds/recent/'
description = 'Recent bookmarks posted to Django Bookmarks'
def items(self):
return Bookmark.objects.order_by('-id')[:10]

让我们解释这些代码的含义:

  • 首先我们导入了Feed类,这是基类。我们还导入了Bookmark模型类,因为我们要查找最近的十个书签。
  • 然后我们定义一个名为RecentBookmarks类,让它继承Feed类。
  • 我们定义了三个属性:订阅的标题、订阅的链接和订阅的简短描述。
  • 最后我们定义了一个items方法,这个方法返回订阅的内容列表。由于我们准备订阅最近发布的十个书签,所以我们在这里重用了以前的代码。

有几种不同的订阅格式,不过最通常的是RSS,所以Django把这种作为了缺省的格式,一个RSS包含两个部分:

  • 第一个部分通过标题、链接、描述文本以及其他属性定义了订阅本身的信息。这些属性可以通过在类中定义属性字段实现。
  • 还有包含订阅条目的列表,每个订阅条目都包含一个标题、链接、描述和其他几个可能的属性字段,后面我们将逐渐讲解这些内容。

上面的items方法返回一个包含Bookmark对象的列表,那么Django是如何将将Bookmark对象映射到上面提到的订阅条目上 呢?Django利用Python对象的__str__方法来取得订阅条目的标题和描述信息,Django利用get_absolute_url方法来定 义如何显示条目的链接。Django可以让我们轻松的自定义这些缺省行为。

我们首先从定义条目的链接开始,打开bookmarks/models.py文件在Bookmark加入下面黑体字部分的代码:

class Bookmark(models.Model):
title = models.CharField(maxlength=200)
user = models.ForeignKey(User)
link = models.ForeignKey(Link)
def __str__(self):
return '%s, %s' % (self.user.username, self.link.url)
def get_absolute_url(self):
return self.link.url
class Admin:
list_display = ('title', 'link', 'user')
list_filter = ('user', )
ordering = ('title', )
search_fields = ('title', )

这个新增的方法非常简单,它简单的返回了书签的链接。现在我们让Django利用对象的字符串表达式作为订阅条目的标题,回头我们再来自定义它。

创建订阅的最后一个步骤是给它定义URL入口。由于我们这里使用了Django提供的订阅框架来为我们的应用程序增加订阅功能,所以给它定义URL 的步骤也比较特殊。我们通过django.contrib.syndication来映射订阅条目的URL,我们通过一个字典对象把订阅条目信息作为参数 传给这个包。打开urls.py 加入下面高亮部分的代码:

import os.path
from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from bookmarks.views import *
from bookmarks.feeds import *
site_media = os.path.join(os.path.dirname(__file__), 'site_media')
# Make sure you add the feeds dict before the urlpatterns object.
feeds = {
'recent': RecentBookmarks
}
urlpatterns = patterns('',
# Feeds
(r'^feeds/(?P.)/$', 'django.contrib.syndication.views.feed',*
{'feed_dict': feeds}),
)

上面的代码中我们首先导入了feeds模块,并且创建了一个名为feeds的字典对象。这个字典将每一个订阅类对象映射为一个固定的URL,之后我们我们将所有的订阅链接映射到feeds/ 路径下,我们用包含订阅条目的字典对象作为第三个参数。

这看起来有点复杂,不过一旦我们知道他的工作原来就会发现其实他很间的:

  • 我们把视图的URL映射到feeds/下,并且把包含订阅条目的字典对象作为参数传给这个视图。
  • 当请求一个^feeds/recent/$ 链接时,django.contrib.syndication.views.feed视图就会被请求,这时这个视图会搜索feeds后面的字符串(这里 是recent),然后根据这个字符串从feeds字典中查找对应的订阅条目对象。
  • 然后这个试图会输出一个XML格式的订阅文件给用户。

接下来试试我们新增的订阅功能,运行开发服务器并打开浏览器输入下面的地址http://127.0.0.1:8000/feeds/recent/。结果是什么样取决于你使用的浏览器,如果你使用的是Firefox浏览器你看到的是类似下面的页面:

程序运行的很好,这里的使用了书签对象的字符串描述来显示订阅条目的标题和描述信息,这个信息对于调试来说很有用,但是对于使用的用户并不友好,所以我们要自定义这些信息。

自定义订阅条目字段

我们可以通过模板来自定义订阅条目。有很多种方式自定义订阅条目的方式,你可以自定义条目的标题、给他增加描述信息或者显示条目的作者等等。feed视图会在模板目录下查找一个名为feeds的目录,并在这个目录下查找相应的模板文件,模板文件的命名规则依据下面的格式:

feedname_fieldname.html

feedname是订阅条目的名称,这个名称与前面我们定义的feeds字典对象中保存的订阅条目对象的键的名称相同。在我们的 RecentBookmarks订阅对象中,他的名称是recent。如果你要自定义订阅条目的名称就需要修改fieldname。要修改订阅条目的标 题,首先需要在模板路径下创建一个名为feeds的文件夹,并在这个目录下创建一个名为recent_title.html的文件,在这个文件中加入下面 的代码:

{{ obj.title }}

feed视图将Bookmark对象以obj为名称传给模板,所以这里我们用obj对象来输出标签对象的标题。注意,我们这里没有像通常模板中那样 对标题字段进行字符转义处理,那是因为Django的feed框架已经自动处理了。至于描述信息我们现在还不需要添加,所以我们在 templates/feeds/目录下创建一个空的recent_description.html文件。

这两个文件创建之后,我刷新一下浏览器看看有什么变化:

现在页面看起来好多了,你可以自定义很多更多的信息,比如,你可以在订阅条目中增加书签的用户信息、标签信息等。

本节我们实现了第一个书签。接下来我们将指定创建一个针对某个特定用户的订阅,这里我们将介绍书签的高级功能,这里将使用用户名称作为订阅URL的参数。

创建用户书签订阅

除了查看站点上最新发布的书签之外,用户可能还想查看某个特定用户的书签,比如你可能想关注你某个朋友的书签。因此,创建一个针对用户的订阅非常有用,这个订阅会列出某个用户的书签。

实现这个订阅比前面的要稍显复杂,因为在这里我们要将用户名称作为参数传给订阅URL,视图函数会根据参数显示特定用户的书签信息。

显然,为每个用户写一个单独的书签是不现实的。如果能够通过分析订阅的URL来输出订阅内容就好了,幸运的是Django提供了一种优雅的机制来实现这一功能。

这种机制的工作原理如下:如果请求的URL中包含了feeds字典中没有定义的额外信息,Django就会认为所请求的订阅与特定的对象有关(在我 们的例子中是User对象)。Django通过这个额外的信息(在我们的例子中是用户名称)利用订阅对象的get_object方法来获得这个特定对象。 接下来在显示订阅信息时Django会将这个对象传递给它,订阅就根据这个对象来显示订阅条目。

下面我们就通过一个实际的例子来学习如何创建一个用户对象,打开bookmarks/feeds.py文件并加入下面的代码:

from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
class UserBookmarks(Feed):
def get_object(self, bits):
if len(bits) != 1:
raise ObjectDoesNotExist
return User.objects.get(username=bits[0])
def title(self, user):
return 'Django Bookmarks | Bookmarks for %s' % user.username
def link(self, user):
return '/feeds/user/%s/' % user.username
def description(self, user):
return 'Recent bookmarks posted by %s' % user.username
def items(self, user):
return user.bookmark_set.order_by('-id')[:10]

让我们分别看看这些方法的含义是什么:

  • get_object:如果订阅URL中包含额外的信息,Django就会调用这个方法。这些额外的信息是以bits参数的方式传递,这个 bits是一个字符串数组,他们之间以/符号分隔。比如我们将一个订阅映射为^feeds/user/$,并且请求的连接是^feeds/user /param1/param2/$,那么bits参数将是['param1','param2']。
  • get_obect:这个方法返回与订阅相关的对象。现在这个订阅需要bits参数中的一个元素(这个元素就是用户名称),并且根据用户名称返回User对象。如果这个bits对象是空的,或者根据这个用户名称找不到一个特定的用户,我们就抛出404对象未找到的异常。
  • title,link,description:在我们前面的订阅对象中这些内容是作为对象的属性存在的,现在他们变成了方法。我们这样作是为了 根据用户的名称显示这些信息。Django可以识别出订阅对象中使用的是属性字段还是方法,如果是方法Django就会将关联对象作为参数传给这个方法。
  • items:这个方法会接收订阅相关的User对象,并根据这个对象来输出书签列表。

完成这个订阅类之后,我们应该将他添加到feeds字典对象中,所以打开urls.py文件并加入下面黑体字部分:

import os.path
from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from bookmarks.views import *
from bookmarks.feeds import *
site_media = os.path.join(
os.path.dirname(__file__),
'site_media'
)
feeds = {
'recent': RecentBookmarks,
'user': UserBookmarks
}

接下来我们为这个订阅创建模板,在templates/feeds/目录下创建一个user_title.html文件,并加入下面的代码:

{{ obj.title }}

最后我们在这个目录下创建一个空的user_description.html文件。

现在我们新的订阅已经创建好了,打开http://127.0.0.1:8000/feeds/user/your_username/(将your_username替换为实际的用户名称),观察一下有什么变化:

No comments: