数据库和 Zope
几乎任何创建严肃网站的人最终都会希望将其连接到关系数据库。关系数据库系统可能是 30 年前的技术,但它们灵活、安全且快速。使用数据库确保我们可以存储和检索我们的 Web 应用程序所需的数据,而无需创建我们自己的持久存储层。这减少了错误,提高了速度,并大大提高了安全性。
Zope,我们在过去几个月中讨论过的面向对象的 Web 应用程序服务器,包括一个名为 ZODB 的内置对象数据库。ZODB 既强大又易于使用;Zope 中的所有内容,包括 DTML 文档和文件夹,都作为对象存储在 ZODB 中。ZODB 支持事务等数据库概念,这意味着您可以使用它来存储重要数据,并确信在执行漫长而复杂的查询期间,没有人会修改信息。
但在许多情况下,ZODB 与我们想要存储和检索的数据并不匹配。在许多情况下,这是因为数据已经存在,我们只是想使用 Zope 来访问它。也许我们正在创建一个新的持久存储层,但希望人们能够从 Zope 外部访问它。也许我们的数据更适合关系数据库模型而不是对象数据库。最后,也许我们组织的 IT 部门要求所有信息都存储在关系数据库中。
由于所有这些原因和情况,标准的 Zope 安装定义了一个 ZSQL 方法对象。本月,我们将了解 ZSQL 方法以及 Zope 与关系数据库的总体集成。正如您将看到的,将一个简单的 Zope 站点变成一个在关系数据库中读取和写入数据的站点非常容易。
在我们可以使用数据库之前,我们必须首先连接到它。在 Zope 中,我们通过创建数据库连接对象来实现这一点。一个 Zope 站点可以包含任意数量的此类对象,每个对象都可以用来向数据库发送 SQL 查询。
Zope 附带一种数据库连接,允许您使用简单的 Gadfly 关系数据库。但是,虽然 Gadfly 非常适合演示 Zope 的数据库连接性,但在速度或功能方面,它无法与任何其他关系数据库相提并论。我建议完全跳过 Gadfly,为您打算连接的服务器安装数据库适配器。
我的办公室数据库服务器上运行着一个 PostgreSQL 服务器,所以我决定安装 psycopg 数据库适配器,这是 Internet 上目前可用的几个 PostgreSQL 适配器之一。(有关 psycopg 的更多信息,请参阅资源。)在安装这些(和其他)软件包时,请记住 Zope 通常带有它自己的 Python 副本,该副本独立于系统上可能安装的任何其他副本。这意味着您必须将 psycopg 安装到 Zope 定义的 Python 库中(使用 $ZOPE/bin/python),而不是 /usr/local/bin/python 或 /usr/bin/python。
在我们可以安装 psycopg 之前,我们必须首先安装由 eGenix 编写和分发的 mxDateTime 类。此软件包使处理超出当前 UNIX 限制(从 1970 年开始到 2038 年结束)的日期和时间成为可能,并提供了许多方便例程来处理各种格式的日期和时间。即使您不使用此模块,您仍然需要安装它才能使 psycopg 正确安装。您可以从 www.egenix.com/files/python/eGenix-mx-Extensions.html 下载 mxDateTime。
请注意,您将需要下载“base”扩展包(它是免费的),而不是商业扩展包。即使您使用的是 RPM 兼容的 Linux 发行版,您也不应该下载 mxDateTime 的 RPM。这是因为我们需要将库编译并安装到我们的 Zope Python 树中,而不是系统 Python 树中。
下载并解压缩 mxBase 软件包后,您应该能够通过切换到 mxBase 目录并键入以下内容来安装它
$ZOPE/bin/python setup.py install
这将编译 mx 模块并将其安装到您的 Python 安装中。
我们几乎准备好安装 psycopg 了,它是 Python 和 C 的组合,需要您安装 PostgreSQL 开发库。如果您使用 RPM 安装 PostgreSQL,那么您将需要适合您正在运行的 PostgreSQL 版本的 postgresql-devel RPM。这应该将文件安装到 /usr/local/pgsql 和 /usr/include/pgsql 中,尽管某些安装在这些路径中都使用 postgresql 而不是 pgsql。
现在从 initd.org/pub/software/psycopg 下载 psycopg 源代码。我检索了 1.0.4 版本,但似乎每隔几周就会出现新版本,因此请务必检索最新版本。为了解压缩和安装 psycopg,您需要使 makesetup shell 脚本(在 Zope 2.5b1 中安装到 $ZOPE/lib/python2.1/config 中,这是撰写本文时的最新版本)可执行
chmod 775 $ZOPE/lib/python2.1/config
要配置 psycopg,请更改到其源目录并输入以下内容
./configure --with-python=$ZOPE/bin/python --with-zope=$ZOPE --with-mxdatetime-includes=$ZOPE/lib/python2.1/ site-packages/mx/DateTime/mxDateTime --with-postgres-includes=/usr/include/pgsql您显然应该更改路径以反映您的安装,特别注意 Python 版本号(在我的例子中为 2.1)和 PostgreSQL 包含目录。
虽然我仍然确信有一种方法可以通过向 configure 传递另一个选项来避免这样做,但似乎您现在必须手动编辑 Makefile,以将新的标头目录添加到 CFLAGS 变量。在您喜欢的编辑器中打开 Makefile 并修改 CFLAGS 定义(在我版本中的第 90 行),以包含来自 $ZOPE/include/python2.1 的标头。因此,如果 $ZOPE 是 /usr/local/zope,您将向 CFLAGS 添加以下内容
-I/usr/local/zope/include/python2.1
保存 Makefile,然后使用以下命令安装 psycopg
make && make install && make install-zope这将在您的 $ZOPE 目录中为 Python 和 Zope 编译和安装 psycopg。
最后,将 psycopg 共享库 (psycopgmodule.so) 从 $ZOPE/lib/python2.1/site-packages 移动到 $ZOPE/lib/python2.1/lib-dynload/。
您可以通过重新启动 Zope 并在根目录中添加新产品来测试 psycopg。(不幸的是,重新启动 Zope 是告诉系统已安装新产品的唯一方法。)您要安装的产品在 Zope 管理屏幕右上角的“添加产品”菜单中称为 Z Psycopg 数据库连接。
每个数据库连接对象允许您连接到单个主机上的单个数据库,使用单个用户名和密码。这意味着,如果您已将信息分散在两个不同的数据库(或两个不同的数据库服务器)中,则需要两个连接对象。
当您从“添加产品”菜单中选择 Z Psycopg 数据库连接时,系统会要求您提供有关此数据库连接的一些基本详细信息。您必须输入 ID(在文件夹中必须是唯一的)和标题(将显示在管理屏幕中),以及数据库连接字符串。此连接字符串告诉 Zope 如何查找和连接到 PostgreSQL 服务器。在我的办公室里,atf 数据库位于“databases”上的 PostgreSQL 服务器上,我可以使用用户“reuven”连接到它,而无需密码。因此,我输入以下连接字符串
host=databases dbname=atf user=reuven
如果您愿意,可以将其余项目保留为默认状态。单击“添加”按钮,您将返回到添加新连接对象的文件夹。
单击连接对象会显示几个 Zope 选项卡,您可以使用这些选项卡来管理数据库连接。最有趣的四个是
状态:此选项卡告诉您数据库连接是否打开(即,是否已连接到您的 PostgreSQL 服务器)。它还允许您关闭连接。
属性:您可以修改您最初创建数据库连接对象时设置的属性。如果数据库移动到不同的服务器,或者如果您更改访问数据库所需的密码,这将特别有用。
测试:您可以通过向数据库发送任何 SQL 查询来测试数据库连接。当然,查询必须有效;如果您发送非法 SQL 或尝试寻址不存在的表,那么您将从 PostgreSQL 服务器收到相应的错误。例如,您可以输入 SELECT * FROM pg_database;。您可以通过此框输入任何 SQL,这在您没有直接 Telnet 或 SSH 连接时测试数据库非常方便。如果您输入 INSERT 或 UPDATE 查询,Zope 将指示查询未返回任何结果。与往常一样,最好避免使用 SELECT *,除非在简单的示例中,以避免对结果集中的列的顺序或名称感到惊讶。
浏览:您可以使用浏览选项卡浏览 PostgreSQL 数据库中的表,该选项卡显示表和字段的 Zope 树状列表。
现在我们有了数据库连接,我们可以创建一个或多个 ZSQL 方法。每个 ZSQL 方法都是一个 SQL 查询(如果需要,可以使用变量参数),它与连接一起工作。
让我们创建一个 ZSQL 方法,让我们将新名称添加到地址簿中。当然,这意味着我们必须首先在数据库中定义一个适当的表。我们可以通过将清单 1 的内容发送到 PostgreSQL 来创建表,可以在数据库连接的测试选项卡中或使用传统的 psql 命令行界面中进行。
如果您尝试在没有可用的数据库连接时添加 ZSQL 方法,Zope 将显示一条错误消息,抱怨它找不到任何合适的数据库连接。
Zope 内置的“获取”系统意味着 ZSQL 方法可以使用 Zope 层次结构中高于它的任何数据库连接。因此,用户可以为每种方法选择不同的数据库连接,从而可以在单个应用程序中集成来自不同数据库的信息,或者将您的网站从一种数据库品牌迁移到另一种数据库品牌。
要创建 ZSQL 方法,请转到您创建数据库连接的文件夹,然后从“添加产品”菜单中选择“ZSQL 方法”。系统将要求您输入几个项目:ID(必须唯一标识此文件夹中的对象)、标题(将在管理界面中可见)、参数(我们将在下一节中讨论)以及最终的 SQL 本身。您的 SQL 查询可以像您希望的那样简单或复杂,并且可以执行 INSERT、UPDATE 或 DELETE。
将 ZSQL 方法添加到系统后,单击它会打开许多 Zope 选项卡。其中一个选项卡标记为“测试”,(正如您可能期望的那样)它允许您测试查询。如果您的查询有参数,那么系统会要求您在 HTML 表单中输入它们。如果没有,系统只会要求您单击“提交查询”按钮。与数据库连接对象的“测试”选项卡一样,这将返回一个 HTML 表格,描述查询的结果,或者一条消息,指示查询未返回任何结果。
我们可以为我们希望执行的每个查询创建一个 ZSQL 方法。虽然这看起来可能有点奇怪,但这实际上是一个非常灵活和优雅的解决方案,我越来越欣赏它。如果我希望在一个 Web 应用程序中执行 20 个查询,我可以将每个查询放在一个单独的 ZSQL 方法中,然后从 DTML 页面中调用这些方法。
在 DTML 页面中,我们可以通过在 <dtml-in> 标记中命名 ZSQL 方法来检索结果。例如,我创建一个 ZSQL 方法来实现以下查询
SELECT first_name, last_name, phone_number, fax_number, cell_number FROM AddressBook ORDER BY last_name, first_name
如果我给这个 ZSQL 命名为“names-and-phone-numbers”,那么我可以使用清单 2 中的代码从 DTML 文档中调用它。仅需几行 DTML,我们就成功地生成了一个简单(但有用且灵活)的 ZSQL 方法。但它是如何工作的呢?
当 Zope 收到对这个 DTML 文档的请求时,它会解析 DTML 并执行其中包含的每个标记。<dtml-in> 循环构造期望一个序列作为参数;在这种特定情况下,该序列是从调用 names-and-phone-numbers 方法获得的结果。<dtml-in> 标记还为返回结果集中的每一列分配一个变量。这就是我们如何使用 <dtml-var first_name> 标记来打印用户的名字;Zope 自动将 first_name 列的值分配给一个名为 first_name 的变量。
为了避免打印不必要的和空白的行,我们使用 <dtml-if> 来检查我们是否没有从 PostgreSQL 收到 NULL 或空值。
显然,我们如何使用 ZSQL 方法和 DTML 来每次执行相同的查询。但是,如果我们想在每次运行时修改我们的基本查询,我们将需要定义一个或多个参数。
例如,如果我们想根据某人的姓氏(或其中的一部分,使用 SQL 正则表达式)检索有关某人的信息,我们将要定义以下类型的 ZSQL 方法
SELECT first_name, last_name, phone_number, fax_number, cell_number FROM AddressBook WHERE last_name LIKE ORDER BY last_name, first_name
在 DTML 中,我们可以用 <dtml-sqlvar> 标记替换 XXXXXX,它会自动为我们处理引号。我们必须命名我们正在使用的 SQL 变量,并指示其类型
SELECT first_name, last_name, phone_number, fax_number, cell_number FROM AddressBook WHERE last_name LIKE <dtml-sqlvar name_sqlregexp type="string"> ORDER BY last_name, first_name为了使上面的 ZSQL 方法工作,我们必须在创建方法时在相应的文本框中命名一个参数 (name_sqlregexp)。Zope 将获取该变量的值,将其放在我们的查询中并检索结果。
如果我们使用 <dtml-sqltest> 标记,我们可以将更多的负担放在 Zope 上,它的操作方式与 <dtml-sqlvar> 类似
SELECT first_name, last_name, phone_number, fax_number, cell_number FROM AddressBook WHERE <dtml-sqltest name_sqlregexp op="like" type="string"> ORDER BY last_name, first_name
如果我们将上面的查询存储在名为 select_by_last_name 的 ZSQL 方法中,那么 Zope 可以自动生成骨架 DTML 文档,允许用户输入搜索词并查看结果。为此,只需从“添加产品”列表中选择“Z 搜索界面”产品。您将能够从系统上的所有可搜索对象中进行选择,包括我们刚刚创建的 ZSQL 方法 (select_by_last_name)。选择它,并为报告指定一个 ID(我使用了 search_by_last_name)。还要为“输入 ID”指定一个名称,这是用于将输入发送到 search_by_last_name 的 HTML 表单的别称。(我将其命名为 search_by_last_name_form。)在现代版本的 Zope 中,您还必须指示您是否希望系统创建 DTML 方法或页面模板;我们想要前者。
单击“添加”会在当前文件夹中创建两个新的 DTML 方法,对应于您在表单中输入的名称。单击“输入 ID”URL 将显示一个简单的 HTML 表单,您可以在其中输入 SQL 正则表达式。单击提交按钮会将您的查询发送到 search_by_last_name DTML 方法,该方法将依次调用我们的 ZSQL 方法 (select_by_last_name),然后将我们的查询传递给 PostgreSQL。PostgreSQL 将结果返回给 select_by_last_name,select_by_last_name 将结果集返回给 search_by_last_name,然后 search_by_last_name 将结果显示在我们的 Web 浏览器中。
当然,您可以修改创建的 DTML 方法以匹配您自己网站的风格。您还可以将 Zope 自动创建的 DTML 复制到您自己的 DTML 页面中,将它们用作如何创建您自己的数据库查询的示例。
剩下的唯一主要任务是实现 INPUT 查询,它将项目添加到数据库中。幸运的是,这相当容易:我们创建一个 ZSQL 方法,将一行插入到数据库中。然后我们创建一个 DTML 文档,该文档将其 HTML 表单元素提交给另一个 DTML 文档。第二个文档调用 <dtml-call> 到我们的 ZSQL 方法。 瞧——我们的记录被插入到数据库中。
清单 3 显示了我们需要的 ZSQL 方法,我将其命名为 insert_address_data。现在我们将创建一个简单的 DTML 文档,其中将包含一个 HTML 表单(参见清单 4)。
清单 3. ZSQL 方法 insert_address_data
最后,我们创建 DTML 文档 insert_address,它接收来自 insert_address_form 的输入,并将参数传递给 ZSQL 方法 insert_address_data
<dtml-var standard_html_header> <h2><dtml-var title_or_id></h2> <dtml-try> <dtml-call insert_address_data> <dtml-except> <p>Sorry, but the INSERT didn't work.</p> <dtml-else> <p>Successfully inserted! </dtml-try> <dtml-var standard_html_footer>
用户现在可以使用指向 insert_address_form 的 HTML 表单将信息插入到我们的 PostgreSQL 表中,并且他们可以使用 search_by_last_name_form 检索它。令人印象深刻的是,我们可以在如此少的文件中完成如此多的工作,更令人印象深刻的是,为了使它工作,我们甚至不必接触文本编辑器一次,而所有这些都可以通过我们的 Web 浏览器来完成。
虽然它们并不完美,但我发现 ZSQL 方法是将 HTML 页面与底层数据库连接起来的一种优雅方式。ZSQL 是 Zope 展示其非常灵活、优雅的 Web 开发方法的另一种方式——尽管这种方法会让您多次挠头,然后一切才变得清晰和明显。已经了解 DTML 和 SQL 的人可以轻松地将数据库集成到他们的 Zope 应用程序中——使用 ZSQL 方法,可以将站点的工作分配给那些了解 SQL 的人(并处理 ZSQL 方法)和那些想要处理调用它们的 DTML 方法的人。
电子邮件: reuven@lerner.co.il
Reuven M. Lerner 是一位专门从事 Web/数据库应用程序和开源软件的顾问。他的著作《Core Perl》于 2002 年 1 月由 Prentice Hall 出版。Reuven 与他的妻子和女儿住在以色列的莫迪因。