Monday, April 27, 2009

A completely example for how to read xml in Python

from xml.dom.minidom import parse

dom1=parse('c:/domain.xml')
obj=lambda x:x.localName=='OBJ'
nodelist1=dom1.getElementsByTagName('XPD:OBJ')
entity=''
fname=''
ftype=''
fdesc=''
for node in nodelist1:
if node.getAttribute('type')=='UMLClass':
entity=node.childNodes[1].firstChild.nodeValue
print "Entity name is: %s </p>" % entity

print "<TABLE>"
fields=[x for x in node.childNodes if obj(x)]
for objfield in fields:
for subfield in objfield.childNodes:
if subfield.nodeType==1:
if subfield.getAttribute('name')=="Name":
fname=subfield.firstChild.nodeValue
if subfield.getAttribute('name')=="TypeExpression":
ftype=subfield.firstChild.nodeValue
if subfield.getAttribute('name')=="InitialValue":
fdesc=subfield.firstChild.nodeValue
print """
<TR>
<TD>%s</TD>
<TD>%s</TD>
<TD>%s</TD>
</TR>

"""%(fname,ftype,fdesc)
print "</TABLE>"

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替换为实际的用户名称),观察一下有什么变化:

创建管理接口

章节介绍

接下来我们需要一个管理接口来维护用户输入的评论内容。实际上管理接口对于任何需要管理和存储数据的web应用程序来说是一个通用的功能。因此 Django提供了一个功能完备的管理接口模块,Django中的这个模块可以说非常棒,因为它简单易用而且功能强大、而且扩展性很强。

本章你将学到一下内容:

  • 激活管理接口。
  • 使用这个管理接口来维护数据内容。
  • 自定义管理接口。
  • 给用户或者用户组分别权限。

激活管理接口

管理接口在Django中是一个应用程序模块,要使用它必须首先在我们的应用程序中将之激活,步骤和我们前面讲的激活用户身份验证程序的步骤类似。

管理接口应用程序位于django.contrib.admin包中,所以第一步是把这个包的路径加入到INSTALLED_APPS变量中,打开settings.py文件找到INSTALLED_APPS变量修改如下:

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.comments',
'django_bookmarks.bookmarks',
)

接着执行下面的命令在数据库中创建必要的数据表:

$ python manage.py syncdb

现在为了能够访问这个新增的模块我们需要给它定义一个URL入口。在Django提供的管理接口应用程序中有大量的试图函数,所以如果我们分别给这 些试图函数定义URL的话,那将是一个非常繁琐的工作。因此Django为此提供了一中快捷方式。Django把这个模块中所有的URL映射放在了一个单 独的包中:django.contrib.admin.urls,所以Django提供了一个include()函数,这样我们就可以把所有这些URL映 射“包含”到我们当前的应用程序中,打开urls.py文件加入下面的内容:

urlpatterns = (
# Admin interface
(r'^admin/', include('django.contrib.admin.urls')),
)

这和我们通常定义URL映射的方式略有不同,我们告诉Django从django.contrib.admin.urls这个包中获取URL,而且这些URL都位于 ^admin/路径下。这样我们就可以在自己的应用程序中使用管理接口了。

接下来要作的事情就是告诉Django哪些数据模块可以在管理接口中进行维护。要做到这一点就需要在每一个模型类中定义一个内部的名为Admin的类。打开bookmarks/models.py 文件并给Link模型加入下面黑体字部分的代码:

class Link(models.Model):
url = models.URLField(unique=True)
def __str__(self):
return self.url
class Admin:
pass

我们给Link类加上了一个Admin的内部类就是告诉Django这个Link数据模型可以在管理接口中维护。pass关键字是说这个类什么都不做。随后我们会通过这个类来自定义管理界面的行为,现在现让他空着。

使用相同的方法给Bookmark , Tag 和 SharedBookmark 模型加上一个空的Admin内部类。这样他们就可以在管理接口中维护。由于User对象是有Django提供的所以我们不能自己给他加上Admin类,不 过幸好他默认就是包含这个类的。所以他同样可以在管理接口中访问。

接下来让我们启动开发服务器然后在浏览器中输入下面的网址:http://127.0.0.1:8000/admin/。你会看到一个登录页面。还记得我们在创建项目时同时创建了一个超级用户吧,现在就用那个用户登录:


然后你就会看到一个维护数据模型的页面,上面列出了所有你加上了Admin内部类的模型:

用鼠标点击一个模型的连接,你会得到一个保存在数据库中的这个模型的列表。你可以通过这个页面查看、修改或者增加一个新的对象。下图显示了Link数据模型的内容:

Django会根据模型中的字段属性自动产生一个编辑表单。比如对于Link模型,它包含一个唯一的Url文本字段,你可以通过这个表单来查看或者修改一 个Link对象,另外在你提交的死后,表单还会根据模型的验证逻辑进行校验。所以对于Link对象来说如果你要保存一个无效的URL连接,系统会给出一个 错误提示信息,告诉你应该给出一个合法的连接地址。下图显示了保存非法URL连接是的异常页面:

编辑表单会根据模型字段属性显示不同的组件。对于日期字段会显示一个日历组件,对于外键关联字段会显示一个列表组件,等等。下图显示了一个使用日历组件的用户对象表单:

你还将发现管理接口利用模型中__str__方法的返回值来定义模型的显示内容,这对于我们的开发和维护非常有帮助。

你可以试着增加、删除或者修改一些模型对象来检验一下管理接口的功能。你会发现所有改变都可以在管理接口中立即查看。而且管理接口还会跟踪记录你的操作行为并且你可以根据历史操作会滚你的动作。

本节覆盖了使用Django提供的管理接口的大部分内容,这一功能是使用Django进行开发的益处之一,你可以通过一行代码就获得了一个功能丰富的管理接口。

接下来我们将需要到如何自定义管理接口,另外还将了解iiiiDjango提供的权限管理系统中的特性。

自定义管理接口

Django 提供的管理接口功能非常强大而且扩展性强。你只要花几分钟时间就可以得到一个功能完善的管理接口。尽管Django默认提供的管理借口已经提供了丰富的功 能,不过Django还是提供了许多方法来自定义和增强管理接口。你可以指定那些模块可以出现在管理接口中,以及模型结果列表显示的方式,你还可以自定义 管理接口。现在就让我开始学习这些个功能吧。

自定义列表页面

就像我们前面讲的那样,我们在模型类中定要了一个空的Admin类,这个类可以用于自定义管理接口。

让我们通过一个例子来学习,如下图所示页面中显示了书签的列表:

如果将书签的标题、URL和书签的所有者分不同的列显示不是更好吗?实现这一功能只需要一行代码。我们修改bookmarks/models.py中的bookmark模型,将其中Admin类中的pass关键字替换为下面代码中黑体字的部分:

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)
class Admin:
list_display = ('title', 'link','user')

现在刷新一下页面看看有什么变化:

现在表格看起来组织的更好了,我们在Admin类中定义了一个list_display元组,这个元组中的元素包含了显示在列表中的字段名。

还有其他一些属性方法可以在Admin类中使用,每一个都是一个元组,其中可以包含一个或者多个元素:

  • list_filter:如果定义了这个属性那么页面上会显示一个側边栏,根据这个元组中定义的字段属性会显示过滤器链接。
  • ordering:这个元组用于定义显示类别中的排序字段有哪些。存在于元组中字段在页面上会有一个排序标志,可以进行升序或者绛序排列。
  • search_fileds:增加元组中定义的字段为搜索字段。

现在就在我们的书签应用程序中应用我们上面提到的这些功能。打开bookmark/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)
class Admin:
list_display = ('title', 'link', 'user')
list_filter = ('user', )
ordering = ('title', )
search_fields = ('title', )

新增部分增加了以下功能:

  • list_filter:提供按照用户过滤书签的功能。
  • ordering:为标题字段提供排序功能。
  • search_fields:允许用户按照书签的标题进行搜索。

现在刷新一下页面看看有什么变化:

就像你看到的那样我们只用了很少的代码就改进了页面的功能,接下来我们将学习如何修改管理接口模块的模板来对管理接口提供更好的控制。

重载管理接口模板

有时你可能想好修改一下管理接口的页面外观或者将模型部分还一下位置,幸运的是Django提供的管理接口非常容易扩展,你可以很容易的重载它来实现你想要的。

自定义管理接口模板的过程很简单。首先你把管理接口的模板复制到你的项目的模板目录下,然后扩展模板来实现你想要的功能。管理接口模板的位置取决于Django安装的位置,下列出了一些主流操作系统上Django的安装目录(x.x代表Python的版本号):

* Windows: C:PythonXXLibsite-packagesdjango

* UNIX and Linux: /usr/lib/pythonX.X/site-packages/django

* Mac OS X: /Library/Python/X.X/site-packages/django

如果在默认的安装路径下找不到,就查找一下django-admin.py的位置,可能你会找到多个,但是其中一个是位于Django安装目录下的bin目录下。

找到Django的安装路径后,打开django/contrib/admin/templates/,你就会找到管理接口用到的模板,这里有很多文件但是最重要的是:

  • admin/base_site.html:这是所有管理接口的根模板,所有页面都继承自这个模板。
  • admin/change_list.html:这是用于显示模型对象列表的页面。
  • admin/change_form.html:这个模板用于显示修改或者增加数据模型的页面。
  • admin/delete_confirmation.html:这个模板用于删除对象是显示的确认页面。

让我们来试着修改一下这些模板,假设我们想要修改管理接口模板上的文字"Django administration"。要完成这一功能,我们需要在templates目录下创建一个admin目录,然后把 admin/base_site.html 文件复制到这里,然后打开它把所有的"Django"替换为"Django Bookmarks":

{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title|escape }} |
{% trans 'Django Bookmarks site admin' %}{% endblock %}
{% block branding %}

{% trans 'Django Bookmarks administration' %}

{% endblock %}
{% block nav-global %}{% endblock %}

刷新一下页面看看有什么变化:

修改过程很简单不是吗?你可以自己试着检验一下,比如你可能希望在列表页面和编辑页面上加上一个帮助信息。管理接口的模板利用了Django的模板技术,如果你在其中看到不熟悉的标签,你可以查看Django的文档。

用户,用户组和授权

到目前为止,我们一直使用在manage.py syncdb时创建的超级用户登录系统。在实际情况下还有其他一些受信用户会访问管理接口,本节我们将学习如何授权其他用户登录管理接口,并讲解Django的权限管理系统。

不过在我们开始之前,我们需要强调一下:只有受信用户可以访问管理接口,管理接口功能非常强大所以一定要确定用户是你可以信任的才能授权他访问管理接口。

用户权限

如果除了超级用户你的系统里还没有其他用户,那么你可以通过我们在第四章中建立的用户注册程序注册一个用户。另外你也可以通过在管理接口中点击 User下的Add User来增加一个用户。接下来返回到用户列表页面点击你刚才创建的那个用户名称。你会看到一个表单页面,在这个页面上你可以编辑用户信息比如用户名和用 户的email等。在权限管理部分(Permissions)你可以看到一个Staff status复选框。打开这个选项就可以让这个用户登录管理接口;但是这个用户出了可以登录管理接口之后什么都不能作因为我们并没有授权给他。

为了给用户赋予足够的权限来编辑数据模型,你可以打开Superuser status选项,这样这个用户就有了与管理员一样的权限。但是通常给一个用户赋予管理员的权限并不可取。因此你可以通过Django提供的权限管理系统 更灵活的给用户授权。在Superuser status复选框的下面你可以看到一个授权列表,仔细看看这个列表你会发现每个数据模型有三个可用权限:

  • 在数据模型中增加对象的权限。
  • 在数据模型中修改对象的权限。
  • 从数据模型中删除对象的权限。

Django会为包含Admin内部类的所有数据模型自动创建这样的权限模块。你可以通过箭头来给当前的用户授予权限。比如我们可以给一个用户授予 对link,tag和bookmark授予增、删和改的权限。现在登出系统并用刚才我们授权的那个用户登录,你会发现你只能操控link,tag和 bookmark模型。

编辑用户页面的授权功能部分有一个Active复选框,如果你取消选择这个复选框,那么这个用户就无法再登录这个站点或者管理接口。

用户组权限

如果你有许多个有相同权限的用户,而你准备给每个用户单独授权,这无意是一件恐怖的事情。因此,Django提供了另一个用户管理功能;用户组。简 单的说,用户组是用来对拥有相同权限的用户进行分组。你可以创建一个组并给这个组授权,那么当你将一个用户加入到这个组中,那么这个用户就拥有了这个组所 拥有的权限。

