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

Google App Engine 上的 Django 表单验证框架

Alexander Power
2008 年 4 月

简介

本文介绍如何在 Google App Engine 上使用 Django 的表单验证框架。此框架可让您从数据模型构建 HTML 表单,还可在与数据存储区交互的同时无缝处理通过表单输入的信息。

什么是 Django 表单验证框架?

Django 的表单验证框架包含在 Django 项目中。它使用数据库模型为应用程序构建高质量的 HTML 表单。此外,它还可以承担服务器端功能,对输入内容进行验证并将输入的数据存入数据存储区中。通过 Django 表单框架,您可轻松地将数据模型演变成一组网页,用于在数据存储区中进行数据插入和更新。数据存入数据存储区之后,您即可使用 GQL 查询访问此数据。

Django 表单如何与数据存储区进行交互?

Google App Engine Model 类db.Model)与 Django 所用的 Model 类不同。因此,您无法在 Google App Engine 上直接使用 Django 表单框架。但是,Google App Engine 包含有一个名为 db.djangoforms 的模块,可扮演以 Google App Engine 和 Django 模型规范使用的两种数据存储区模型。在大多数情况下,您可以以使用 Django 框架的相同方式使用 db.djangoforms.ModelForm。

购物列表 - 示例

下面我们开发一个简单的应用程序,即使用 Django 表单创建可允许您在购物列表上添加和编辑物品的页面,然后将信息存储在我们的数据存储区中。最后,我们将使用 GQL 查询该数据,并将其显示在我们的应用程序中。

首先,我们指定应用程序的 Python 模块导入和 Google App Engine 导入。请注意,您必须先导入 google.appengine.webapp.template,然后才能导入 Django 模块:

import cgi
import wsgiref.handlers

from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

from google.appengine.ext.db import djangoforms

定义示例的模型和表单类

接下来,我们将购物列表条目的模型定义为 Google App Engine db.Model 类的子类。对于每个对象,我们记录物品的名称、所需数量、所希望支付的价格、条目创建日期以及创建人:

class Item(db.Model):
  name = db.StringProperty()
  quantity = db.IntegerProperty(default=1)
  target_price = db.FloatProperty()
  priority = db.StringProperty(default='Medium',choices=[
    'High', 'Medium', 'Low'])
  entry_time = db.DateTimeProperty(auto_now_add=True)
  added_by = db.UserProperty()

接下来,我们根据该模型创建表单对象。为此,我们创建一个继承自 djangofroms.ModelForm 的类,然后从该类创建一个名为 Meta 的子类,在其中指定模型以及表单中任何不需要的应排除字段。在本例中,由于我们将利用 Users API 获取向购物列表添加物品的人员,因此不需要在表单中包含 added_by 字段:

class ItemForm(djangoforms.ModelForm):
  class Meta:
    model = Item
    exclude = ['added_by']

另外,我们也不需要让用户指定输入时间。但是,由于生成表单时不会自动显示 auto_now 或 auto_now_add 设置为 true 的 DateTime 字段,因此,我们不需要将 entry_time 字段添加到排除项列表中。

定义请求处理程序

添加物品

现在,让我们来创建购物列表的请求处理程序。该表单将通过 HTTP GET 在根 URL 处进行访问,而用户将通过发布到同一 URL 的 HTTP POST 将其提交到应用程序。

Django 表单的好处之一就是具有自动表单验证功能。如果用户提交的数据无效,表单将产生错误并自动显示表单消息,要求用户更正输入错误。

定义 HTTP GET 请求方法:

class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>'
                            '<form method="POST" '
                            'action="/intl/zh-CN/">'
                            '<table>')
    # This generates our shopping list form and writes it in the response
    self.response.out.write(ItemForm())
    self.response.out.write('</table>'
                            '<input type="submit">'
                            '</form></body></html>')

在提供有默认值的情况下,表单将使用默认值进行预填充,否则将留为空白。此外,如果提供有固定的选项列表,则会创建下拉菜单。

接下来,我们编写一个方法来处理 HTTP POST 请求:

  def post(self):
    data = ItemForm(data=self.request.POST)
    if data.is_valid():
      # Save the data, and redirect to the view page
      entity = data.save(commit=False)
      entity.added_by = users.get_current_user()
      entity.put()
      self.redirect('/items.html')
    else:
      # Reprint the form
      self.response.out.write('<html><body>'
                              '<form method="POST" '
                              'action="/intl/zh-CN/">'
                              '<table>')
      self.response.out.write(data)
      self.response.out.write('</table>'
                              '<input type="submit">'
                              '</form></body></html>')

我们使用 data.is_valid() 检查用户输入中是否有错误。如果发现错误,则会重新输出表单,给出用户输入的信息和相应的错误消息。

