My favorites | 中文(简体) | Sign in

使用数据存储区

将数据存储在可扩展的网络应用程序中可能会非常复杂。用户可在指定时间与任意数量的网络服务器进行交互,并且该用户的下一个请求可能会转到与处理上一个请求不同的网络服务器上。所有网络服务器都需要与同样分布在多个机器(可能位于世界各地的不同位置)上的数据进行交互。

有了 Google App Engine,您就不必再为上述任何问题担心了。App Engine 的基础架构通过一个简单的 API 就能处理好数据的所有分布、复制和负载平衡,同时您还可获得一个功能强大的查询引擎和事务处理。

使用数据存储区的完整示例

以下是新版本的 helloworld/helloworld.py,它将问候语存储在数据存储区中。本页的其余部分讨论了新的内容。

import cgi

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)

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>""")

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('/')

application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                      ('/sign', Guestbook)],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()

helloworld/helloworld.py 替换为如上,然后在浏览器中重新载入 http://localhost:8080/。发布一些消息以确认消息能正确地存储和显示。

存储提交的问候语

App Engine 包括一个针对 Python 的数据建模 API。它类似于 Django 的数据建模 API,不同的是它在后台使用 App Engine 的可扩展数据存储区。

对于留言簿应用程序,我们希望存储用户发布的问候语。每个问候语都包括作者的姓名、消息内容以及消息发布的日期和时间,以便我们可以按时间顺序显示消息。

要使用该数据建模 API,请导入 google.appengine.ext.db 模块:

from google.appengine.ext import db

下文定义了问候语的数据模型:

class Greeting(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)

这定义了一个具有以下三个属性的 Greeting 模型:值为 User 对象的 author、值为字符串的 content 以及值为 datetime.datetimedate

有些属性构造函数将使用参数进一步配置其行为。为 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 对象,然后使用用户所发布的数据设置该对象的 authorcontent 属性。它不会设置 date 属性,因此按照我们所配置的模型的行为,date 将自动设置为“现在”。

最后,greeting.put() 会将新对象保存到数据存储区中。如果我们是通过查询获得的该对象,则 put() 将更新现有对象。使用 Model 构造函数创建该对象以后,put() 会将新对象添加到数据存储区中。

使用 GQL 检索存储的问候语

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>""")

在下面这一行上进行查询:

    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 样式表的静态文件。

继续转至使用模板