创建用户组对象很简单,点击管理接口主页上的Groups,然后点击Add Group。接下来给这个组起个名字并授予一定权限,最后保存它。

要将一个用户加到某个组下面,你可以选择编辑一个用户,然后在Groups部分选择你要将这个用户加入的组。

在视图中使用权限

尽管到目前为止我们之在管理接口中使用到了权限系统,Django同样允许我们在编写视图函数时使用权限功能。你可以在编写试图函数时利用权限功能,给某个函数设置一个或者一组权限,比如对于某些似有信息只有特殊授权的用户可以访问。本节我们将学习如何实现这些功能。

如果你要检验一个用户是否有某个权限,你可以利用User对象的has_perm方法。这方法接收一个代表用户权限的字符串参数:

app.operation_model

app代表应用程序的名称;operation可以是add,change和delete;model是模块的名称,比如我们要检验一个用户是否有增加连接的功能,我们可以这样:

user.has_perm('bookmarks.add_tag')

也可以通过下面的方法检验一个用户是否可以修改书签:

user.has_perm('bookmarks.change_bookmark')

此外,Django还提供了称为"装饰器"的技术,通过它可以限制某个试图只有特定用户可以访问。这个装饰器名为permission_required,它位于django.contrib.auth.decorators包中。

使用这个装饰器和我们之前使用login_required来限制某些视图只有特定用户可以访问的方式类似。假设我们想限制 bookmark_save_page视图只有拥有 bookmarks.add_bookmark权限的用户才可以访问,我们可以通过下们的代码实现:

from django.contrib.auth.decorators import permission_required
@permission_required('bookmarks.add_bookmark', login_url="/login/")
def bookmark_save_page(request):
# [...]

这个装饰器有两个参数:第一个是访问这个试图需要具备的权限,另一个是一个URL,这个URL用于没有权限的用户访问这个视图时重定向的位置。

那么到底使用has_perm还是permission_required方法取决于你需要控制的权限粒度。如果你打算对整个视图函数的访问进行控 制你可以使用permission_required;如果你准备对视图函数内部逻辑进行权限控制,你可以使用has_perm方法。这两个方法应该足够 应付任何有关权限控制的问题。

总结

尽管本章相对较短,但是我们还是学到了很多东西。这充分证明了使用Django你可以用很少的代码实现许多强大的功能。本章你学习了Django强大的管理接口功能,以及如何自定义管理接口,以及如何利用Django提供的功能丰富的权限管理系统。

下面是对本章的一个简短总结:

  • 在项目中激活管理接口的方法
    • 在settings.py文件中的INSTALLED_APPS变量增加django.contrib.admin应用程序。
    • 运行manage.py syncdb来将管理接口的数据模型同步到数据库中。
    • 在urls.py文件中定义管理接口的入口URL。
  • 你可以在数据模型的Admin类中增加以下字段来定制管理接口中页面显示方式。

  • 你可以通过调用User对象的has_perm方法来判断用户是否拥有某个权限。

  • 你可以通过django.contrib.auth.decorators包下的permission_required装饰器来显示某些视图只有特定用户可以访问。

在接下来的一章中你将学到当今几乎所有的Web2.0程序中都有的几个功能。诸如RSS,查询和浏览流行的内容等等,所以接着学习吧。

Thursday, March 12, 2009

02 Google App 快速入门之 Hello World!

Google App Engine 利用 CGI 与web服务器进行通信。当服务器从你的应用程序接收到请求时,它会在当前环境中根据请求数据执行应用程序,之后服务器会向客户端的标准输入输出流中写回响应数据,包括http headers和具体的内容。

让我们从开发一个显示简短信息的迷你应用程序开始吧。

创建一个简单的请求处理程序(Request Handler)

创建一个名为 helloworld 的目录。这个应用程序的所有文件都会放在这个目录下。

helloworld 目录下创建一个名为 helloworld.py 的文件,并输入下面的内容:

print 'Content-Type: text/plain' 
print '' 
print 'Hello, world!' 

上面这段Python代码向应用程序的请求返回一段包含了HTTP header信息、一个空行已经一个文本消息的 'Hello, world!'。

创建配置文件

一个App Engine包含一个名为 app.yaml 的配置文件,其中描述了哪个脚本文件被用于处理URLs。

我们需要在helloworld目录下创建这个文件并加入以下内容:

application: helloworld 
version: 1 
runtime: python 
api_version: 1  
handlers: 
   - url: /.*   
     script: helloworld.py 

这段代码描述了以下信息;

  • 应用程序的名词为 helloworld ,在你最终部署这个应用程序的时候,你必须指定一个唯一的名称。在开发阶段这个值可以是任意内容。现在我们就将他简单的设置为 helloworld
  • 我们通过version变量来设置当前应用程序的版本号为1。当你更新了应用程序的版本后,App Engine可以记住以前的版本号,这样你就有机会回滚到以前的版本。
  • runtime变量告诉App Engine现在用的运行时环境是Python,当前的App Engine版本是1。以后App Engine会支持其他语言。
  • 对于最后一行表示每一个URL请求,如果其正则表达式可以匹配到/.*(这里表示任何连接)上时,就通过 helloworld.py 脚本处理。

测试应用程序

我们在上面一节中已经编写了一个可以处理任何连接的脚本,并在配置文件中进行了设置。现在你可以通过App Engine SDK 中自带的web服务器来测试一下。

你可以通过下面的命令启动并测试刚才的代码,我们需要将helloworld的路径作为参数传递给这个命令。

google_appengine/dev_appserver.py helloworld/ 

现在web服务器应该已经运行起来了,监听端口是8080。现在输入下面的地址:

http://localhost:8080/

如果想获得有关开发环境中web服务器更多的信息,比如修改缺省的8080端口,可以查看 the Dev Web Server reference 或者在刚才的命令后加上--help参数查看。

迭代开发

你可以在编写调试代码时让web服务器一直开着。服务器会监听你的源代码目录,一旦有改变它就会根据需要重新加载代码。你现在就可以试一下:让 web服务器一直开着,然后打开helloworld.py文件把其中的Hello World!消息改成其它内容。这时刷新依稀http://localhost:8080/ 页面看看有什么变化。如果想关闭web服务器只要在web服务器运行的命令窗口按下Control+C(或者其它‘终止命令’的按键)。在学习这边指南的 过程中你可以让web服务器一直开着,如果想重启它按照上面将的步骤就可以了。

接下来

现在你已经完成了一个完整的App Engine,你完全可以把它部署到实际的网络环境中,让别人看懂你的‘Hello World!’消息。不过,在部署之前我们考虑使用web应用程序框架来方便的给它增加一些新的功能。下一节我们将介绍 使用web应用程序框架

Wednesday, March 11, 2009

01 GoogleApp 快速入门之 安装篇

介绍

欢迎使用Google App Engine!创建一个 Google App Engine非常简单,花几分钟就可以。开发并不复杂:上载你的应用程序,然后别人就可以马上看到你的应用,而且这些都是免费的并且没有任何限制。

在这篇指南中,你将创建一个简单的留言板程序,这样用户就可以把他们的留言发布到公共的留言板上。用户可以匿名留言或者登录他们的Google帐户后留言。

这个留言板程序将演示如何使用App Engine的数据库,如何将App Engine应用程序集成到Google帐户上,以及如何使用一个名为webapp的简单的Python web框架。这个应用还将演示如何使用Django模板引擎。

接下来

在开始开发Goole App Engine应用程序之前,我们需要先下载并安装它的软件开发环境。下一节 配置开发环境 .

配置开发环境

开发和部署Goole App Engine需要使用它的软件集成开发环境(SDK)。SDK包含一个用于模拟Goole App Engine环境的web服务器,一个本机版本的数据库,一个Google帐户,通过App Engine你可以获取URLs并从你的计算机上直接发送电子邮件。SDK可以运行在任何安装Python2.5的计算机上,你可以下载 Windows,Mac OS X和Linux版本的Python。

如果必要请从 Python 站点上下载并安装适合于你当前操作系统平台的Python2.5。Mac OS X 10.5 Leopard 用户的计算机上已经预装了Python2.5。

下载 App Engine SDK , 然后按照说明在你的计算机上安装SDK。

在这篇指南中你会用到SDK中的两个命令:

对于Windows用户来说:Windowsa安装程序将把上面的两个变量添加到环境变量的command path中。安装之后你就可以直接在命令窗口中使用这两个命令了。

对于Mac用户来说:Google App Engine Launcher中包含了这些命令。你可以在“GoogleAppEngineLauncher”菜单中通过“Make Symlinks...”把这些命令放到command path中。你可以通过Launcher来运行开发web服务器和部署你的应用,而不必通过命令行的方式。

如果你下载的是一个压缩包版本的SDK,你可以在 google_appengine 命令下找到这些命令。

接下来

在你把应用程序发布到网络上之前,你可以使用开发环境来开发和测试完整的App Engine 程序。接下来让我们写一些实际的代码。下一节 Hello,World

Thursday, February 05, 2009

投票与评论












介绍


我们应用程序的主要目的是为用户提供方便的方法来查找和共享书签。实现这一功能的方法是允许用户想其他人推荐书签。由于我们的应用程序的主要除了欢迎消息之外什么都没有,我们将通过允许用户在主页上提交最爱书签的方式来实现书签共享的功能。之后我们将允许用户通过应用程序对他们喜欢的书签进行投票,之后我们会创建一个专门的页面显示投票最多的书签。这一系列的功能将提供一个显示所有用户感兴趣的书签的列表。在本章的第二部分,我们将实现允许用户对书签添加评论的功能,用户之间可以据此交换观点并将他们的讨论内容张贴在书签网站上。本章将展示很多有趣的功能,你还将学到一些新的Django提供的功能。


在本章中,你将学到一下内容:


  • 允许用户在主页上共享书签。
  • 允许用户对他们喜欢的书签进行投票。
  • 显示最近共享的和最流行的书签。
  • 允许用户对书签进行评论。


在主页上共享书签


目前为止,我们可以在应用程序中通过浏览标签页面和用户页面来查找书签。让我们为用户提供一个新的方法让用户共享和查找书签。当用户保存书签的时候我们将提供一个选项让用户可以在应用程序主页上共享这个书签。当一个书签被共享之后,如果愿意其他用户可以对这个共享的书签进行投票,我们还将创建一个页面用于显示投票最多的书签。这一功能对我们的应用程序来说非常重要,因为它将改变应用程序主页来显示最新共享的书签,在这里用户可以查找他们感兴趣的书签。


我们将分以下几步来实现这个功能:


  • 我们将创建一个数据模型来保存在主页上显示的书签信息,这个模型将保存被共享书签的部分内容。
  • 我们将修改保存书签的表单,以便让用户可以在主页上共享书签。
  • 我们还将修改应用程序主页,让他可以显示最近被共享的书签。每一个书签标题的旁边都有一个投票按钮。
  • 我们将创建一个视图函数,这个视图函数将接受投票请求并更新投票数。

为了实现这个功能我们需要作大量的工作,但这一切都是值得的,我们在这一过程中将学到很多新的知识,让我们开始吧。



被共享书签的数据模型


如果需要在主页上共享书签,我们需要在数据库中存储以下信息:


  • 书签共享的时间:我们需要利用这个信息来显示一段时间内最流行的书签。
  • 对这个书签的投票数。
  • 给这个书签投票的用户信息。保存这个信息是为了防止一个用户对同一书签多次投票。

为此,我们需要创建一个新的数据模型名为 SharedBookmark。打开bookmarks/models.py文件并加入如下类:



class SharedBookmark(models.Model):


bookmark = models.ForeignKey(Bookmark,unique=True)

date=models.DateTimeField(auto_now_add=True)

votes=models.IntegerField(default=1)

users_voted=models.ManyToManyField(User)

def __str__(self):


return '%s,%s' %
self.bookmark,self.votes

这个模型中使用了我们以前没接触过得功能,现在我来一一解释:


  • bookmark字段对应被共享的书签。因为我们不希望同一个书签被共享多次,所以这里我们把它设置为unique=True。
  • date
    字段的类型为models.DateTimeField,就像名字映射的那样,你可以在这个字段中存储日期/时间格式的内容。参数auto_now_add设置为True是告诉Django在第一次创建一条记录的时候给这个字段赋值为当前系统日期/时间。

  • votes的类型是models.IntegerField,这个字段存储整型数据,default=1是告诉Django在第一次创建一条记录的时候给它赋值为1。

  • users_voted定义了这个类与User对象之间的多对多关系,它会保存给这个书签投票的一个列表。

