本文介绍如何在 Google App Engine 上使用 Django 的表单验证框架。此框架可让您从数据模型构建 HTML 表单,还可在与数据存储区交互的同时无缝处理通过表单输入的信息。
Django 的表单验证框架包含在 Django 项目中。它使用数据库模型为应用程序构建高质量的 HTML 表单。此外,它还可以承担服务器端功能,对输入内容进行验证并将输入的数据存入数据存储区中。通过 Django 表单框架,您可轻松地将数据模型演变成一组网页,用于在数据存储区中进行数据插入和更新。数据存入数据存储区之后,您即可使用 GQL 查询访问此数据。
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 的表单验证框架可针对数据类型进行严格验证。但是,它并不提供对客户端输入的 Javascript 验证。因此,使用 Django 表单时,某些数据类型(如日期和列表)使用起来可能很不方便,因为用户必须按照规定的特定格式输入数据。