用 TurboGears 和 Python 开发 Web 站点
dragon76
|
1#
dragon76 发表于 2007-07-30 13:53
用 TurboGears 和 Python 开发 Web 站点Python Web 框架,第 2 部分: 使用 TurboGears 和 Python 开发 Web 站点 使用一个在线销售程序来展示基础知识 文档选项 [url=javascript:document.email.submit();]将此页作为电子邮件发送[/url] 未显示需要 JavaScript 的文档选项 级别: 中级 Ian Maurer ( [email=ian@itmaurer.com?subject=使用 TurboGears 和 Python 开发 Web 站点]ian@itmaurer.com[/email] ), 资深顾问, Brulant, Inc. 2006 年 9 月 18 日 本 系列 文章一共有两篇,本文是第二篇。在本文中,我们将展示 TurboGears 的用法,它是另外一个基于 Python 的 MVC 风格的 Web 应用程序框架。在第一篇文章中我们介绍了 Django 框架,本文将介绍如何使用 TurboGears 来创建一个基于 Web 的应用程序,并将对 Turbogears 和 Django 进行比较。 TurboGears 开发人员称这个项目是一个 “大框架(megaframework)”,这是因为它是由现有的子项目构成的。TurboGears 可以帮助将很多主要组件集成在一起:
安装 TurboGears 使用 TurboGears 的第一个步骤是确保我们已经安装了 Python。TurboGears 的最新版本需要 Python 2.4。请参见 参考资料 中 Python 主页的链接。 Setuptools 是 Python 社区开发的一个新项目,它可以更方便地安装并更新 Python 软件。软件以归档文件的形式进行打包,称为 Egg,这与 Java™ 的 JAR 文件或 Ruby GEM 文件类似,可以使用一个名为 easy_install 的工具下载并安装。 TurboGears 是第一个使用 setuptools 工具来进行分发和安装的大型 Python 项目。请参见 参考资料 中 setuptools 页面的链接和 TurboGears 的下载页面。 本文使用了撰写本文时最新的开发版本(0.9a5),这可以通过 preview 的下载页面得到: easy_install -f http://www.turbogears.org/preview/download/index.html TurboGears 要获得最新版本的 TurboGears(目前是 0.8.9),我们可以简单地删除 -f 选项和 preview 站点的 URL: easy_install TurboGears 检查 TurboGears 管理工具 在安装 TurboGears 之后,我们就应该有了管理工具 tg-admin,它在您自己的目录中。我们可以在命令行中输入 tg-admin 查看它是否存在: 清单 1. 使用 TurboGears 管理工具 ~/dev$ tg-admin TurboGears 0.9a5 command line interface Usage: /usr/bin/tg-admin [command] [options] Available commands: i18n Manage i18n data info Show version info quickstart Create a new TurboGears project shell Start a Python prompt with your database available sql Run the SQLObject manager toolbox Launch the TurboGears Toolbox update Update an existing turbogears project 回页首 快速启动 要启动 TurboGears 项目,我们需要使用 tg-admin quickstart 功能。我们会被要求提供一个项目名和一个目录名。对于本文来说,我创建了一个简单的购物车,名为 TG Commerce,它有一个名为 tgcommerce 的包: 清单 2. 启动 TurboGears 项目 ~/dev$ tg-admin quickstart Enter project name: TG Commerce Enter package name [tgcommerce]: Selected and implied templates: turbogears#turbogears web framework Variables: package: tgcommerce project: TG-Commerce Creating template turbogears Creating ./TG-Commerce/ ... (output snipped) ... TurboGears 使用下面的 tgcommerce 包创建一个 TG-Commerce 项目。我们现在可以启动这个项目提供的测试服务器了: 清单 3. 启动测试服务器 ~/dev/TG-Commerce$ python start-tgcommerce.py ... (output snipped) ... 05/Mar/2006:11:31:54 HTTP INFO Serving HTTP on http://localhost:8080/ 查看所提供的 URL 的测试页面,然后使用 Ctrl-C 杀掉这个服务器。 回页首 创建一个模型 SQLObject 是对象关系映射器(ORM)库,它让我们可以开发数据库持久的 Python 对象。我们定义一个 Python 类,并添加所需要的属性(域),然后让 SQLObject 生成创建表、插入新记录以及查找、更新或删除现有记录时所需用到的 SQL 语句。 SQLObject 可以支持多种数据库,包括 MySQL、PostgreSQL、Firebird 等。在 参考资料 给出的链接中我们可以找到有关 SQLObject 的更多信息。 在这个例子中,我们使用 SQLite 作为后台的数据库。SQLite 是一个轻量级的数据库,它不需要进行任何配置,只是以简单文件的形式保存在磁盘上。要使用 SQLite,我们需要使用 setuptools 安装 pysqlite 库: easy_install pysqlite 要配置 TurboGears 数据库,我们需要在 dev.cfg 文件中指定 sqlobject.dburi。对于 SQLite 来说,我们要指定数据库文件所在的位置的路径: 清单 4. 开发配置文件(dev.cfg) sqlobject.dburi="notrans_sqlite:///path/to/devdir/TG-Commerce/tgcommerce.database" server.environment="development" autoreload.package="tgcommerce" TurboGears 快速启动使用样例代码创建并提前生成了一个 model.py 文件。这就是 SQLObject 类应该保存的地方。最上面一节设置数据库连接信息: 清单 5. 模型样例代码(model.py) from sqlobject import * from turbogears.database import PackageHub hub = PackageHub("tgcommerce") __connection__ = hub 接下来是模型类。每个类表示数据库中的一个表,它是使用一个映射为数据库列的类级属性定义的。这些属性是 SQLObject 列类型的实例,包括基本数据类型,例如 StringCol 和 CurrencyCol;以及关系类型,例如 ForeignKey 和 MultipleJoin。 对于这个购物车来说,有一个层次 Category 类和一个简单的 Product 类。目录层次是由父 ForeignKey 和子类 MultipleJoin 定义的。 清单 6. Category 和 Product 类(model.py,续) class Category(SQLObject): name = StringCol(length=64) parent = ForeignKey('Category', default=None) subcategories = MultipleJoin('Category', joinColumn='parent_id') products = MultipleJoin('Product') class Product(SQLObject): name = StringCol(length=64) sku = StringCol(length=64) price = CurrencyCol(notNone=True, default=0.0) category = ForeignKey('Category') 要验证这个模型,请使用 tg-admin sql sql 命令显示创建所需要的表使用的 SQL 代码。注意 SQLObject 会为每个表都创建一个 id 列。即使没有定义主键,这种操作也会发生。有关 SQLObject 的更多文档,请参见 参考资料 给出的链接。 清单 7. 使用 “tg-admin sql sql” 命令查看数据库模式 ~/dev/TG-Commerce$ tg-admin sql sql Using database URI sqlite:///home/ubuntu/dev/TG-Commerce/tgcommerce.db CREATE TABLE category ( id INTEGER PRIMARY KEY, name VARCHAR(64), parent_id INT ); CREATE TABLE product ( id INTEGER PRIMARY KEY, name VARCHAR(64), sku VARCHAR(64), price DECIMAL(10, 2) NOT NULL, category_id INT ); 下面是 Order 和 OrderItem 类,它们分别用来保存购物车和所购物品清单。由于 order 是一个 SQL 关键字,因此 Order 类的表名被使用 sqlmeta 子类的表属性重新定义为 orders: 清单 8. Order 和 OrderItem 类(model.py,续) class Order(SQLObject): items = MultipleJoin('OrderItem', joinColumn='order_id') class sqlmeta: table = 'orders' class OrderItem(SQLObject): quantity = IntCol(notNone=True) price = CurrencyCol(notNone=True) total = CurrencyCol(notNone=True) order = ForeignKey('Order') product = ForeignKey('Product') 使用 tg-admin sql create 命令创建数据库表: 清单 9. 使用 “tg-admin sql create” 命令创建数据库表 ~/dev$ tg-admin sql create Using database URI sqlite:///home/ubuntu/dev/TG-Commerce/tgcommerce.db 我们可以使用 tg-admin sql help 找到更多命令,包括删除表的命令。在使用这个命令时要特别小心,因为这会删除所有的表以及表中的数据。由于这个原因,在生产环境中应当锁定 tg-admin 命令。 回页首 使用 CatWalk 操纵模型 从 0.9 版本开始,TurboGears 提供了一组名为 Toolbox 的工具,其中包含了一个名为 CatWalk 的模型浏览器。CatWalk 是为那些希望使用一个 GUI 工具来快速创建、更新和删除模型中数据的开发人员设计的。 Toolbox 可以作为一个单独的服务器启动,它可以使用 tg-admin 命令来运行: 清单 10. 使用 tg-admin 启动 toolbox 服务器 ~/dev/TG-Commerce$ tg-admin toolbox ... (snip) ... 05/Mar/2006:15:01:33 HTTP INFO Serving HTTP on http://localhost:7654/ 如果浏览器没有自动打开这个地址,请手工输入 Toolbox 服务器所指定的 URL(http://localhost:7654/),并点击 CatWalk 链接打开 CatWalk。 图 1. CatWalk 工具 Toolbox 是面向开发人员的,而不是面向终端用户的,它最适合辅助完成数据模型化和为应用程序快速提供数据。我们可以使用 Ctrl-C 关闭 toolbox 服务器。在本文介绍中,我们将不会使用这个工具。 回页首 创建视图 在 TurboGears 中创建视图的默认方法是使用 Kid XML 模板化语言。由于 Kid 使用了 XML,因此所有的模板都必须很好地进行格式化,否则在呈现时就会抛出异常。Kid 还可以支持模板继承(template inheritance),这样所生成的新模板就可以对基本模板进行扩充,从而可以在一个地方创建并维护通用代码。 在 TurboGears 中,Kid 文件都位于 templates 目录中,扩展名为 .kid。默认情况下,有一个 master.kid 文件和一个 welcome.kid 文件,其中 master.kid 文件是基础模板文件,welcome.kid 在其 标记中使用 py:extends 属性对其进行了继承。 要创建一个新模板,我建议您对 welcome.kid 文件进行拷贝或重命名,并使用它作为起点开始下一步的工作。对于本例来说,我们首先创建的是分类模板,它会显示有关给定分类的以下信息:
清单 11. 分类页面 kid 模板文件(category.kid) Category: ${category.name} > > Subcategories Products 清单 11 展示了一些关键的 Kid 功能:
上面引用的 ancestors 属性要想有效,Category 模型类需要修改成包含一个 _get_ancestors 方法: 清单 12. 将 “ancestors” 属性的 “get” 方法添加到 Category 类中 class Category(SQLObject): name = StringCol(length=64) parent = ForeignKey('Category', default=None) subcategories = MultipleJoin('Category', joinColumn='parent_id') products = MultipleJoin('Product') def _get_ancestors(self): ancestor = self.parent while ancestor: yield ancestor ancestor = ancestor.parent 回页首 创建控制器 TurboGears quickstart 提供了一个具有 controllers.py 模块的项目,该模块是 Root 控制器类所在的位置。这是应用程序的主入口点,也是添加新控制器方法的地方。 下面是 TurboGears expose 中与分类 HTML 模板有关的两个样例控制器方法。控制器方法会返回字典,它们在对指定的 Kid 模板进行呈现时被用作名称空间或上下文。 清单 13. 控制器类 from turbogears import controllers, expose class Root(controllers.Root): @expose("tgcommerce.templates.category") def index(self): from model import Category category = Category.selectBy(parent=None)[0] return dict(category=category) @expose("tgcommerce.templates.category") def category(self, categoryID): from model import Category category = Category.get(categoryID) return dict(category=category) 在 TurboGears 中,URL 非常清楚地映射到方法上,它们都包含在 Root 控制器中。根 URL / 映射为一个名为 index 的特殊方法。通过向 Root 添加一个名为 category 的方法,它就可以通过 URL /category 进行访问了。任何提交的 URL 如果无法匹配给定方法,就会产生一个 404 错误,除非定义了一个 default 方法。 下面是一些可能的 URL 情况及其结果:
图 2 给出了分类显示的页面: 图 2. 分类显示 回页首 产品显示 对于产品显示页面来说,我们要创建一个 product 控制器,它会从数据库中检索出一个产品,并将其传递给 product Kid 模板进行呈现。 清单 14. 增加产品控制器方法 @expose("tgcommerce.templates.product") def product(self, productID): from model import Product product = Product.get(productID) return dict(product=product) product.kid 模板有一个产品信息表。此处要注意用来显示价格(有两位小数)的 Python 字符串的格式: 清单 15. 分类页 kid 模板文件(product.kid) Name: SKU: Price: $ 图 3 给出了产品显示页面: 图 3. 产品显示页面 回页首 错误处理 控制器方法尚未考虑的一件事是 SQLObject 的 get 方法所抛出的 SQLObjectNotFound 异常。清单 16 给出了捕获这个异常并将其作为 NotFound 异常重新抛出的方法,这会发送一个基本的 HTTP 404 错误: 清单 16. 向 Controller 类添加的错误处理 from model import Category, Product from sqlobject import SQLObjectNotFound from cherrypy import NotFound from turbogears import controllers, expose, url class Root(controllers.Root): @expose("tgcommerce.templates.category") def category(self, categoryID): try: category = Category.get(categoryID) except SQLObjectNotFound: raise NotFound() return dict(category=category) @expose("tgcommerce.templates.product") def product(self, productID): try: product = Product.get(productID) except SQLObjectNotFound: raise NotFound() return dict(product=product) 处理找不到对象的错误的另外一个策略不是发送 404 错误,而是对其进行重定向。这是使用 turbogears.redirect(...) 方法实现的: 清单 17. 重定向的例子 from turbogears import redirect try: object = ClassName.get(objectID) except SQLObjectNotFound: raise redirect("/path_to_redirect") 回页首 购物车 为了有一个可以正常工作的购物车,我们需要启用会话来维护请求之间的状态。会话是在配置文件中通过将 session_filter.on 值设置为 True 来启用的。 清单 18. 在配置文件中启用会话(dev.cfg) session_filter.on = True sqlobject.dburi="notrans_sqlite:///path/to/devdir/TG-Commerce/tgcommerce.database" server.environment="development" autoreload.package="tgcommerce" 要在每个页面上显示当前的购物车,我们可以将下面的 HTML 代码放到主模板文件中,而不用将其拷贝并粘贴到每个页面上: 清单 19. 主 kid 模板中包含的购物车(master.kid) ... Shopping Cart: items ($) 上面这段模板的变化本身会失败,因为在从控制器方法返回的字典中没有 cart 值。清单 20 给出了为了将购物车包含到会话中要对 products 方法所做的修改。 清单 20. 从控制器返回的购物车对象 @expose("tgcommerce.templates.product") def product(self, productID): try: product = Product.get(productID) except SQLObjectNotFound: raise NotFound() return self.results(product=product) def results(self, cart=None, **kw): if not cart: cart = self.get_cart() kw['cart'] = cart return kw def get_cart(self): cartID = session.get("cartID", None) cart = None if cartID: try: cart = Order.get(cartID) except SQLObjectNotFound: pass if cart is None: cart = Order() session["cartID"] = cart.id return cart results 方法会调用 get_cart,它会检索当前的 Order 对象,如果不存在或没有找到,就创建一个新对象。这些修改应该在 index 和 category 方法中都进行。 回页首 Ajax 添加购物车 要让这个购物车可以更好地工作,产品的 add-to-cart 函数需要调用 Ajax,这样不用对页面进行刷新。对于不了解 Ajax 的人来说,Ajax 就是一种 Web 实现模式,它代表 Asynchronous JavaScript + XML。最初对 Ajax 术语进行定义的文章请参见 参考资料 。 这个例子使用了 JavaScript Object Notation(JSON)作为传输格式,而没有使用 XML。JSON 是一种更为轻量级的技术,TurboGears 和 MochiKit 都可以很好地支持这项技术。尽管异步 JavaScript 和 JSON 技术的应用日益流行,但是它的广泛使用却使人们更多地记住了 Ajax 的名字,因为这样比较简单,而 Ajaj 作为一个缩写就没有这么容易记住了。 下面是控制器的 add_to_cart 方法,它会获得一个指定的产品,并将其添加到当前会话的购物车中。这个方法返回购物车对象,以及购物车中商品的总价和商品总数: 清单 21. add_to_cart 控制器方法 @expose() def add_to_cart(self, productID): cart = self.get_cart() product = Product.get(productID) cart.add_product(product, 1) return self.results(cart=cart, price=cart.total_price, quantity=cart.total_quantity) 注意 expose 方法没有提供一个模板名。这是因为这个方法不会从浏览器中直接调用,并在 HTML 中进行呈现。如果我们在浏览器或 curl 之类的工具中直接查看 add_to_cart 页面(http://localhost:8080/add_to_cart/1),就会看到原始的 JSON 数据: {"tg_flash":null, "price":24, "cart":{"id":24}, "quantity":1}。 客户机(浏览器)对服务器上 add_to_cart 控件方法的 JavaScript 调用非常简单。MochiKit 提供了一个名为 loadJSONDoc 的函数,它接收一个 URL,并返回一个称为 deferred 的对象。我们可以使用这个 deferred 对象来定义一个在异步调用返回响应时执行的回调函数。 清单 22. 购物车 AJAX 逻辑(cart.js) function addToCart(productID) { var deferred = loadJSONDoc('/add_to_cart/' + productID); deferred.addCallback(updateCart); } function updateCart(results) { $("cart-amt").innerHTML = numberFormatter("#.00")(results["price"]); $("cart-qty").innerHTML = results["quantity"]; } updateCart 函数是在返回之前调用的,JSON 值是在一个称为 results 的联合数组变量中提供的。这个函数使用 ID 和 innerHTML 属性来更新购物车中商品的数量。此外,MochiKit numberFormatter 函数用来指定价格的小数部分。 最后一个步骤是添加一个调用上面的 addToCart 函数的链接,这会传递当前的产品 ID。我们还需要将 MochiKit.js 和 cart.js 文件作为脚本标记添加到产品模板中: 清单 23. 将所添加的购物车 JavaScript 调用添加到产品页面中(product.kid) ... ... ... Add To Cart 回页首 结束语:TurboGears 和 Django 的比较 Django 和 TurboGears 都是 MVC 风格的框架,开发人员可以利用这些技术使用 Python 语言快速开发 Web 站点。为了选择最适合您的需求的技术,请考虑以下区别:
参考资料 学习
获得产品和技术
讨论
关于作者 Ian Maurer 是 Brulant, Inc. 的一名资深顾问,他擅长使用开源软件和 IBM WebSphere 技术为各个行业开发集成的电子商务解决方案,其中包括消费品和零售行业。Ian 居住在东北部的俄亥俄州,他是 Cleveland Area Python Interest Group 的成员。 |