创建了这个模型类之后,执行下面的命令将数据模型同步到数据库中:


  • $ python manage.py syncdb

这样我们就可以将共享书签数据保存到数据库中,接下来我们实现在主页上共享书签的功能。



修改书签保存表单


我们将通过在书签保存表单页面上增加一个复选框的方式来提供书签共享功能。如果用户选择了这个复选框书签就会被共享到主页上。为了实现这个功能我们首先需要修改显示显示保存表单的类,打开bookmarks/forms.py文件修改BookmarkSaveForm类如下:



class BookmarkSaveForm(forms.Form):


url=forms.URLField(

label='URL',

widget=forms.TextInput(attrs={'size':64})

)

title=forms.CharField(

label='Title',

widget=forms.TextInput({attrs={'size':64}})

)

tags=forms.CharField(

label='Tags',

required=False,

widget=forms.TextInput({attrs={'size':64}})

)

share=forms.BooleanField(

label='Share on the main page',

required=False

)

我们在BookmarkSaveForm中使用了新的字段类型forms.BooleanField,这个字段的值可以是Ture或者False,它在页面中显示为复选框。


接下来我们来修改一下保存书签的方法,让它可以出来新增的复选框的行为,打开bookmarks/views.py文件在_save_bookmark方法中加入以下黑体字部分的代码:



def _bookmark_save(request,form):


# Create or get link

link,dummy=Link.objects.get_or_create(

url=form.cleaned_data['url']

)

# Create or get bookmark

bookmark,created=Bookmark.objects.get_or_create(

user=request.user,

link=link

)

# Update bookmark title

bookmark.title=form.cleaned_data['title']

# If bookmark is being updated,clean old tag list.

if not created:


bookmark.tag_set.clear()

# Create new tag list

tag_names=form.cleaned_data['tags'].split()

for tag_name in tag_names:


tag,dummy=Tag.objects.get_or_create(name=tag_name)

bookmark.tag_set.add(tag)

# Share on the main page if requested

if form.cleaned_data['share']:


shared_bookmark,created=SharedBookmark.objects.get_or_create(bookmark=bookmark)

if created:


shared_bookmark.users_voted.add(request.user)

shared_bookmark.save()

# Save bookmark to database and return it

bookmark.save()

return bookmark

这段代码的原理是这样的,如果“共享”复选框被选中了我们通过get_or_create方法来校验数据库中是否已经存在一个SharedBookmark对象,如果没有就创建一个。如果通过create_or_get方法创建了一个SharedBookmark对象,我们就把当前的用户加入到给这书签投票的用户列表中,然后保存这个SharedBookmark对象。如果这个复选框没有选中或者SharedBookmark对象已经存在,我们什么都不做。


这就是我们为了实现在主页上共享书签要编写的代码。也就是说,当用户在提交书签数据时,如果选择了共享复选框相应的SharedBookmark就会被创建并保存到数据库中,所以下一节我们要实现的功能是,将最近共享的书签显示在主页上并让用户可以对其投票。



浏览并对共享的书签进行投票


现在我们已经有了一个保存共享书签的数据模型。这样我们就可以方便的将最近共享的书签找出来并显示在主页上。首先修改bookmarks/views.py中的main_page函数,我们在这个函数中取出共享的书签并把它作为参数传给主页模板:



def main_page(request):


shared_bookmarks=SharedBookmark.objects.order_by('-date')[:10]

variables=RequestContext(request,{'shared_bookmarks',shared_bookmarks})

return
render_to_response('main_page.html',varibales)

新增的代码简洁明了,我们通过SharedBookmark.objects的order_by方法获得按日期降序排列的共享书签列表(注意其中的-date负号标记),然后通过Python的数组切片功能取出前十条记录。最后我们把得到的共享书签列表放入RequestContext中并把它作为参数传递给main_page.html模板。


接下来我们修改一下主页模板让它显示共享的书签列表。我们通常使用bookmark_list.html模板显示书签列表,不过我们现在要显示的是SharedBookmark对象而不是Bookmark对象。因此我们需要单独创建一个模板用于显示共享的书签列表。这个模板与bookmark_list.html稍有不同,比如页面中每个书签后会显示对这个书签的投票数。在模板文件夹下创建一个名为shared_bookmark_list.html的文件并输入下面的代码:



{% if shared_bookmarks %}

<ul class="bookmarks">


{% for shared_bookmark in shared_bookmarks%}

<li>

<a href="{{shared_bookmark.bookmark.link.url}}"
class="title">


{{shared_bookmark.bookmark.title|escape}}</a>

<br/>

Posted By:

<a href="/user/{{shared_bookmark.bookmark.user.username}}"
class="username">


{{shared_bookmark.bookmark.user.username}}</a>|

<span class="vote-count">Vote:


{{shared_bookmark.votes}}</span>

</li>

{%endfor%}

</ul>

{%else%}

<p>No bookmarks found</p>

{%endif%

这个模板和bookmark_list.html很类似,所以并不难理解。首先校验共享书签列表是否为空,如果不为空就迭代这个列表并在页面上输出每个书签的链接和其它信息,比如谁共享的这个书签,用户对书签的投票数。


创建了shared_bookmark_list.html文件后,我们需要把它包含在主页模板中。打开main_page.html模板并插入黑体字部分的代码:



{% extends "base.html" %}

{% block title %}Welcome to Django Bookmarks{% endblock %}

{% block head %}Welcome to Django Bookmarks{% endblock %}

{% block content %}

{% if user.username %}

<p>Welcome {{ user.username }}!

Here you can store and share bookmarks!</p>

{% else %}

<p>Welcome anonymous user!

You need to <a href="/login/">login</a>

before you can store and share bookmarks.</p>

{% endif %}

<h2>Bookmarks Shared by Users</h2>

{% include 'shared_bookmark_list.html' %}

{% endblock %}

现在启动开发服务器,通过书签表单共享一些书签,然后打开http://127.0.0.1:8000/链接,页面显示应该类似下面图示:

 


images/shared_bookmark.jpg

现在差不多了,主页面上已经可以显示被共享的书签了,但是书签的投票数总是显示为1,那是因为我们还没办法给一个书签投票。下面我们就开始实现这个功能。


现在我们创建一个视图函数来接收对共享书签的投票请求,并记录投票数量。你一定记得前面我们将创建数据模型的时候,Django会自动给数据模型加上一个ID属性。这个属性是一个唯一的整数,它用于唯一标识一个对象。这里我们会利用这个书签来标识投票数。


首先我们需要给投票视图函数加上一个URL入口,打开urls.py文件,并加入下面黑体字部分:


urlpatterns = patterns('', # Account management (r'^save/$',
bookmark_save_page), (r'^vote/$', bookmark_vote_page), )


现在你应该很容易就理解这个URL的含义,我们给链接^vote/$映射一个视图函数名为
bookmark_vote_page,接下来我们来实现这个函数。打开文件bookmarks/views.py并输入下面代码:



@login_required

def bookmark_vote_page(request):


if request.GET.has_key('id'):


try:


id=request.GET['id']

shared_bookmark=SharedBookmark.objects.get(id=id)

user_voted=shared_book.users_voted.filter(username=request.username)

if not user_voted:


shared_bookmark.votes+=1

shared_bookmark.users_voted.add(request.user)

shared_bookmark.save()

except ObjectDoesNotExist:


raise Http404('Bookmark not found')

if request.META.has_key('HTTP_REFERER'):


return
HttpResponseRedreict(request.META['HTTP_REFERER'])

return HttpResponseRedirect('/')

让我们一起来看看代码的执行逻辑:



@login_required

def bookmark_vote_page(request):


@login_required

def bookmark_vote_page(request):

我们通过login_required来修饰这个函数,因为只有登录用户可以对书签进行投票。



if request.GET.has_key('id'):


try:


id=request.GET['id']

shared_bookmark=SharedBookmark.objects.get(id=id)

然后代码会校验请求中是否包含'id'变量,如果有就通过它获得一个SharedBookmark对象



user_voted=shared_book.users_voted.filter(username=request.username)

接下来函数会校验当前用户是否已经给书签投票了,可以通过给shared_bookmark.users_voted的filter方法传递一个username参数获得。如果这个返回结果是空:



if not user_voted:


shared_bookmark.votes+=1

shared_bookmark.users_voted.add(request.user)

shared_bookmark.save()

如果这是用户第一次给这个书签投票我们就给shared_bookmark.votes加一。并在shared_bookmark.users_voted加入当前的用户对象并保存这个shared_bookmark对象。


如果无法通过id找到共享的书签对象我们就抛出一个Http404异常,并输出一个404未找到页面。



if request.META.has_key('HTTP_REFERER'):


    return
HttpResponseRedreict(request.META['HTTP_REFERER'])

return HttpResponseRedirect('/')
 最后,如果所有逻辑都正常执行,我们就将页面重定向到之前的位置。我们可以通过HTTP头中的HTTP_REFERER来实现。当你点击页面上的一个链接的时候你的浏览器会将包含这个链接的当前页面的URL发送给服务器,所以我们可以利用这个特性将用户从投票页面从定向到他最初的页面。这对我们后面将要实现的功能是非常有用的。
HTTP头信息存在于Django的视图 request.META中,因为某些浏览器并不发送 HTTP_REFERER给服务器,所以我们首先要确定它是否存在,如果不存在我们就重定向到主页面上。
现在投票页面已经完成了,我们只需要在主页面上加上它的链接就可以了。下面打开 shared_bookmark_list.html文件并加入下面代码中黑体字部分:
{% if shared_bookmarks %}
  <ul class="bookmarks">
    {% for shared_bookmark in shared_bookmarks %}
      <li>
        <a href="/vote/?id={{ shared_bookmark.id }}"
           class="vote">[+]</a>

        <a href="{{ shared_bookmark.bookmark.link.url }}"
           class="title">
          {{ shared_bookmark.bookmark.title|escape }}</a>
        <br />
        Posted By:
        <a href="/user/{{ shared_bookmark.bookmark.user.username }}/"
           class="username">
          {{ shared_bookmark.bookmark.user.username }}</a> |
        <span class="vote-count">Votes:  
              {{ shared_bookmark.votes }}</span>
      </li>
    {% endfor %}
  </ul>
{% else %}
  <p>No bookmarks found.</p>
{% endif %}
黑体字部分将投票链接加入到主页中并用一个[+]符号表示。这个链接的URL是/vote/?id=再加上书签的id。现在我们完成了书签投票功能,接下来刷新一下你的主页,你应该看到类似下面的页面:
现在如果你点一下旁边的投票链接,投票数可能会增加,当然如果你是这个书签的创建者,投票数是不会增加的。记住,如果你共享了一个书签默认会给这个书签投一票,所以不妨试试注册多个帐号然后分别给这些书签投票测试一下。
目前,主页上显示了最近被共享的十个书签,但是如果我们想根据投票数看看最受欢迎的书签呢,下一节将创建一个页面来满足这个需求。

最受欢迎书签页面

我们可以非常方便的实现类似主页面的最受欢迎书签页面。我们只要把过滤条件从日期改为投票数量就可以了。不过,为了显示最新的最受欢迎书签我们需要显示最近一天被投票最多的书签。
实现这个功能的第一步是创建一个视图。所以请打开bookmarks/views.py文件加入以下方法:
from datetime import datetime, timedelta
def popular_page(request):
  today = datetime.today()
  yesterday = today - timedelta(1)
  shared_bookmarks = SharedBookmark.objects.filter(
    date__gt=yesterday
  )
  shared_bookmarks = shared_bookmarks.order_by(
    '-votes'
  )[:10]
  variables = RequestContext(request, {
    'shared_bookmarks': shared_bookmarks
  })
  return render_to_response('popular_page.html', variables)
这个函数比 main_page函数要稍微复杂一点,需要注意的是代码中获得昨天时间并根据它查找书签列表的部分,让我们逐行分析这段代码:
from datetime import datetime, timedelta
def popular_page(request):
  today = datetime.today()
  yesterday = today - timedelta(1)
首先,从datatime模块中导入datetime和timedalta类,datetime模块是一个标准Python模块,它提供了大量操作时间和日期的对象,其中datetime对象用户维护日期和时间,timedelta对象用于处理两个时间之间的持续时长或者不同点。
在popular_page函数中我们使用datatime.today()方法来获得当天的时间,然后我们通过timedelta对象创建一个长度为1天的对象,再用它与当前时间相减得到昨天的日期:
shared_bookmarks = SharedBookmark.objects.filter(
    date__gt=yesterday
  )
  shared_bookmarks = shared_bookmarks.order_by(
    '-votes'
  )[:10]


