将数据存储在可扩展的网络应用程序中可能会非常复杂。用户可在指定时间与任意数量的网络服务器进行交互,并且该用户的下一个请求可能会转到与处理上一个请求不同的网络服务器上。所有网络服务器都需要与同样分布在多个机器(可能位于世界各地的不同位置)上的数据进行交互。
有了 Google App Engine,您就不必再为上述任何问题担心了。App Engine 的基础架构通过一个简单的 API 就能处理好数据的所有分布、复制和负载平衡,同时您还可获得一个功能强大的查询引擎和事务处理。
App Engine 包括一个针对 Python 的数据建模 API。它类似于 Django 的数据建模 API,不同的是它在后台使用 App Engine 的可扩展数据库。
对于留言簿应用程序,我们希望存储用户发布的问候语。每个问候语都包括作者的姓名、消息内容以及消息发布的日期和时间,以便我们可以按时间顺序显示消息。
编辑 helloworld/helloworld.py,然后将以下 import 语句添加到顶部:
from google.appengine.ext import db
将以下类添加到 MainPage 类的正上方:
class Greeting(db.Model): author = db.UserProperty() content = db.StringProperty(multiline=True) date = db.DateTimeProperty(auto_now_add=True)
这定义了一个具有以下三个属性的 Greeting 模型:值为 User 对象的 author、值为字符串的 content 以及值为 datetime.datetime 的 date。
有些属性构造函数将使用参数进一步配置其行为。为 db.StringProperty 构造函数提供 multiline=True 参数表示该属性的值可以包含换行符。为 db.DateTimeProperty 构造函数提供 auto_now_add=True 参数会将模型配置为创建对象时自动为新对象提供 date(如果应用程序未以其他方式提供值)。有关属性类型及其选项的完整列表,请参阅数据库参考。
有了问候语的数据模型后,应用程序即可使用该模型创建新的 Greeting 对象并将这些对象放置在数据库中。编辑 Guestbook 处理程序以使其显示如下:
class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
此新的 Guestbook 处理程序将创建一个新的 Greeting 对象,然后使用用户所发布的数据设置该对象的 author 和 content 属性。它不会设置 date 属性,因此按照我们所配置的模型的行为,date 将自动设置为 [现在]。
最后,greeting.put() 会将新对象保存到数据库中。如果我们是通过查询获得的该对象,则 put() 将更新现有对象。使用 Model 构造函数创建该对象以后,put() 会将新对象添加到数据库中。
App Engine 数据库有一个用于数据模型的成熟查询引擎。App Engine 数据库不是传统的关系数据库,因此不使用 SQL 指定查询。您可以替代使用类似 SQL 的查询语言(称为 GQL)进行查询。GQL 使用一种熟悉的语法提供对 App Engine 数据库查询引擎功能的访问。
编辑 MainPage 处理程序以使其显示如下:
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
for greeting in greetings:
if greeting.author:
self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
else:
self.response.out.write('An anonymous person wrote:')
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))
# Write the submission form and the footer of the page
self.response.out.write("""
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")
在您的浏览器中重新加载 http://localhost:8080/,然后发布几条消息以验证消息是否可以正常发布。
在下面这一行上进行查询:
greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
也可以调用 Greeting 类中的 gql(...) 方法,并忽略查询中的 SELECT * FROM Greeting:
greetings = Greeting.gql("ORDER BY date DESC LIMIT 10")
使用 SQL 时,关键字(例如 SELECT)不区分大小写。但名称区分大小写。
由于查询返回的是全数据对象,因此从模型中选择特定属性没有任何意义。所有 GQL 查询均以 SELECT * FROM model 开头(或者由模型的 gql(...) 方法如此暗含),以接近其 SQL 等价表达式。
GQL 查询可以含有 WHERE 子句,以基于属性值按一个或多个条件过滤结果集。与 SQL 不同,GQL 查询不能含有值常量;而是对查询中的所有值都使用参数绑定。例如,要仅获得当前用户发布的问候语:
if users.get_current_user():
greetings = Greeting.gql("WHERE author = :1 ORDER BY date DESC",
users.get_current_user())
还可以使用命名参数而不是位置参数:
greetings = Greeting.gql("WHERE author = :author ORDER BY date DESC",
author=users.get_current_user())
除了 GQL 之外,数据库 API 还提供了使用方法构建查询对象的其他机制。上述查询还可以准备如下:
greetings = Greeting.all()
greetings.filter("author =", users.get_current_user())
greetings.order("-date")
有关 GQL 和查询 API 的完整描述,请参阅数据库参考。
开发网络服务器使用本地版本的数据库以通过临时文件来测试应用程序。只要临时文件存在,数据就存在,并且网络服务器不会重设这些文件,直到您要求重设。
如果希望开发服务器在启动前清除其数据库,请在启动该服务器时使用 --clear_datastore 选项:
dev_appserver.py --clear_datastore helloworld/
现在,我们有了一个工作留言簿应用程序,用于对使用 Google 帐户的用户进行身份验证、使用户可以提交消息以及显示其他用户留下的消息。App Engine 自动处理扩展,因此我们不需要随着我们的应用程序逐渐普及而重新访问该代码。
该最新版本将 HTML 内容与 MainPage 处理程序的代码混合起来。这使得更改应用程序的外观变得很困难,尤其是当应用程序变得更大且更复杂时。让我们使用模板来管理外观,并引入 CSS 样式表的静态文件。
继续转至使用模板。