如果表单输入有效,则 data.save 将生成数据存储区实体。由于希望将用户信息添加到该实体中,因此我们指定 commit=False。若无此标记,调用 save() 会将该实体直接保存至数据存储区。我们加入当前用户信息,然后调用 put() 方法将数据保存至数据存储区。

设置默认值是用于初始化数据存储区中实体的十分有效的方式。但是,我们无法通过这种方式对 users.get_current_user() 的当前用户进行初始化,因为该值在请求间被缓存了。有关详细信息,请参见属性页面。

显示列表

在将物品添加至购物列表后,我们需要在用户请求 URL /items.html 时向其显示这些内容。我们为该页创建另一个请求处理程序,使用 GQL 查询语言查询数据存储区。

class ItemPage(webapp.RequestHandler):
  def get(self):
    query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
    for item in query:
      self.response.out.write("%s - Need to buy %d, cost $%0.2f each<br>" %
                              (item.name, item.quantity, item.target_price))

编写 main() 函数

编写完所有处理程序类后,我们需要定义此程序的 main() 函数,以处理 CGI 请求:

def main():
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        ('/items.html', ItemPage),
                                        ],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__=="__main__":
  main()

此主函数会根据 URL 请求将各请求引导至相应的处理程序类。

编写 app.yaml 文件

在 Google App Engine 中,app.yaml 文件包含规范,其中规定了将由哪个脚本来处理收到的请求。我们的应用程序仅有一个脚本(即 form.py),因此所有请求都将由该脚本进行处理:

application: shoppinglist
version: 1
runtime: python
api_version: 1

handlers:
- url: .*
  script: form.py

编辑现有的实体

我们可以通过允许购物列表的用户编辑现有的购物列表条目,来扩展上述示例。

修改 ItemPage

首先,处理物品页面请求时,我们将在每件物品旁添加一个链接,供用户通过单击来编辑该条目。该链接将请求 /edit URL,并使用物品 ID 作为查询参数。

class ItemPage(webapp.RequestHandler):
  def get(self):
    query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
    for item in query:
      self.response.out.write('<a href="/edit?id=%d">Edit</a> - ' %
                              item.key().id())
      self.response.out.write("%s - Need to buy %d, cost $%0.2f each<br>" %
                              (item.name, item.quantity, item.target_price))

添加 EditPage

现在,我们必须添加 EditPage 类来处理编辑请求。此处理程序与用于添加物品的处理程序相似。

get() 方法中,对于 EditPage 类,我们首先从数据存储区中检索要编辑的实例,然后将该实例传至表单呈现器,以便填充该物品信息。此外,我们将物品 ID 作为隐藏输入添加到表单中,以便可以在用户提交编辑过的物品后访问该 ID 信息。

class EditPage(webapp.RequestHandler):
  def get(self):
    id = int(self.request.get('id'))
    item = Item.get(db.Key.from_path('Item', id))
    self.response.out.write('<html><body>'
                            '<form method="POST" '
                            'action="/edit">'
                            '<table>')
    self.response.out.write(ItemForm(instance=item))
    self.response.out.write('</table>'
                            '<input type="hidden" name="_id" value="%s">'
                            '<input type="submit">'
                            '</form></body></html>' % id)

接下来,我们编写处理 HTTP POST 的方法。我们查询数据存储区找到所编辑的物品,然后使用 Django 表单的验证框架对编辑的信息进行验证。与前面一样,如果信息有效,我们会将更改存入数据存储区,否则会让用户修改输入:

  def post(self):
    id = int(self.request.get('_id'))
    item = Item.get(db.Key.from_path('Item', id))
    data = ItemForm(data=self.request.POST, instance=item)
    if data.is_valid():
      # Save the data, and redirect to the view page
      entity = data.save(commit=False)
      entity.added_by = users.get_current_user()
      entity.put()
      self.redirect('/items.html')
    else:
      # Reprint the form
      self.response.out.write('<html><body>'
                              '<form method="POST" '
                              'action="/edit">'
                              '<table>')
      self.response.out.write(data)
      self.response.out.write('</table>'
                              '<input type="hidden" name="_id" value="%s">'
                              '<input type="submit">'
                              '</form></body></html>' % id)

修改 main() 函数

最后,我们必须将该新的 URL 和请求处理程序添加到主函数中:

def main():
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        ('/edit', EditPage),
                                        ('/items.html', ItemPage),
                                        ],
                                       debug=True)

  wsgiref.handlers.CGIHandler().run(application)

关于设置 Django 表单的其他注意事项

Django 的表单验证框架可针对数据类型进行严格验证。但是,它并不提供对客户端输入的 Javascript 验证。因此,使用 Django 表单时,某些数据类型(如日期和列表)使用起来可能很不方便,因为用户必须按照规定的特定格式输入数据。