在上面的代码中我们通过 SharedBookmark.objects的filter获得昨天到现在所有的贡献书签,我们通过给filter传递一个 date__gt=yesterday参数实现,其中__gt代表“大于”,这样我们就能够获得共享日期大于昨天的所有书签。

接下来我们调用书签列表的order_by方法获得按照投票数量逆序排列的列表。我们使用Python的数组分片技术获得列表的前十个记录:

  variables = RequestContext(request, {
    'shared_bookmarks': shared_bookmarks
  })
  return render_to_response('popular_page.html', variables)
 最后我们把列表对象shared_bookmarks传给模板popular_page.html来显示结果。
现在我们开始创建显示最受欢迎书签的页面模板popular_page.html,创建并打开这个文件输入下面的代码:
{% extends "base.html" %}
{% block title %}Popular Bookmarks{% endblock %}
{% block head %}Popular Bookmarks{% endblock %}
{% block content %}
  {% include 'shared_bookmark_list.html' %}
{% endblock %}
这个模板页面非常简单,它集成自base.html模板并重装其中的标题等信息,然后将hared_bookmark_list.html包含进来。
最后一步我们需要给这个新的视图定义URL,所以请打开 urls.py文件并输入下面黑体字部分:
urlpatterns = patterns('',
  # Browsing
  (r'^$', main_page),
  (r'^popular/$', popular_page),
  (r'^user/(\w+)/$', user_page),
  (r'^tag/([^\s]+)/$', tag_page),
  (r'^tag/$', tag_cloud_page),
  (r'^search/$', search_page),
)
好了现在我们完成了,打开浏览器输入地址 http://127.0.0.1:8000/popular/ 你会看到这个新增的页面,注意这里页面中是按照投票数量来排序的。如果你尝试给一个书签投票,之后你会返回这个页面。
现在我们可以把这个新增的功能加到主页面菜单上了,打开templates/base.html 文件并加入下面黑体字部分的代码:
[...]
  <div id="nav">
    <a href="/">home</a> |
    <a href="/popular/">popular</a> |
    {% if user.is_authenticated %}
      <a href="/save/">submit</a> |
      <a href="/search/">search</a> |
      <a href="/user/{{ user.username }}/">
        {{ user.username }}</a> |
      <a href="/logout/">logout</a>
    {% else %}
      <a href="/login/">login</a> |
      <a href="/register/">register</a>
    {% endif %}
  </div>
[...]
增加这个功能并不是很困难对吗?我们确实花了些时间实现它不过并不麻烦。现在我们站点的用户可以共享并对书签进行投票,而且他们可以浏览最新的最受欢迎的书签。下一节我们将增加更有意思的功能,让用户可以对书签进行评论。

给书签加上评论

给书签投票是用户表达他们对书签的看法的一种方式。如果能让用户直接对一个书签进行评论那就更好了,这是一个很好的方式,用户可以针对书签交流看法并结识新的朋友。这一节我们就实现这个功能。

实现给书签增加评论功能分以下几个步骤:

  • 激活评论应用程序功能并在数据库中创建它的数据模型。
  • 用一系列评论应用程序中提供的模板标签来显示评论表单和已增加的评论内容。
  • 创建增加评论的表达模板和增加评论成功后的显示页面模板。
Django实现这一功能是非常简单的,你一会儿就会看到。

激活评论应用程序

 

Django的评论应用程序位于 django.contrib.comments中,和打开其他Django中的应用程序一样,我们首先需要编辑 settings.py文件,然后把 'django.contrib.comments'加入到INSTALLED_APPS 变量中:
INSTALLED_APPS = (
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.sites',
  'django.contrib.comments',
  'django_bookmarks.bookmarks'
)

接下来执行下面的命令将评论应用程序的数据模型同步到数据库中:
$ python manage.py syncdb
接下来我们需要把评论应用程序的URL入口加入到url.py中,由于Django的评论应用程序包含很多函数,所以一个个的增加这些函数的入口显然是一种重复功能。Django为此提供了一个快捷方式。Django将评论应用程序的所有函数URL放在了一个单独的模块中 django.contrib.comments.urls.comments,我们可以通过使用include函数将它包含进来。打开 urls.py文件并加入下面黑体字部分:
urlpatterns = patterns('',
  # Comments
  (r'^comments/', include('django.contrib.comments.urls.comments')),
)
这和我们之前的用法有点不同,我们告诉Django所有的URL入口都在模块django.contrib.comments.urls.comments 中,现在把它包含进来,并且所有这些URL都映射到^comment/下面。这样做可以提高重用性,其他项目想使用评论应用程序的时候可以直接使用这个模块。
现在我们完成了要做的事,评论应用程序可以使用了。

为评论功能创建视图









由于我们准备让用户可以对共享的书签进行评论,我们需要一个单独的页面来显示对共享书签的评论。为此我们将创建一个新的视图函数,这个视图函数将通过URL获得共享书签的ID,并显示这个共享的书签和它的评论已经一个用于添加评论的表单。


我们从定义这个视图函数的URL入口开始,打开urls.py
文件并加入下面黑体字部分:


urlpatterns = patterns('',


# Browsing


(r'^$', main_page),


(r'^popular/$',
popular_page),


(r'^user/(\w+)/$',
user_page),


(r'^tag/([^\s]+)/$',
tag_page),









(r'^tag/$',
tag_cloud_page),


(r'^search/$', search_page),


(r'^bookmark/(\d+)/$',
bookmark_page),


)


这个新的URL看起来和用户页面的URL很类似,但是这里给bookmark_page函数传入的是\d而不是\w参数,如果你还记得前面章节的内容,你应该知道\w代表任意字符,所以你一定猜到了这里的\d代表任意的数字。这是因为bookmark_page函数需要一个书签的ID值,而ID值是一个数字型的字段。你也一定记得给一个正则表达式加上括号对于视图函数来说是一个附加的字符串参数。


现在让我们来编写bookmark_page函数,打开bookmarks/views.py加入下面的方法:


def bookmark_page(request,
bookmark_id):


shared_bookmark =
get_object_or_404(


SharedBookmark,


id=bookmark_id


)


variables =
RequestContext(request, {


'shared_bookmark':
shared_bookmark


})


return
render_to_response('bookmark_page.html', variables)




这个函数非常简单,它利用get_object_or_404方法来通过ID获得一个SharedBookmark对象。之后把这个对象传给模板bookmark_page.html。最后我们来创建bookmark_page函数的模板。在模板目录下创建一个bookmark_page.html文件并加入下面的代码:


{% extends "base.html"
%}


{% block title %}Bookmark:


{{
shared_bookmark.bookmark.title|escape }}{% endblock %}


{% block head %}


<a href="/vote/?id={{
shared_bookmark.id }}"


class="vote">[+]</a>


<a href="{{
shared_bookmark.bookmark.link.url }}"


class="title">


{{
shared_bookmark.bookmark.title|escape }}</a>


{% endblock %}


{% block content %}


Posted By:









<a
href="/user/{{ shared_bookmark.bookmark.user.username }}/"


class="username">


{{
shared_bookmark.bookmark.user.username }}</a> |


<span
class="vote-count">Votes: {{ shared_bookmark.votes
}}</span>


{% endblock %}


这断代码没什么特别之处,我们在页面的上部显示书签和投票连接,以及有关这个书签信息的内容放在也没的中部。


创建bookmark_page函数很简单,不过下一节会是一个很激动人心的部分,我们将在页面上加入一个书签的评论的列表和一个增加评论的表单。


显示评论信息和评论表单


你会吃惊的发现利用Django的评论模块,你可以非常简单给应用程序增加评论功能。基本上评论程序为你提供了3个模板标签,你可以将它们应用到你自己的模板中:


  • get_comment_count
    :返回当前页面的评论个数。


  • get_comment_list :
    返回当前页面评论的列表。


  • comment_form :
    显示一个用于添加评论的表单。



缺省情况下这些标签并不在模板中,为了使用它们你需要在模板的开头加入下面的语句:


{% load comments %}


load方法通常用于加载模板中附加的选项,这些选项不是缺省存在的。


我们上面提到的这些标签都接受下面的参数:


  • 评论内容的类型,你可以使用下这样的参数格式:application.model


  • 评论对象的ID



所以如果 你想获得一个共享书签的评论个数,将下面的代码加入bookmark_page.html中:


{% get_comment_count for
bookmarks.sharedbookmark


shared_bookmark.id as
comment_count %}


现在模板变量comment_count中保存的是当前共享书签的评论个数。


类似的你可以在bookmark_page.html中加入下面的代码来获得一个共享书签的评论列表:


{% get_comment_list for
bookmarks.sharedbookmark


shared_bookmark.id as
comment_list %}


现在 comment_list变量中保存的了当前共享书签的评论列表。这个列表中的每个评论对象包含以下属性:


  • user:这个user对象代表添加这个评论的用户。


  • submit_date:提交评论的日期。


  • Comment:评论信息的文本内容。


  • ip_address:添加这个评论的ip地址。



最后,如果你想在页面上增加一个用于提交评论的表单,在bookmark_page.html中输入下面的代码:


{% comment_form for
bookmarks.sharedbookmark shared_bookmark.id %}


现在是时候把它们组织到一起了,打开templates/bookmark_page.html并加入下面黑体字部分的内容:


{%
extends "base.html" %}


{%
load comments %}


{%
block title %}Bookmark:


{{
shared_bookmark.bookmark.title|escape }}{% endblock %}


{%
block head %}


<a
href="/vote/?id={{ shared_bookmark.id }}"


class="vote">[+]</a>


<a
href="{{ shared_bookmark.bookmark.link.url }}"
class="title">


{{
shared_bookmark.bookmark.title|escape }}</a>


{%
endblock %}


{%
block content %}


Posted
By:


<a
href="/user/{{ shared_bookmark.bookmark.user.username }}/"


class="username">


{{
shared_bookmark.bookmark.user.username }}</a> |


<span
class="vote-count">Votes: {{ shared_bookmark.votes
}}</span>


<h2>Comments</h2>


{% get_comment_count for
bookmarks.sharedbookmark


shared_bookmark.id as
comment_count %}


{% get_comment_list for
bookmarks.sharedbookmark


shared_bookmark.id as
comment_list %}


{% for comment in
comment_list %}


<div class="comment">


<p><b>{{
comment.user.username }}</b> said:</p>


{{
comment.comment|escape|urlizetrunc:40|linebreaks }}


</div>


{% endfor %}


<p>Number of
comments: {{ comment_count }}</p>


{% comment_form for
bookmarks.sharedbookmark


shared_bookmark.id %}


{%
endblock %}


上面的代码中利用刚才讲过的标签给共享书签增加了显示评论列表的功能和添加评论的表单。结合我们前面将的内容代码应该很好理解。除了下面这行需要额外解释一下:


{{
comment.comment|escape|urlizetrunc:40|linebreaks }}


你一定还记得这是用于模板过滤器的语法结构,这里我们使用了三个过滤器:


  • escape:我们使用这个过滤器来转换评论内容中的html代码,避免人们在输入了HTML代码后出现异常。


  • Urlizetrunc:这个过滤器将评论内容中的URL转换为可以点击的链接。如果URL的字符超过40个那么可显示的链接只显示40个。


  • Linebreaks:这个过滤器将评论中的换行标志转换为<p><br>标记。



我们差不多完成了评论功能,剩下的工作就是给评论表单定义模板,以及提交评论后显示的页面。我们还将作一些小的修改来改进评论的显示效果。


创建评论模板


评论功能需要增加两个模板,一个用于显示评论的表单另一个用于显示提交表单后的页面。这些模板被放在templates下的comments目录中,所以现在现创建这个目录。


我们从创建显示评论表单的模板开始,在templates/comments/下创建一个form.html文件,并加入下面的代码:


{% if user.is_authenticated %}


<form
action="/comments/post/" method="post">


<p><label>Post
a comment:</label><br />


<textarea name="comment"
rows="10"


cols="60"></textarea></p>


<input type="hidden"
name="options"


value="{{ options
}}" />


<input type="hidden"
name="target" value="{{ target }}" />


<input type="hidden"
name="gonzo" value="{{ hash }}" />


<input type="submit"
name="post" value="submit comment" />


</form>


{% else %}


<p>Please <a
href="/login/">log in</a> to post comments.</p>


{% endif %}


如果用户登录系统模板就显示一个提交评论的HTML表单,表单的action和其他字段的内容值的取决于Django
评论程序的定义,不要在意。


接下来我模创建一个成功提交评论后显示的页面模板。模板中有一个变量名为object,这个对象代表了刚刚提交的评论对象(在这里这个对象是SharedBookmark)。在这个页面上显示一个返回共享书签页是个好主意,所以我们在templates/comments/下创建一个posted.html文件,并加入下面的代码:


{% extends "base.html"
%}


{% block title %}Comment Posted
Successfully{% endblock %}


{% block head %}Comment Posted
Successfully{% endblock %}


{% block content %}


<p>Thank you for
contributing.</p>


{% if object %}


<p><a
href="/bookmark/{{ object.id }}/">


View your comment</a></p>


{% endif %}


{% endblock %}


现在我们完成了评论功能,在我们测试这个功能之前还需要给它加个链接,打开templates/shared_bookmark_list.html,修改并加入下面黑体字部分的代码:


{% if shared_bookmarks %}


<ul class="bookmarks">


{% for shared_bookmark in
shared_bookmarks %}


<li>


<a
href="/vote/?id={{ shared_bookmark.id }}"


class="vote">[+]</a>


<a href="{{
shared_bookmark.bookmark.link.url }}"


class="title">


{{
shared_bookmark.bookmark.title|escape }}</a>


<br />


Posted By:


<a href="/user/{{
shared_bookmark.bookmark.user.username }}/"


class="username">


{{
shared_bookmark.bookmark.user.username }}</a> |


<span
class="vote-count">Votes:


{{
shared_bookmark.votes }}</span> |


<a
href="/bookmark/{{ shared_bookmark.id}}/">Comments</a>


</li>


{% endfor %}


</ul>


{% else %}


<p>No bookmarks
found.</p>


{% endif %}


我们给评论功能也没加上一些修饰,打开site_media/style.css并加入下面的内容:


.comment {


margin: 1em;


padding: 5px;


border: 1px solid #000;


}


现在终于大功告成了,打开http://127.0.0.1:8000/测试一下,在共享书签后点击评论链接,你会看到类似下面的页面:










如果你试着提交评论你会看到一个提示成功提交的页面,看起来像下面这样:










点击"View your
comment"
你会看到你刚才提交的评论:










神奇的是实现这些功能之用了很少的代码,我们没费多大功夫就完成了评论功能。更重要的是你给书签应用程序增加了书签评论功能。


总结


本章我们为应用程序增加了两个重要的功能。首先是可以让人们对感兴趣的书签进行投票并浏览最受欢迎的书签。第二个功能是允许用户对书签进行评论。这意味着用户可以查找他们感兴趣的书签并和其他用户进行沟通,从而为我们的应用程序提供社交功能。本章我们学习了几个新的Django
功能和Django中的评论模块。


下一章我们将讨论一个新的话题。我们需要为应用程序提供一个管理控制台来维护你的数据模型。幸运的是Django为此提供了一套功能完善的接口。我们将在下一章学习有关知识。









Sunday, January 11, 2009

使用Ajax技术增强用户界面

使用Ajax技术

介绍


Ajax对于Web2.0来说可以说是里程碑式的技术,Ajax是由一些列技术组成的可以让开发人员实现可交互式、功能丰富的Web应用程序。这些技术在Ajax出现之前已经存在很多年了,然而使用Ajax技术可以让你实现无属性的页面数据交互。


由于我们的项目是一个Web2.0应用程序,所以增加用户体验变得尤为重要。我们项目的成功取决于用户可以通过它方便的实现发布和共享书签的功能。本章将通过使用Ajax技术使用户界面更优化并提高交换性。


本章你将学到以下内容:




  • Ajax以及在Web应用程序中使用它的好处。



  • 如何在Django中安装Ajax架构。



  • 如何使用开源的jQuery架构。



  • 增加标签搜索功能。



  • 不加载新页面直接修改一个书签。



  • 发布书签时,提供标签的自动补全功能。


Ajax以及使用它的好处


Ajax是Asynchronous JavaScript and XML的缩写,意思是异步JavaScript与XML,包括以下技术:




  • HTML和CSS用于构造页面和样式。



  • JavaScript用于动态的访问和操作页面信息。



  • XMLHttpRequest,这是一个有浏览器提供的对象,通过它可以在不刷新页面的情况下实现客户端与服务器之间的数据交互。



  • 一种用于在客户端和服务器端交换数据的数据格式。XML是一种格式,也可以使用HTML,普通文本或者符合JavaScript标准的JSON格式。


使用Ajax时,如果用户发出一个请求无需刷新页面就可以在后台实现客户端到服务器之间数据交换。这样开发人员可以更好的实现页面交换行为并增加用户体验。


正确使用Ajax技术有如下好处:




  • 更好的用户体验。使用Ajax用户可以在不刷新页面的情况下完成功能工作,就像操作一个桌面应用程序一样。



  • 更好的性能。使用Ajax只有请求数据被发送到服务器端,节省可带宽资源提供了应用程序速度。


有很多应用程序使用了Ajax技术,Google的地图服务和Gmail就是很好的例子。实际上这两个应用是使用Ajax的杰出代表。Gmail与其他邮件服务的一个不同之处是他的用户界面,用户在使用Gmail操作是可以不用等待页面的重新加载直接在当前页面完成操作。这确实提高了Gmail的用户体验,使用Gmail时你会感觉你使用的是一个功能丰富、反映快速的应用程序而不再只是一个web站点。


本章介绍了如何在Django中使用Ajax技术,从而提升我们项目的用户体验。我们将实现当今Web应用程序中三种主要的Ajax应用。不过在这之前我们将先来看看使用Ajax框架而不是原生的JavaScript的好处。



在Django中使用Ajax框架


本节我们将选择并在我们的项目中安装一个Ajax框架。当然这不是在Django中使用Ajax所必须的,但是这将大大提高使用Ajax的便利性。下列了使用Ajax的好处:




  • 在不同的浏览器之间JavaScript的实现是不同的。一些浏览器提供了功能完善的实现,而另一些的实现并不完全或者并不是标准的实现。如果不使用Ajax框架,开发人员就要注意不同浏览器中的这些区别,对于同一个功能可能要根据不同的浏览器单独实现。但是,如果使用Ajax框架这一切都由框架来负责,Ajax框架抽象了对不同浏览器JavaScript实现的访问方法。这样我们就可以专注于功能的实现,而不必再操心不同浏览器和它们的限制。



  • 标准JavaScript提供的函数和类无法满足web应用程序的需求。我们不得不变量很多代码来完成大多数常用的功能。因此,即使你不打算使用Ajax框架,你也会发现你不得不编写一个函数库来封装这些JavaScript从而提供他们的重用性。但是既然这里已经有很多现成的优秀的开源框架我们为什么还要再开发一个呢?


目前市面上有很多Ajax框架,大到提供综合解决方案的框架:这些框架提供服务端到服务端、客户端到服务端的组件;小到轻量级的框架:这些框架只提供了客户端到服务器端JavaScript 库封装。根据目前我们项目的需要,一个客户端到服务端的Ajax框架就够了。另外,这个Ajax框架应该能够方便的集成到Django中,而且这个框架最好是轻量级的快速的框架。有很多优秀的框架都能满足我们的需求,比如Prototype,Yahoo! UI Library 和 jQuery。这些框架我都使用过,而且他们都很不错。但是对于当前的项目我准备使用jQuery,因为它是其中最轻量级的一个。而且jQuery有一个活跃的开发社区和大量的插件。如果你熟悉其他的Ajax框架你可以在本章中继续使用他们。不管你使用哪种Ajax框架服务端的Ajax代码都是一样的。


现在你已经了解了使用Ajax框架的好处,现在我们准备把jQuery安装到Django中。


下载并安装jQuery


使用jQuery的好处之一是它只有一个单独的文件,你可以在http://jquery.com上下载它的最新版本。你有两个选择:




  • 未压缩版本:这是标准版本,我建议你在开发时使用这个版本,你将得到一个.js文件,所有的库函数代码都包含在其中。



  • 压缩版本:你得到的还是一个.js文件,不过代码看起来有点混乱。jQuery的开发人员为了减少文件的尺寸,对未压缩版的文件做了一些改动,比如移除空格、重命名变量等等。当你将应用程序部署到生产环境时这个版本非常有用,因为它提供了和未压缩版本一样的功能,但是文件更小了。


我建议你使用未压缩版本,因为在开发阶段你可能想要查看jQuery中的一段代码到底是如何运行的。不过这两个版本提供的功能是一样的,从一个版本切换到另一个只要把文件替换一下就可以了。


下载了jquery-xxx.js(xxx是版本号)文件后,把它重命名为jquery.js,复制到我们项目的site_media文件夹下,接下来把这个文件包含到我们的base.html模板文件中,这样我们就可以在每个集成它页面中使用jQuery了。打开 templates/base.html 文件加入下面代码中黑体字部分:


<head>
<title>Django Bookmarks
{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style
type="text/css" />
<script type="text/javascript"
src="/site_media/jquery.js"></script>

</head>


你也可以把自己的JavaScript代码放在一个单独的.js文件中然后像这样把它包含到html页面中,或者可以向下面这样直接将代码嵌入到HTML中:


<script type="text/javascript">
// JavaScript code goes here.
</script>


建议使用第一种方法,因为把HTML和JavaScript分开使代码更加清晰。由于我们准备使用自己的.js文件,我们需要一种方法将.js文件连接到模板中而不用每次都更改base.html文件。我们来创建一个模板块,将它置于HTML的head标签之间。我们跟这个模板块取名为external,因为它将链接一个外部的文件到当前模板中,打开 templates/base.html 文件加入下面黑体字部分:


<head>
<title>Django Bookmarks {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style.css"
type="text/css"/>
<script type="text/javascript" src="/site_media/jquery.js">
</script>
{% block external %}{% endblock %}
</head>


现在如果子模板中需要使用JavaScript代码,只要重载这个external就可以了。


在我们开始使用Ajax之前,先来大概的介绍一下jQuery框架。


jQuery JavaScript框架


jQuery是一个JavaScript函数库,它用于与HTML文档交换以及操作HTML文档。使用jQuery你可以大大的节省开发时间并且避免了跨浏览器带来的问题。


通常使用jQuery分以下两步:




  1. 选取一个或一组要操作的HTML元素。



  2. 使用jQuery提供的方法来操作这些HTML元素。


选取HTML元素


jQuery提供了一个非常简单的方法来选取HTML元素,你可以通过将HTML元素标签作为字符串参数传递给 $函数来实现元素的选取。下面举几个例子:




  • 如果你想选择一个页面中所有的(<a>)元素,可以通过调用$("a") 函数来实现。



  • 如果你想选取带有 .title class 属性的 <a> 元素,可以通过调用$("a.title")函数来实现。



  • 如果你想选择一个ID属性为#nav的元素,可以通过调用$("#nav")来实现。



  • 如果你想选择属性ID为#nav 元素下的所有(<li>) 元素,可以通过调用$("#nav li")来实现。


$()函数返回一个jQuery对象,一旦你得到这个对象就可以通过这个对象的方法来与HTML元素进行交互了。


jQuery方法


jQuery提供了大量的方法来操纵HTML文档。通过这些方法,你可以显示或者隐藏页面元素,给元素增加监听方法,修改元素的CSS属性,控制页面结构,更重要的是你可以通过jQuery执行Ajax请求。


在学习jQuery之前,我强烈建议你使用FireFox浏览器并安装它的一个插件FireBug,这对于开发和调试jQuery程序非常有帮助。这个扩展提供了一个非常类似于Python控制台的JavaScript控制台,通过这个插件你可以直接输入JavaScript语句并立即看到结果,你不必再单独的创建一个文件来查看输出结果。我们可以在 http://www.getfirebug.com/上下载安装Firebug插件。


如果由于某些原因你不想使用Firefox,Firebug为其他浏览器提供了一个“lite”版本的扩展,这个扩展是一个JavaScript文件。你可以下载这个文件并把它保存到site_media目录下,然后把这个文件包含到templates/base.html文件中:


<head>
<title>Django Bookmarks {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style.css"
type="text/css"/>
<script type="text/javascript" src="/site_media/firebug.js">
</script>
<script type="text/javascript" src="/site_media/jquery.js">


</script>
{% block external %}{% endblock %}
</head>


现在我们来看看如果使用Firebug插件,运行开发服务器然后打开浏览器并按下F12键,试着操作一下。


隐藏和显示元素


让我们先从简单的开始,hide()表示隐藏元素,show()方法表示显示元素。比如下面的例子中我们通过命令来改变导航菜单的行为:


>>> $("#nav").hide()
>>> $("#nav").show()



通过jQuery我还可以在元素显示或者隐藏的时候个他加上一些特效,试试这些方法 fadeOut(), fadeIn(), slideUp() 或者 slideDown() ,你看到了什么?

这些方法和我们前面将的方法类似,比如你执行下面的命令页面上所有的标签都会消失:


>>> $('.tags').slideUp()


访问CSS属性和HTML元素属性


接下来我们学习如何修改页面元素的CSS属性。jQuery提供了一个方法名为css()来执行CSS操作。如果你调用这个方法并把CSS属性名作为参数传递给它,这个方法会返回CSS参数的值:


>>> $("#nav").css("display")
Result: "block"


如果你给这个方法传递第二个参数,那么这第二个参数会被当作第一个参数的值,在方法执行后会重新设置这个CSS属性的参数值:


>>> $("#nav").css("font-size", "0.8em")
Result: <div id="nav" style="font-size: 0.8em;">


实际上你可以向操作CSS一样操作HTML元素的任何属性。同样jQuery提供了一个名为attr()方法。如果你给它传入的是一个元素属性名,那么这个方法返回的是这个属性的值,如果你传入的是一对属性名称/值,就会重新设置这个属性的值。为了测试这个方法,我们打开书签提交表单,输入下面的内容:


>>> $("input").attr("size", "48")
Results:
<input id="id_url" type="text" size="48" name="url">
<input id="id_title" type="text" size="48" name="title">
<input id="id_tags" type="text" size="48" name="tags">


上面的方法将页面中所有的文本输入框的长度修改为48。


另外,jQuery还提供了一些快捷方法来完成通常的取值和赋值操作,比如val()方法返回一个文本输入框的值,如果这个方法有一个参数值,那么就会用参数值来设置文本输入框的值。还有两个方法addClass() 和removeClass()方法用于给HTML元素增加或删除CSS样式,toggleClass()方法用于触发HTML元素的CSS样式,也就是说使它立即生效。


操作HTML文档


现在你已经熟悉了如何控制页面内的元素属性,现在我们来看看如何在页面中增加或者移除元素。要在一个页面元素之前增加一段HTML代码调用方法before()方法,要在元素后面增加HTML代码调用after()方法。你看jQuery的方法名非常好记,不是吗?


现在让我们来测试一下这个两个方法,打开一个页面,输入下面的内容:


>>> $(".tags").before("<strong>(</strong>")
>>> $(".tags").after("<strong>)</strong>")


你可以通过这种方法在页面的任何位置增加你想要的内容,这些内容可以是HTML标记,也可以是任何文本内容,这些方法为操作HTML页面内容提供了非常灵活的方式。


如果你想在页面上移除内容,可以调用remove()方法,比如:


$("#nav").remove()


这个方法不仅是让元素在页面上消失,而是彻底的从页面上删除了元素。你没办法再次获取被移除的元素,比如:


>>> $("#nav")
Result: []


当然,你只是在当前的页面实例中移除了这个元素,它并没有真正的消失,如果你重新打开这个页面,被移除的元素会重新显示。


遍历文档树


尽管jQuery提供的选取器已经很强大了,你可以通过它找到你要的元素,但是有的时候我们希望从HTML文档的某个特殊部分遍历元素。为此,jQuery提供了一系列的方法来完成这些操作, parent() 方法返回元素的一个上层元素, children()方法返回当前元素的所有子元素。find()方法用于找到当前元素组中具体的某个或者某组元素,比如 $("#nav").find("li") 会找到,菜单组中使用的li元素。如果你想访问元素集合中某个特定的元素可以通过get加索引参数的方式,比如: $("li").get(0) 会得到第一个个元素。


控制事件


接下来我们讨论事件控制器。一个事件控制器就是一个JavaScript函数,它在某些特定事件下会被触发,比如单击一个按钮或者提交表单的时候。jQuery提供了大量的方法来维护时间操作,在我们当前的项目中我们只关心鼠标点击和表单提交两种事件。对于鼠标点击事件我们需要用到click()方法,现在我们在Firebug中输入下面的内容:


>>> $("p").after("<button id=\"test-button\">Click me!</button>")


(注意,这里我们必须使用\ 符号来对引号进行转义)


如果现在你点击这个按钮,什么都不会发生。那么,让我们来给他加上事件控制器:


>>> $("#test-button").click(function () { alert("You clicked me!"); })


现在当你再点击这个按钮的时候,你会看到一个对话框。那么到底click方法是如何运行的呢?,首先:


function () { alert("You clicked me!"); }


这是click方法的参数,它定义了一个函数,但是这个函数没有名称,实际上这种结构在JavaScript中称为匿名函数,他用于当你准备定义一个函数把它作为参数传给另一个函数的时候。我们可以不使用匿名函数:


>>> function handler() { alert("You clicked me!"); }
>>> $("#test-button").click(handler)


上面的两种方式效果是一样的,但是第一种更简洁紧凑,我建议你使用匿名函数你会发现这中方式更加简洁而且可读性更高。


控制表单时间和控制鼠标事件类似,你需要先获得一个表单元素,然后调用submit() 方法,在方法的参数中放入你要处理的事件。在以后的章节中的Ajax中会经常使用这个方法。


发送Ajax请求


在我们结束这一小节之前,先来谈谈Ajax请求。jQuery提供了很多想服务器发送Ajax请求的方法,比如 load()方法会在当前选取的元素中根据URL来加载一个页面。还有一些方法用来发送GET和POST请求,并接收返回结果。在后面讲述Ajax中我们会深入讲解这些方法。


接下来是什么


本节中对jQuery的快速指南已经足够应付我们当前项目中需要开发的Ajax功能,结束本章后你可以利用所学给你自己的项目增加更多的Ajax功能。不过记住这里的介绍只是jQuery的冰山一角,如果你想对jQuery有一个综合的了解我建议你阅读《Learning jQuery》这本书,你可以从http://www.packtpub.com/jQuery得到更多有关这本书的信息。


实现书签在线查询功能


让我们通过给项目增加在线查找功能开始我们的Ajax之旅。在线查找功能很简单:用户输入几个字符然后点击“查找”,通过Ajax将查找的字符串发送到后台,然后在当前页面显示查询结果。当前页面不会重新加载,从而节省了网络带宽并且提升了用户体验。


在我们开始实现这个功能之前,一定要记住Ajax开发的一个重要原则:首先在不使用Ajax的情况下实现这个功能,然后给它加上Ajax。这样你才能确保所有的用户都可以使用你的应用程序,包括那些使用了不支持JavaScript或者Ajax技术浏览器的用户。


实现查询


所以,在我们实现Ajax之前先来实现一个通过标题查找书签的视图函数。首先,我们需要创建一个查询表单,请打开 bookmarks/forms.py加入下面的内容:


class SearchForm(forms.Form):
query = forms.CharField(
label='Enter a keyword to search for',
widget=forms.TextInput(attrs={'size': 32})
)


如你所见这是一个非常简单的表单类,只有一个用于输入查询关键字的文本框。


接下来,我们打开bookmarks/views.py文件创建一个用于查询的视图函数:


def search_page(request):
form = SearchForm()
bookmarks = []
show_results = False
if request.GET.has_key('query'):
show_results = True
query = request.GET['query'].strip()
if query:
form = SearchForm({'query' : query})
bookmarks = \
Bookmark.objects.filter (title__icontains=query)[:10]
variables = RequestContext(request, { 'form': form,
'bookmarks': bookmarks,
'show_results': show_results,
'show_tags': True,
'show_user': True
})
return render_to_response('search.html', variables)


除了几个方法调用之外,这个函数非常容易理解。首先我们初始化了三个变量,form用于保存查询表单数据,bookmarks变量保存了要显示的书签记录查询结果,show_results是一个布尔变量,我们通过这个标记区分两中情况:




  • 没有提交任何查询,这种情况下查询页面不显示查询结果。



  • 提交了一个查询,这种情况下页面将显示查询结果,如果没有匹配查询条件的记录就显示“No bookmarks found”(未找到书签)的消息。


我们需要使用 show_results变量,因为光是bookmarks无法判断用户是否提交了查询,因为不过是用户没有提交查询或者是提交的查询字符串没有匹配记录,bookmarks都是空的。


接下来,我们通过request.GET字典对象的has_key方法来判断用户是否提交了查询字符串:


if request.GET.has_key('query'):
show_results = True
query = request.GET['query'].strip()
if query:
form = SearchForm({'query' : query})
bookmarks = Bookmark.objects.filter(title__icontains=query)[:10]


这里我们使用的GET而不是POST,原因是查询页面并不会创建或者修改数据。通常的规则是,如果仅仅是查询数据,我们使用GET对象,如果是在数据库中创建、修改或者删除数据我们使用POST对象。


如果用户提交了查询字符串,我们设置show_results 为True,并且通过调用strip()方法来确保字符串中不包含空格。如果查询关键字去掉空格后不为空,那么我们就根据它创建一个查询表单对象,然后通过Bookmark.objects的filter方法进行查询,这是我们第一次使用filter方法,你可以把它想象成Django中的查询语句,它根据方法参数的内容来进行查询并返回查询结果集。这个方法的参数必须遵循以下规则:


field__operator


注意这里的field和operator中间是两个下划线:其中field是我们要查询的字段名,operator是我们要执行的希望执行的查询方法。下来是常用的查询方法:




  • exact:字段值必须精确匹配要查询的内容。



  • contains:字段值包含要查询的内容。



  • startswith:字段值以查询内容开头。



  • lt:字段值小于要查询的内容。



  • gt:字段值大于要查询的内容。


注意以上方法对于查询内容是大小写敏感的,同时Django还提供了对应的大小写不敏感的方法: iexact, icontains 和 istartswith。


结合以上解释然我们再来看看刚才的视图函数,我们使用icontains来查找标题包含查询内容的数据集合,然后通过Python的数组分片技术取出前十条记录。最后我们将这些变量传递给 search.html 模板。


现在我们在templates目录下创建 search.html页面并输入下面的内容:


{% extends "base.html" %}
{% block title %}Search Bookmarks{% endblock %}
{% block head %}Search Bookmarks{% endblock %}
{% block content %}
<form id="search-form" method="get" action=".">
{{ form.as_p }}
<input type="submit" value="search" />
</form>
<div id="search-results">
{% if show_results %}
{% include 'bookmark_list.html' %}
{% endif %}
</div>
{% endblock %}


这个页面和我们前面创建的模板非常相似,我们在其中包含了 bookmark_list.html 部分,我们给查询表单设置了ID,并且给查询结果部分的DIV也设置ID,这样我们就可以在后面使用JavaScript来与他们交互。你看,使用include标记为我们节省了很多时间。我们只需要修改这个一个文件就能改变所有引用它的页面,这是一个对于组织和维护模板来说非常有用的技术。


最后别忘了在urls.py加入对视图函数的URL访问入口:


urlpatterns = patterns('',
# Browsing
(r'^$', main_page),
(r'^user/(\w+)/$', user_page),
(r'^tag/([^\s]+)/$', tag_page),
(r'^tag/$', tag_cloud_page),
(r'^search/$', search_page),
)




现在打开 http://127.0.0.1:8000/search/链接测试一下,我们也可以把这个链接加入templates/base.html 页面的导航菜单中:


<div id="nav">
<a href="/">home</a>
{% if user.is_authenticated %}
<a href="/save/">submit</a>
<a href="/search/">search</a>
<a href="/user/{{ user.username }}/">
{{ user.username }}</a>
<a href="/logout/">logout</a>
{% else %}
<a href="/login/">login</a>
<a href="/register/">register</a>
{% endif %}
</div>


现在我们已经实现了查询功能,我们有了一个新查询页面。我们将在本章的后面给他加入Ajax功能:查询过程将在后台执行,页面不会重新加载,你会发现按照我们当前清晰的代码结构,增加Ajax功能非常简单。


实现在线搜索


实现在线搜索我们需要做两件事:




  • 拦截并控制查询表单的提交事件。这可以通过jQuery的submit()方法实现。



  • 通过Ajax在后台加载搜索结果,并把结果插入到当前页面。这可以通过jQuery的load()方法实现,我们后面会介绍这个方法。


jQuery提供了一个load()方法,它用于从服务器取回一个页面并把页面内容插入到所选的元素中。所以在当前的应用程序中远程页面的URL会被当作参数加载。


首先,我们需要修改一下视图函数,当GET对象中包含一个附加的名为ajax的变量时,视图函数值返回搜索结果页面,而不包含其他部分(比如搜索表单、导航菜单等)。也就是说当GET中包含变量ajax时我们可以只返回bookmark_list.html而不用返回search.html,现在打开 bookmarks/views.py文件修改内容如下:


def search_page(request):
[...]
variables = RequestContext(request, {
'form': form,
'bookmarks': bookmarks,
'show_results': show_results,
'show_tags': True,
'show_user': True
})
if request.GET.has_key('ajax'):
return render_to_response('bookmark_list.html', variables)
else:
return render_to_response('search.html', variables)




接着,我们在site_media 文件夹下创建一个名为 search.js的文件,并把它加入templates/search.html中:


{% extends "base.html" %}
{% block external %}
<script type="text/javascript" src="/site_media/search.js">
</script>
{% endblock %}
{% block title %}Search Bookmarks{% endblock %}
{% block head %}Search Bookmarks{% endblock %}
[...]


现在真正有趣的部分开始了,让我们创建一个函数来加载搜索结果并把它插入到相应的div中,在site_media/search.js中加入如下代码:


function search_submit() {
var query = $("#id_query").val();
$("#search-results").load(
"/search/?ajax&query=" + encodeURIComponent(query)
);
return false;
}


让我们逐行解释它们的含义:




  • 函数首先通过val()方法获得查询关键字的值。



  • 我们通过load() 函数从search_page视图函数获得查询结果,然后将结果内容插入到 id为#search-results 的div元素中。在这里我们第一次使用了encodeURIComponent函数,它用于给query参数进行URL编码,类似于Django视图函数中的urlencode,这样做是为了确保用户输入的关键字中包含的特殊字符被正确的转义。然后我们把这个转义后的关键字链接到/search/?ajax&query=后面。这个URL会请求search_page视图函数然后将变量ajax和query的值传给它。然后视图函数返回结果页面, load()函数将结果页面插入到ID位 #search-results 的div元素中。



  • 我们在函数的最后返回false是为了告诉浏览器,这个函数执行后不进行任何表单提交操作,否则页面会被重新加载。


最后要提到的一点是,我们应该在什么时候调用这个search_submit函数。一个编写JavaScript的原则是我们不能在页面文档结束加载之前操作页面中的元素。因此这个函数应该在页面刚刚被加载后执行。幸运的是jQuery提供了一个在HTML加载后执行函数的方法,现在让我们来使用这个方法,打开site_media/search.js文件输入下面的内容:


$(document).ready(function () {
$("#search-form").submit(search_submit);
});


$(document)代表当前页面的文档元素。注意document没有用引号包含,它是一个浏览器提供的变量而不是一个字符串。ready()方法用于当所选取的元素加载完毕后执行一个函数。所以这里我们告诉jQuery当页面加载完毕后就执行一个函数。我们将一个匿名函数传递给ready() 方法,这个匿名函数将 search_submit 函数绑定到ID位 #search-form的表单的提交事件上。


就是这些了,我们只用了15行代码就实现了我们的在线搜索功能。现在打开http://127.0.0.1:8000/search/链接测试一下,提交一个查询关键字,你会发现查询结果被自动加载到当前页面,而页面并没有刷新。





本节讲述的内容可以应用于任何需要无刷新加载页面的需求。比如你可以给增加评论的页面提供一个预览功能,用户可以在不刷新页面的情况下直接在当前页面预览他准备提交的评论信息。在下一节我们将给项目增加实时修改书签的功能,用户可以在不刷新页面的情况下直接修改书签。


实时修改书签


我们已经实现了大部分的表情编辑功能。你应该还记得前面章节中 bookmarks/views.py中的bookmark_save_page视图函数,对于相同URL的书签我们只保存一条记录,而不是重复的记录,这得益于Django提供的 get_or_create方法,这大大简化了对于书签内容的编辑操作。下面将实现书签的编辑功能,我们需要实现:




  • 将书签的URL通过GET方式传递一个名为url的变量给bookmark_save_page函数。



  • 修改 bookmark_save_page 函数,让他根据传入的url变量值来修改书签的URL。


在实现上述功能之前让我们先给bookmark_save_page 来个瘦身,我们把存储书签的代码单独抽取为一个 _bookmark_save函数。方法前的下划线告诉Python在导入视图函数模块的时候不要把这个函数导入。这个函数需要一个request(请求)对象和一个有效的表单对象作为参数,这个函数用于保存表单中的书签对象并返回保存后的书签对象。请打开 bookmarks/views.py文件并创建这个函数,如果你愿意可以从 bookmark_save_page 函数中把代码直接剪切出来:


def _bookmark_save(request, form):
# Create or get link.
link, dummy = \
Link.objects.get_or_create(url=form.clean_data['url'])
# Create or get bookmark.
bookmark, created = Bookmark.objects.get_or_create(
user=request.user,
link=link
)
# Update bookmark title.
bookmark.title = form.clean_data['title']
# If the bookmark is being updated, clear old tag list.
if not created:
bookmark.tag_set.clear()
# Create new tag list.
tag_names = form.clean_data['tags'].split()
for tag_name in tag_names:
tag, dummy = Tag.objects.get_or_create(name=tag_name)
bookmark.tag_set.add(tag)
# Save bookmark to database and return it.
bookmark.save()
return bookmark


接下来在 bookmark_save_page函数中将你刚才移除的部分替换为_bookmark_save函数:


@login_required
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
bookmark = _bookmark_save(request, form)
return HttpResponseRedirect(
'/user/%s/' % request.user.username
)
else:
form = BookmarkSaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('bookmark_save.html', variables)


当前 bookmark_save_page 函数的逻辑应该是这样:


if there is POST data:
Validate and save bookmark.
Redirect to user page.
else:
Create an empty form.
Render page.


为了实现编辑书签的逻辑,我们稍微的修改一下变成这样:


if there is POST data:
Validate and save bookmark.
Redirect to user page.
else if there is a URL in GET data:
Create a form an populate it with the URL's bookmark.

else:
Create an empty form.
Render page.


让我们把上面的伪代码实现为Python代码,打开bookmarks/views.py文件编辑 bookmark_save_page函数:


from django.core.exceptions import ObjectDoesNotExist
@login_required
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
bookmark = _bookmark_save(request, form)
return HttpResponseRedirect(
'/user/%s/' % request.user.username
)
elif request.GET.has_key('url'):
url = request.GET['url']
title = ''
tags = ''
try:
link = Link.objects.get(url=url)
bookmark = Bookmark.objects.get(
link=link,
user=request.user
)
title = bookmark.title
tags = ' '.join(
tag.name for tag in bookmark.tag_set.all()
)
except ObjectDoesNotExist:
pass
form = BookmarkSaveForm({
'url': url,
'title': title,
'tags': tags
})
else:
form = BookmarkSaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('bookmark_save.html', variables)


这段代码首先检查GET中是否包含一个url变量,如果有,就通过这个url查找对应的Link和Bookmark对象,并把这些对象的数据绑定到书签表单中。你也许想问我们为什么把获取Link和Bookmark对象的操作放在一个异常处理结构中。老实说如果通过URL找不到这两个对象的时候抛出一个Http404异常也是可以的,但是我们的代码在这里实现为如果找不到这两个对象就创建一个标题和标签为空的书签表单。


好了,现在我们给每个标签加一个编辑的链接,打开 templates/bookmark_list.html 文件,加入如下代码:


{% if bookmarks %}
<ul class="bookmarks">
{% for bookmark in bookmarks %}
<li>
<a href="{{ bookmark.link.url }}" class="title">
{{ bookmark.titleescape }}</a>
{% if show_edit %}
<a href="/save/?url={{ bookmark.link.urlurlencode }}"
class="edit">[edit]</a>
{% endif %}

<br />
{% if show_tags %}
Tags:
{% if bookmark.tag_set.all %}
<ul class="tags">
{% for tag in bookmark.tag_set.all %}
<li><a href="/tag/{{ tag.nameurlencode }}/">
{{ tag.nameescape }}</a></li>
{% endfor %}
</ul>
{% else %}
None.
{% endif %}
<br />
[...]


注意这里编辑链接的URL格式:/save/?url=。由于我们希望只有登录用户可以修改书签,所以我们在这里使用了 show_edit 标记,当它的值为True时才能编辑。另外,应用程序应该不允许用户编辑其他人的书签。现在打开文件bookmarks/views.py,在user_page函数中加入show_edit变量:


def user_page(request, username):
user = get_object_or_404(User, username=username)
bookmarks = user.bookmark_set.order_by('-id')
variables = RequestContext(request, {
'bookmarks': bookmarks,


'username': username,
'show_tags': True,
'show_edit': username == request.user.username,
})
return render_to_response('user_page.html', variables)


表达式 username == request.user.username 的结果只有用户登录并查看的是自己的标签时这个值才为True,这正是我们想要的功能。


最后,我建议改名一下编辑链接的字体,打开site_media/style.css 文件并加入下面的内容:


ul.bookmarks .edit {
font-size: 70%;
}


现在打开页面测试一下我们新增的功能。


实现实时修改书签功能


现在我们已经实现了书签修改功能,现在开始实现有意思的部分通过Ajax增加实时修改功能。


我们通过以下步骤实现这个功能:



  • 我们拦截修改书签的鼠标单击事件,然后通过Ajax从服务器上加载书签编辑表单。然后我们根据书签表单中的内容更新当前页面上的书签信息。
  • 当用户提交书签修改表单时,我们拦截提交事件通过Ajax将修改后的书签数家发送到服务器。服务器端将书签信息保存并返回HTML格式的书签信息替换原来的书签编辑表单。

我们将使用与在线搜索功能非常相似的方法来实现这个功能。首先我们修改bookmark_save_page函数,这样它就可以响应GET中包含ajax变量的请求。接下来我们编写JavaScript代码来接收页面上编辑表单的信息,这样在用户提交书签信息的时候就可以截获书签数家并发送到服务器上保存。


由于我们准备从bookmark_save_page函数中得到一个包含书签编辑信息的表单给Ajax脚本,所以让我们稍稍修改一下模板。创建一个名为bookmark_save_form.html的模板文件,然后将bookmark_save.html文件中的一些部分放入这个新创建的模板文件中:


<form id="save-form" method="post" action="/save/">



{{ form.as_p }}

<input type="submit" value="save" />


</form>


注意这里我们同时还将表单的action 属性修改为/save/,并且给这个表单设置了ID属性,注意这里给表单定义ID属性是非常有必要的。


接下来我们把这个新增的模板加入到bookmark_save.html中:


{% extends "base.html" %}


{% block title %}Save Bookmark{% endblock %}


{% block head %}Save Bookmark{% endblock %}


{% block content %}


{% include 'bookmark_save_form.html' %}


{% endblock %}


好了,现在我们将表单分类成一个单独的模板文件,现在我们修改一下bookmark_save_page函数来加入处理Ajax请求功能。打开文件bookmarks/views.py 修改文件如下:



def bookmark_save_page(request):


ajax = request.GET.has_key('ajax')

if request.method == 'POST':


form = BookmarkSaveForm(request.POST)

if form.is_valid():



bookmark = _bookmark_save(form)


if ajax:



variables = RequestContext(request, {



'bookmarks': [bookmark],

'show_edit': True,

'show_tags': True

})

return render_to_response('bookmark_list.html', variables)

else:



return HttpResponseRedirect(


'/user/%s/' % request.user.username

)

else:


if ajax:


return HttpResponse('failure')

elif request.GET.has_key('url'):



url = request.GET['url']

title = ''

tags = ''

try:


link = Link.objects.get(url=url)

bookmark = Bookmark.objects.get(link=link, user=request.user)

title = bookmark.title

tags = ' '.join(tag.name for tag in bookmark.tag_set.all())

except:


pass

form = BookmarkSaveForm({


'url': url,


'title': title,

'tags': tags

})

else:


form = BookmarkSaveForm()

variables = RequestContext(request, {


'form': form

})

if ajax:



return render_to_response(



'bookmark_save_form.html',


variables

)

else:


return render_to_response(

'bookmark_save.html',

variables

)

让我们来逐个介绍其中的重点部分:


ajax = request.GET.has_key('ajax')

在方法的开始部分我们首先判断GET中是否有一个ajax变量。我们将判断结果保存在一个名为ajax的变量中。之后我们就可以通过这个变量判断浏览器是否发出了Ajax请求:



if form.is_valid():


bookmark=_bookmark_save(form):

if ajax:


variables=RequestContext(request,


{'bookmarks':[bookmark],

'show_edit':True,

'show_tags':True}

)

return render_to_response('bookmark_list.html',varibales)

else:


return HttpResponseRedirect('/user/%/'%request.user.username)

else:


if ajax:


return HttpResponse('failure')

如果我们接收的是POST请求,那么就要判断提交的表单是否为通过了校验,如果通过了校验我们就保存书签表单的数据。接下来我们校验是否是一个Ajax请求,如果是我们就将保存的书签表单数据显示到bookmark_list.html模板上;如果不是一个Ajax请求我们就将页面重定向到用户页面。另一种情况,如果表单没有通过校验,如果是Ajax请求就返回一个'failure'字符串,页面会根据这个字符串显示一个错误消息框;如果不是一个Ajax请求我们什么都不用做,页面会重新加载错误信息会自动显示在相应的页面上:



if ajax:


return render_to_response('bookmark_save_form.html',variables)

else:


return render_to_response('bookmark_save.html',variables)

在方法的最后部分我们处理非POST请求,如果是Ajax请求就返回bookmark_save_form.html模板,否则就返回bookmark_save.html模板。


现在我们的页面可以向处理普通页面一样处理Ajax请求。下面我们加入JavaScript代码来处理Ajax请求。创建一个bookmark_edit.js的文件并放入site_media文件夹。在我们加入JavaScript代码之前先把这个文件包含到user_page.html模板中,打开user_page.html并修改如下:



{% extends "base.html" %}

{% block external %}

<script type="text/javascript" src="/site_media/bookmark_edit.js">

</script>

{% endblock %}

{% block title %}{{ username }}{% endblock %}

{% block head %}Bookmarks for {{ username }}{% endblock %}

{% block content %}

{% include 'bookmark_list.html' %}

{% endblock %}

我们需要在bookmark_edit.js中增加两个方法:



  • bookmark_edit: 这个方法用于处理点击"edit连接的事件,他从服务器端获取书签编辑表单,然后用表单信息替换当前的书签。
  • bookmark_save: 这个方法处理表单提交事件,将表单数据发送到服务器端,并将页面上的表单替换为保存结果数据。

让我们先从第一个方法开始,打开site_media/bookmark_edit.js文件输入如下代码:



function bookmark_edit() {


var item = $(this).parent();

var url = item.find(".title").attr("href");

item.load("/save/?ajax&url=" + escape(url), null, function () {

$("#save-form").submit(bookmark_save);

});

return false;

}

因为这个方法用于处理用户单击"edit"链接的事件,所以这里的this关键字代表这个'edit'链接对象。把这个对象放入到jQuery的$()中然后调用parent()方法就可以获得这个链接对象的父辈元素,这里就是页面上的<li>元素,也就是书签内容(你可以在Firebug的控制台中自己试试看)。


在获得了书签的<li>元素之后,我们可以得到这个元素的title引用,并据此通过attr方法获得得到书签的URL。接下来我们通过load方法将编辑书签表单的代码嵌入到书签页面的HTML页面上。我们这次调用load()方法除了URL外比以前多了两个额外的参数,这两个额外的参数是:



  • 如果我们发送的是POST请求,这里会增加一个键/值对象,但这里我们用的是GET请求,所以这里我们使用一个null参数。
  • 另一个参数是一个JavaScrpit函数,它会在jQuery加载完URL之后调用。在这个函数里我们给书签表单的提交事件绑定一个bookmark_save函数(我们会在一会儿完成这个函数)。

最后这个方法返回false,它告诉浏览器什么都别做。


接下来我们要做的事把这个bookmark_edit方法绑定到edit链接的单击事件上。这里我们使用了$(document).ready()方法:



$(document).ready(function () {


$("ul.bookmarks .edit").click(bookmark_edit);

});

现在如果你单击edit链接后,你会看到页面上显示了一个修改书签的表单,但是你应该会在Firebug的控制台上看到JavaScript出错消息,因为bookmark_save方法还没有实现,现在让我们来实现这个方法:



function bookmark_save() {


var item = $(this).parent();

var data = {


url: item.find("#id_url").val(),

title: item.find("#id_title").val(),

tags: item.find("#id_tags").val()

};

$.post("/save/?ajax", data, function (result) {


if (result != "failure") {

item.before($("li", result).get(0));

item.remove();

$("ul.bookmarks .edit").click(bookmark_edit);

}

else {


alert("Failed to validate bookmark before saving.");

}

});

return false;

}

这里this代表的是页面中书签编辑的表单对象。这个方法的开始部分获得了编辑表单的父辈对象,这里还是书签的<li>元素。接下来通过ID属性获获得每个表单字段的值,然后通过$.post方法将数据发送到服务器,最后方法返回false以避免浏览器提交页面。


你一定猜到了$.post是一个jQuery方法,他向服务器发送POST请求,这个方法有三个参数:



  • 想服务器发送请求的URL。
  • 一个包含请求数据的 键/值 对象。
  • 一个JavaScript函数,这个函数在POST请求执行之后被调用。

值得一提的是jQuery提供了一个名为$get()的方法,这个方法用于向服务器发送请求,它的参数与$post方法的参数相同。


我们使用$post方法将更新后的数据发送给bookmake_save_page视图函数。我们前面曾经说过这个函数在书签保存成功后的书签HTML页面,否则它就返回一个"failure"字符串。因此,这里我们首先校验服务器端是否返回了"failure"字符串。如果请求成功我们通过before()方法在旧的书签前面插入一个新的,然后使用remove()方法将旧的书签数据从HTML文档中删除出去。如果是另一种情况,请求失败了就弹出一个消息对话框。


现在还剩下几个问题:为什么我们要在原来的书签之前插入一个$("li", result).get(0)元素而不是直接显示它呢?如果你看一下bookmark_save_page函数,你会发现它是使用bookmark_list.html模板来组织书签数据。在这个模板中bookmark_list.html包含书签数据的<li>元素嵌入在<ul>元素中。基本上$("li", result).get(0) 这段代码告诉jQuery获得这一组书签<li>元素中的第一个,而这个正是我们想要的。在这段代码中你可以看到jQuery使用$()函数可以通过设置第二个参数来获得HTML中的一个元素。


bookmark_submit被绑定到bookmark_edit函数中,所以我们无需在$(document).ready()中对其进行处理。


最后,当我们将修改后的书签数据加载到页面时,我们再次调用$("ul.bookmarks.edit").click(bookmark_edit)方法将bookmark_edit函数绑定到新增的edit链接上。如果不这样做的的话当你第二次点击这个链接的时候就会跳转到另一个页面。


完成这些代码后你可以测试一下,打开浏览器跳转到书签页面尝试编辑书签,你会发现修改的书签内容会立即显示在当前页面,页面没有刷新:



现在你已经结束了这一节,而且你应该明白了书签实时修改功能的实现方式,你还可以在其它很多场合下使用这种方式,比如你可以实现实时修改书签注释的功能,你只要在当前页面就可以修改注释而不必刷新页面跳转到另一个URL。


在下一节,我们开始实现第三个Ajax功能,帮助用户在提交书签的时候输入标签。


标签的自动补全


我们在本章要实现的最后一个Ajax增强功能是书签的自动补全功能。书签自动补全功能的概念来源于Google提供的“搜索建议”功能。Google的搜索建议功能是根据用户在搜索框中输入的文字,在搜索框下给出最接近搜索关键字的结果列表。很多代码开发集成环境中也提供了自动补全功能,会根据你键入的代码自动给出一列最接近你键入的代码的建议性列表。这一功能节省了用户的输入时间,用户只要输入几个字符就可以根据自动补全的结果列表选择想要输入的内容而不必输入整个标签。


我们也将在本章为标签实现自动补全功能,当用户编辑书签时输入标签系统给自动给出一个建议列表,我们不打算从头实现这个功能,我们准备使用jQuery的插件来实现这个功能。jQuery提供了一些列功能强大的插件。安装jQuery插件与安装jQuery没什么区别。你需要下载一个(或者多个)文件并把它们链接到你的模板文件中,然后通过JavaScript代码来调用这些插件。你可以在http://docs.jquery.com/Plugins上浏览jQuery的插件,然后查找auto-complete插件并下载它,或者直接从下面的链接下载这个插件:


http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/


我们将得到一个包含很多文件的压缩文件,将压缩文件内的文件解压缩到site_media目录下:



  • jquery.autocomplete.css
  • dimensions.js
  • jquery.bgiframe.min.js
  • jquery.autocomplete.js

为了在书签页面中实现自动补全功能,在site_media目录下创建一个空的tag_autocomplete.js文件。然后打开文件templates/bookmark_save.html并把刚才的js文件链接到其中:



{% extends "base.html" %}

{% block external %}

<link rel="stylesheet"

href="/site_media/jquery.autocomplete.css" type="text/css" />

<script type="text/javascript"

src="/site_media/dimensions.js"> </script>

<script type="text/javascript"

src="/site_media/jquery.bgiframe.min.js"> </script>

<script type="text/javascript"

src="/site_media/jquery.autocomplete.js"> </script>

<script type="text/javascript"

src="/site_media/tag_autocomplete.js"> </script>

{% endblock %}

{% block title %}Save Bookmark{% endblock %}

{% block head %}Save Bookmark{% endblock %}

[...]

现在我们完成了插件的安装,如果你读一下插件的文档你会发现可以通过在文本输入框上调用autocomplete()方法来激活这个插件。autocomplete()方法有以下参数:



  • 一个服务器端的URL。插件会将键入的字符以GET的方式发送到这个URL中,并且期待服务器端返回一个搜索结果建议列表。
  • 一个可以用于指定不同可选项的对象。其中之一是multiple变量,这是一个布尔变量,它告诉插件文本输入框可以输入多个值(记住,这里我们使用一个文本框输入多个标签),还有一个是multipleSeparator变量,它告诉插件输入文本框的内容什么用什么字符串进行分隔。在我们的应用中这个分割字符串是空格。

在激活插件之前,我们需要定义一个函数来接收用户的输入并返回建议结果,打开文件bookmarks/views.py并输入下面的内容:



def ajax_tag_autocomplete(request):


if request.GET.has_key('q'):


tags =


Tag.objects.filter(name__istartswith=request.GET['q'])[:10]

return HttpResponse('n'.join(tag.name for tag in tags))

return HttpResponse()

自动补全插件将用户输入的内容保存到GET变量'q'中并发送给服务器。因此我们首先校验是否有这样一个变量,然后根据变量的内容在标签中查找是否有意以此开头的书签,并将查找结果放入一个列表中。我们在这里使用了前面学过的filter和istartswith方法。这里我们只取了前十个搜索结果以避免过多的搜索建议结果把用户搞糊涂了,而且节省了宽带和性能开销。最后我们将搜索结果放在一个字符串中并用换行符分隔。然后将这个字符串放入HttpResponse中返回。


完成了视图函数后,需要在urls.py中给这个视图函数加一个URL入口:



urlpatterns = patterns('',



# Ajax

(r'^ajax/tag/autocomplete/$', ajax_tag_autocomplete),

)

现在我们在site_media/tag_autocomplete.js文件中加入如下代码以激活自动补全插件:



$(document).ready(function () {



$("#id_tags").autocomplete(



'/ajax/tag/autocomplete/',

{multiple: true, multipleSeparator: ' '}

);

});

以上代码给$(document).ready()传入一个匿名函数,这个函数在书签文本框上调用autocomplete()方法,并传入我们刚才讲过的参数。


只要这几行简单的代码就可以实现书签的自动补全功能。我们打开http://127.0.0.1:8000/save/来测试一下,在标签文本框中输入一两个字符,系统会从数据库中查询出建议结果并显示在输入文本框下面:




完成这个功能之后我们也该结束本章的内容了,我们在本章介绍了很多新的技术。读完本章之后你应该考虑在用户页面上实现很多其他增强功能,比如在用户页面上删除书签或者根据标签浏览书签等等。


下一章我们将介绍另一个主题,我们将允许用户都他们感兴趣的书签进行投票或者加上注释,我们将给页面加上更丰富的内容。



总结


喔,这真是很长的一章,不过我们也确实从中学到了很多东西。本章开始的部分我们学习了jQuery框架以及如何将之集成到Django框架中;之后我们在书签应用程序中实现了三个Ajax功能:在线实时搜索、在线实时编辑和书签自动补全功能。


下一章我们准备开始另一个主题,我们将允许用户在页面前端提交书签并对它们感兴趣的书签进行投票,我们还将允许用户给书签加上评论。所以跟着我继续学习吧。