在这一章,咱们将会给出几个关于tornado web应用如何使用数据库的例子。首先咱们将会经过一个简单的RESTful API的例子,结合以前的练习Burt’s Book网站,一步一步去建立一个具备完整功能的Burt‘s Book 网站。 咱们将会使用MongoDB做为数据库,pymongo做为python链接MongoDB的驱动。固然还有web应用还能够与许多数据库结合:Redis, CouchDB, MySQL是其中几个比较出名的选择。tornado自带的库中也封装了MySQL的驱动包。咱们之因此选择MongoDB,只是由于它很是简单与方便,很容易安装并集成到Python的代码中,MongoDB具有NoSQL的特性,让咱们能够直接使用原型不须要去预约义数据结构。
在这里咱们假设你有一台能够运行MongoDB的电脑,这样你就能够很轻松地经过这个示例代码去链接MongoDB服务,假如你不想将MongoDB安装到你的电脑中,或者没有合适的操做系统安装MongoDB,咱们建议你使用MongoHQ这个托管的MongoDB服务。 在第一个例子中,咱们假设你已经将MongoDB安装到你的电脑中,固然咱们也能够将使用MongoDB的代码调整成链接远程服务器(MongoHQ),这很是简单。 咱们还假设你拥有一些数据库方面的经验,虽然不必定是与MongoDB相关的,在这里咱们只须要了解MongoDB最基础的一些知识,你能够经过阅读MongoDB的文档获取更多信息。让咱们开始学习吧! javascript
基本的MongoDB操做 css
在咱们编写web应用程序以前,须要学习如何在python中使用MongoDB,在这一小节中,你将会学习到如何经过PyMongo去链接MongoDB,如何经过PyMongo去建立、查找、更新MongoDB中的数据。 PyMongo是一个很轻量的Python库,封装了不少MongoDB 的API,你能够在这里下载它们:http://api.mongodb.org/python/current/。一旦你完成安装,就能够在python的交互命令行中继续下面的内容。 html
创建链接 java
首先,在你建立MongoDB数据库链接前,你须要导入PyMongoDB库。 python
上面的代码将会链接到一个名为your_mongohq_db的主机上面,你须要输入你的用户名和密码,想要了解更多MongoDB 统一资源标识符(URI)的信息能够查看这里: git
http://www.mongodb.org/display/DOCS/Connections 一个MongoDB服务器能够同时运行多个数据库,它也容许链接对象去链接服务器上面任意一个数据库读取数据。你能够获得一个具备特定数据库或对象属性的链接,若是数据库不存在,它将会自动建立一个。 github
一个数据库能够有任意个集合,一个集合只须要存储到ige相关的文档中,大多数状况下,咱们执行MongoDB的操做(查找、保存、删除)都只针对其中的一组对象,你能够经过数据库对象中collection_names方法去获取列表的信息。 web
固然咱们尚未添加任何数据到数据库中,因此这个集合是空的。当咱们插入第一个文档时MongoDB会自动为咱们建立一个集合。你就能够获得一个返回的对象,这个对象表明一个集合,我么能够经过它访问数据库上面对应的集合,而后经过调用对象的insert方法指定一个pyhon字典。例如在下面的代码中调用widgets插入一个文件到集合中,由于它以前没有建立,因此MongoDB将会在添加文档时自动建立出来。 正则表达式
system.indexes将会为MongoDB建立一个内部使用的索引,在本章中,你能够忽略掉他。 在这个简单的例子中能够看到,你能够经过集合去访问一个数据库对象的属性,也能够经过集合名做为关键字访问字典的形式去访问数据库对象。例如,若是DB是一个pymongo数据库对象,db.widgets和db['widgets']都是等价的集合。 mongodb
处理文档
MongoDB 集合将数据作为文档进行存储,这是一种相对松散的数据结构,MongoDB是一种结构化的数据库,一般文档在同一个集合中拥有相同的结构,可是MongoDB中并不强制设定结构,在MongoDB内部使用一种叫作BSON,相似于二进制JSON数据流的格式存储文档。Pymongo 容许咱们像Python字典同样去写入和检索文档。 在集合中建立一个新的文档,须要用字典做为变量调用文档的一个insert方法:
如今这个文档在数据库中,咱们可使用集合对象的 find_one 方法去检索它,你能够看到使用 find_one 来匹配你在这个表达式中的变量,并经过文档的字段名做为字典的关键字去调用一个特定的文档。例如,返回的文档字段名等价于filbnip(刚建立的文档),咱们将会像下面这样调用 find_one 方法:
请注意 _id 字段,MongoDB会自动给全部新建的文档添加这个字段,它的变量值是一个 ObjectID, 一种用于保证文档惟一性的特殊类型: BSON 对象。你可能已经注意到这个 ObjectID 的变量是经过 insert 方法成功建立一个新的文档后返回的值(你能够在建立它的时候,经过放入一个 _id 关键字到文档的中,去重写自动建立的内容) 这个返回值是经过 find_one 从一个简单的 python 字典得到的。你能够像操做其余 python 字典函数同样,经过关联对它进行迭代遍历或修改数值。
不过,pymongo不会自动将字典修改的数值保存到数据库中,若是你想将字典改动的内容保存,须要调用集合保存的方法,将修改的字典做为参数传递:
让咱们添加一些新的文档到集合中:
咱们还能够经过调用集合的 find 方法,将集合中的全部文档列出来。下面是迭代的结果:
若是你只是想获取一个只有子集的文档,能够像使用 find_one 方法同样将字典参数传递给 find 方法。例如咱们想只查找 quantity 关键字等于4的子集:
最后,咱们可使用集合的 remove 方法将文档从集合中删除,这个 remove 方法像 find_one 方法同样接受一个字典参数,而后将指定的文档删除。例如,删除全部关键字等于 flipnip 的文档:
而后将集合中的全部文档打印出来,确认这个文档已经被成功移除:
MongoDB的文档和JSON 当咱们的 web 应用工做的时候,你会频繁地使用python的字典并将它做为JSON对象进行序列化(例如,响应一个AJAX的请求),由于PyMongo只能将MongoDB的文档按照字典操做,你也许须要经过 json 模块的 dumps 函数将它转化成JSON,不过这里有一个问题:
这个问题的缘由是,Python 的 json 模块不知道如何将MongoDB的特定值 ObjectID 类型转换成 JSON,这里有几种方法进行处理。 最简单的方法(也是咱们在本章将使用的方法)是在序列化以前,从文档中将 _id 这个关键字删除:
更复杂的解决方案是在PyMongo中导入 json_util 库。这样也能够帮助咱们将 MongoDB 中特定的类型转换成 JSON 序列。想要了解更多关于这个库的使用能够查看这里:
http://api.mongodb.org/python/current/api/bson/json_util.html
一个简单的持久化 web 服务
如今咱们掌握了经过 MongoDB 数据库访问数据的方法,这已经可以帮助咱们完成一个 web 服务了。首先咱们须要编写一个只从 MongoDB 中读取数据的 web service 服务。而后咱们将会编写一个可以读取和写入数据的。
只读字典
下面咱们将会建立一个简单的基础 web 应用——字典。 你须要接收请求中的特定单词,而后返回一个单词的定义,这里是典型的样例:
这个 web 服务将会从MongoDB中抓取数据,须要明确的是,咱们将要从文档中查看这些单词的属性,在咱们开始查看 web 应用程序的源代码以前,先让咱们将一些单词经过交互的命令行添加到数据库中。
查看例子4-1 web 应用——字典的源代码,他将会查找咱们刚才添加的单词,并将定义返回给客户端。 例子4-1 definitions_readonly.py
在命令行中经过这条命令运行这个程序:
如今咱们能够经过 curl 命令或浏览器向应用提交请求。
假如你提交的请求的单词没变添加到数据库中,咱们将会获得一个404错误的响应信息。
那么这个程序是如何工做的呢?让咱们从一些关键的代码行开始讨论。首先咱们须要在一开始将 pymongo 导入到咱们的程序中。而后咱们经过 tornado application 对象中的 __init__ 方法去初始化pymongo 的链接对象。 咱们建立一个应用的数据库属性对象,它会指向 MongoDB 中的实例数据库。下面是相关的代码:
一旦咱们将数据库属性添加到应用中,咱们就能够经过RequestHandler 对象的 self.application.db 去访问咱们的数据库。实际上,咱们要作的就是经过 WordHandler 中的 get 方法,去检索 pymongo 集合中的 word 集合,下面是这个代码的 get 方法:
咱们将会经过 find_one 方法去获取用户从 HTTP 请求路径中传过来的特定单词,而后关联到集合对象的变量中,若是咱们找到了这个单词,就能够将 _id 关键字从字典中删除 (这样就可使用 python 的json库将其序列化),而后将它传递给 RequestHandler 的 write 方法,write 方法将会把其转换成 JSON 序列。 若是 find_one 方法没有查找到匹配的对象, 它就会返回一个 None 。在这种状况下,咱们能够设置一个 404错误的状态响应和一个更短小的 JSON 将信息传递给用户:这个单词在指定的数据库中没有找到。
写入字典
查找字典中的单词确实很是有趣,可是它有一个麻烦的地方,若是要添加单词,必需要提早在交互的命令行中进行添加。咱们将会在下面的例子中让它能够经过HTTP给 web 服务提交建立和修改单词的请求。它是这样工做的: 发送一个 POST 请求将一个包括单词及其定义的请求,这个单词将会做为关键字进行查找,若是它不存在,就进行建立。例如,建立一个新的单词:
当建立了这个单词以后,咱们能够经过 get 请求进行验证:
咱们能够经过 POST 请求将一个单词的定义进行修改(和咱们定义新单词的参数同样)
看一下例子4-2,一个能够读取和写入的字典 web 应用服务。 例子4-2 definistions_readwrite.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import
tornado
.
httpserver
import
tornado
.
ioloop
import
tornado
.
options
import
tornado
.
web
import
pymongo
from
tornado
.
options
import
define
,
options
define
(
"port"
,
default
=
8000
,
help
=
"run on the given port"
,
type
=
int
)
class
Application
(
tornado
.
web
.
Application
)
:
def
__init__
(
self
)
:
handlers
=
[
(
r
"/(\w+)"
,
WordHandler
)
]
conn
=
pymongo
.
Connection
(
"localhost"
,
27017
)
self
.
db
=
conn
[
"definitions"
]
tornado
.
web
.
Application
.
__init__
(
self
,
handlers
,
debug
=
True
)
class
WordHandler
(
tornado
.
web
.
RequestHandler
)
:
def
get
(
self
,
word
)
:
coll
=
self
.
application
.
db
.
words
word_doc
=
coll
.
find_one
(
{
"word"
:
word
}
)
if
word_doc
:
del
word_doc
[
"_id"
]
self
.
write
(
word_doc
)
else
:
self
.
set_status
(
404
)
def
post
(
self
,
word
)
:
definition
=
self
.
get_argument
(
"definition"
)
coll
=
self
.
application
.
db
.
words
word_doc
=
coll
.
find_one
(
{
"word"
:
word
}
)
if
word_doc
:
word_doc
[
'definition'
]
=
definition
coll
.
save
(
word_doc
)
else
:
word_doc
=
{
'word'
:
word
,
'definition'
:
definition
}
coll
.
insert
(
word_doc
)
del
word_doc
[
"_id"
]
self
.
write
(
word_doc
)
if
__name__
==
"__main__"
:
tornado
.
options
.
parse_command_line
(
)
http_server
=
tornado
.
httpserver
.
HTTPServer
(
Application
(
)
)
http_server
.
listen
(
options
.
port
)
tornado
.
ioloop
.
IOLoop
.
instance
(
)
.
start
(
)
|
这个代码除了WordHnadler中附加的post方法,其它代码与只读服务彻底一致。让咱们看看这个post方法更多的细节:
1
2
3
4
5
6
7
8
9
10
11
12
|
def
post
(
self
,
word
)
:
definition
=
self
.
get_argument
(
"definition"
)
coll
=
self
.
application
.
db
.
words
word_doc
=
coll
.
find_one
(
{
"word"
:
word
}
)
if
word_doc
:
word_doc
[
'definition'
]
=
definition
coll
.
save
(
word_doc
)
else
:
word_doc
=
{
'word'
:
word
,
'definition'
:
definition
}
coll
.
insert
(
word_doc
)
del
word_doc
[
"_id"
]
self
.
write
(
word_doc
)
|
咱们作的第一件事情是使用 get_argument 方法去获取 definition 变量,这个变量是经过 POST 传过来的请求中定义的。而后和 get 方法同样,咱们尝试去加载数据库文档,使用 find_one 方法从数据库中查找 POST 入口获取到的 word 变量。若是查找到,咱们就使用 POST 入口中获取的 definition 变量去更新文档中的单词,而后调用 collection 对象的 save 方法将改动写入到数据库中。若是没有找到文档,咱们将会经过 insert 方法去建立一个新的并保存到数据库中。不论发生哪一种状况,在对数据库进行取值操做以后,咱们都应该将数据库文档的输出写入到响应中(请注意删除掉 _id )。
在第三章中,咱们以 Burt’s Books 做为例子,展现了如何使用 Tornado 的模板工具去建立一个复杂的web应用。在这一个段落,咱们将会向你展现一个使用 MongoDB 做为数据存储的 Burt’s Books 版本。(在你继续学习后面的内容以前,你应该去复习第三章的 Burt’s Books 例子)
让咱们经过这个简单的 Burt’s Books 例子开始吧,这是一个从数据库中获取书籍列表的版本。首先咱们须要在咱们的 MongoDB 服务器中建立一个数据库并将全部书籍信息聚集到 book 这个文档中,就像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
>>>
import
pymongo
>>>
conn
=
pymongo
.
Connection
(
)
>>>
db
=
conn
[
"bookstore"
]
>>>
db
.
books
.
insert
(
{
.
.
.
"title"
:
"Programming Collective Intelligence"
,
.
.
.
"subtitle"
:
"Building Smart Web 2.0 Applications"
,
.
.
.
"image"
:
"/static/images/collective_intelligence.gif"
,
.
.
.
"author"
:
"Toby Segaran"
,
.
.
.
"date_added"
:
1310248056
,
.
.
.
"date_released"
:
"August 2007"
,
.
.
.
"isbn"
:
"978-0-596-52932-1"
,
.
.
.
"description"
:
"<p>
[...]
</p>"
.
.
.
}
)
ObjectId
(
'4eb6f1a6136fc42171000000'
)
>>>
db
.
books
.
insert
(
{
.
.
.
"title"
:
"RESTful Web Services"
,
.
.
.
"subtitle"
:
"Web services for the real world"
,
.
.
.
"image"
:
"/static/images/restful_web_services.gif"
,
.
.
.
"author"
:
"Leonard Richardson, Sam Ruby"
,
.
.
.
"date_added"
:
1311148056
,
.
.
.
"date_released"
:
"May 2007"
,
.
.
.
"isbn"
:
"978-0-596-52926-0"
,
.
.
.
"description"
:"
.
.
.
}
)
ObjectId
(
'4eb6f1cb136fc42171000001'
)
|
(为了节省空间,咱们省略了更多书籍的详细信息),一旦咱们的数据库中有了这些文档,咱们就能够开始重构。例子4-3 展现了 Burt’s Books web应用的代码是怎么被修改的。 让咱们看看 burts_books_db.py 例子4-3 burts_books_db.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
import
os.path
import
tornado
.
auth
import
tornado
.
escape
import
tornado
.
httpserver
import
tornado
.
ioloop
import
tornado
.
options
import
tornado
.
web
from
tornado
.
options
import
define
,
options
import
pymongo
define
(
"port"
,
default
=
8000
,
help
=
"run on the given port"
,
type
=
int
)
class
Application
(
tornado
.
web
.
Application
)
:
def
__init__
(
self
)
:
handlers
=
[
(
r
"/"
,
MainHandler
)
,
(
r
"/recommended/"
,
RecommendedHandler
)
,
]
settings
=
dict
(
template_path
=
os.path
.
join
(
os.path
.
dirname
(
__file__
)
,
"templates"
)
,
static_path
=
os.path
.
join
(
os.path
.
dirname
(
__file__
)
,
"static"
)
,
ui_modules
=
{
"Book"
:
BookModule
}
,
debug
=
True
,
)
conn
=
pymongo
.
Connection
(
"localhost"
,
27017
)
self
.
db
=
conn
[
"bookstore"
]
tornado
.
web
.
Application
.
__init__
(
self
,
handlers
,
*
*
settings
)
class
MainHandler
(
tornado
.
web
.
RequestHandler
)
:
def
get
(
self
)
:
self
.
render
(
"index.html"
,
page_title
=
"Burt's Books | Home"
,
header_text
=
"Welcome to Burt's Books!"
,
)
class
RecommendedHandler
(
tornado
.
web
.
RequestHandler
)
:
def
get
(
self
)
:
coll
=
self
.
application
.
db
.
books
books
=
coll
.
find
(
)
self
.
render
(
"recommended.html"
,
page_title
=
"Burt's Books | Recommended Reading"
,
header_text
=
"Recommended Reading"
,
books
=
books
class
BookModule
(
tornado
.
web
.
UIModule
)
:
def
render
(
self
,
book
)
:
return
self
.
render_string
(
"modules/book.html"
,
book
=
book
,
)
def
css_files
(
self
)
:
return
"/static/css/recommended.css"
def
javascript_files
(
self
)
:
return
"/static/js/recommended.js"
if
__name__
==
"__main__"
:
tornado
.
options
.
parse_command_line
(
)
http_server
=
tornado
.
httpserver
.
HTTPServer
(
Application
(
)
)
http_server
.
listen
(
options
.
port
)
tornado
.
ioloop
.
IOLoop
.
instance
(
)
.
start
(
)
|
正如你看到的,这个程序与第三章中 Burt’s Books web应用的原始版本代码基本一致,只有两个地方不同.
第一:咱们添加了 db 这个属性到咱们的 Application 应用去连接 MongoDB 服务器:
1
2
|
conn
=
pymongo
.
Connection
(
"localhost"
,
27017
)
self
.
db
=
conn
[
"bookstore"
]
|
第二:咱们使用 connection 的 find 方法去获取数据库文档中的书籍列表信息,而且经过 RecommendedHandler 的 get 方法中的 rendering 把书籍列表的信息填充到 recommended.html 返回给用户。这些是相关的代码:
1
2
3
4
5
6
7
8
9
|
def
get
(
self
)
:
coll
=
self
.
application
.
db
.
books
books
=
coll
.
find
(
)
self
.
render
(
"recommended.html"
,
page_title
=
"Burt's Books | Recommended Reading"
,
header_text
=
"Recommended Reading"
,
books
=
books
)
|
在前面的代码中,能够看到书籍列表的全部信息均可以经过 get 方法查询到。然而由于咱们在 MongoDB 中添加的文档使用的是同一个域的字典,这个模板的代码没有任何修改数据的功能。 经过下面的命令,运行这个应用:
1
|
<
code
class
=
"shell"
>
$
python
burts
\
_books
\
_db
.
py
<
/
code
>
|
而后在浏览器中访问http://localhost:8000/recommended/。你会发现这个页面的内容与第三章那一个版本的 Burt’s Books 图3-6的内容彻底一致。
接下来须要在数据库中建立接口用于添加或修改书籍信息,要实现这个功能,咱们须要建立一个给用户填写书籍信息的表单,这个表单由一个 handler 服务接收,而且由 handler 将表单中的内容所有写入到数据库中。 Burt’s Books 这个版本与上一个版本的代码基本一致,额外添加了上面讨论的功能,这个关联的程序是 burts_books_rwdb.py。你能够按照书中这种不断完善代码的方式去构建应用。
这是 BookEditHandler 的代码,它实现了两个功能:
1. 经过 handler 的 get 请求返回给用户一个 html 表单(在模板 book_edit.html),它可能包含已经存在的书籍信息。
2. 经过 handler 的 post 请求获取表单中提交的数据,根据数据库的支持,对数据库中已有的数据进行更新或者添加新记录操做。
下面是这个 handler 的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class
BookEditHandler
(
tornado
.
web
.
RequestHandler
)
:
def
get
(
self
,
isbn
=
None
)
:
book
=
dict
(
)
if
isbn
:
coll
=
self
.
application
.
db
.
books
book
=
coll
.
find_one
(
{
"isbn"
:
isbn
}
)
self
.
render
(
"book_edit.html"
,
page_title
=
"Burt's Books"
,
header_text
=
"Edit book"
,
book
=
book
)
def
post
(
self
,
isbn
=
None
)
:
import
time
book_fields
=
[
'isbn'
,
'title'
,
'subtitle'
,
'image'
,
'author'
,
'date_released'
,
'description'
]
coll
=
self
.
application
.
db
.
books
book
=
dict
(
)
if
isbn
:
book
=
coll
.
find_one
(
{
"isbn"
:
isbn
}
)
for
key
in
book_fields
:
book
[
key
]
=
self
.
get_argument
(
key
,
None
)
if
isbn
:
coll
.
save
(
book
)
else
:
book
[
'date_added'
]
=
int
(
time
.
time
(
)
)
coll
.
insert
(
book
)
self
.
redirect
(
"/recommended/"
)
|
在讲解代码的细节以前,让咱们再讨论一下怎么样去设置 application 类的地址映射,这里是 application 的 init 方法中的实现细节:
1
2
3
4
5
6
7
8
|
<
code
=
"python"
>
handlers
=
[
(
r
"/"
,
MainHandler
)
,
(
r
"/recommended/"
,
RecommendedHandler
)
,
(
r
"/edit/([0-9Xx\-]+)"
,
BookEditHandler
)
,
(
r
"/add"
,
BookEditHandler
)
]
<
/
code
>
|
正如你看到的, BookEditHandler 有两个对应不一样地址映射变量的请求。一个是\/add, 在数据库不存在任何表单提交的信息时生效,因此你能够经过它去添加一个新的书籍信息。另一个 /edit/([0-9Xx-]+),根据书籍的ISBN信息,返回对应书籍的信息的表单。
来看看 BookEditHandler 中的 get 方法是如何工做的:
1
2
3
4
5
6
7
8
9
10
11
|
<
code
=
"python"
>
def
get
(
self
,
isbn
=
None
)
:
book
=
dict
(
)
if
isbn
:
coll
=
self
.
application
.
db
.
books
book
=
coll
.
find_one
(
{
"isbn"
:
isbn
}
)
self
.
render
(
"book_edit.html"
,
page_title
=
"Burt's Books"
,
header_text
=
"Edit book"
,
book
=
book
)
<
/
code
>
|
若是这个方法是由 /add 请求调用的, tornado 调用的 get 方法将不会包括第二个变量(请求路径没有匹配的正则表达式)。在这个例子中,默认状况下,将会把一个空的 book 字典 传给 book_edit.html 模板。
若是这个方法是由相似于 /edit/0-123-456这样的请求调用的。这个isbn变量将会被设置成 0-123-456。在这个例子中,咱们的应用将会从数据库中检索匹配这个 isbn 号的书籍信息,而且存储在 books 字典中,而后咱们会将这个 books 字典的请求传到 template 中返回给用户。
这里是相关的 template 模板(book_edit.html):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<
code
=
"html"
>
{
extends
"main.html"
%
}
{
%
autoescape
None
%
}
{
%
block
body
%
}
<
form
method
=
"POST"
>
ISBN
<
input
type
=
"text"
name
=
"isbn"
value
=
"{{ book.get('isbn', '') }}"
>
<
br
>
Title
<
input
type
=
"text"
name
=
"title"
value
=
"{{ book.get('title', '') }}"
>
<
br
>
Subtitle
<
input
type
=
"text"
name
=
"subtitle"
value
=
"{{ book.get('subtitle', '') }}"
>
<
br
>
Image
<
input
type
=
"text"
name
=
"image"
value
=
"{{ book.get('image', '') }}"
>
<
br
>
Author
<
input
type
=
"text"
name
=
"author"
value
=
"{{ book.get('author', '') }}"
>
<
br
>
Date
released
<
input
type
=
"text"
name
=
"date_released"
value
=
"{{ book.get('date_released', '') }}"
>
<
br
>
Description
<
br
>
<
textarea
name
=
"description"
rows
=
"5"
cols
=
"40"
>
{
%
raw
book
.
get
(
'description'
,
''
)
%
}
<
/
textarea
>
<
br
>
<
input
type
=
"submit"
value
=
"Save"
>
<
/
form
>
{
%
end
%
}
<
/
code
>
|
这是一个很是传统的 html 表单,咱们将会使用这个表单。将数据库中对应请求的 ISBN 对应的书籍信息整理到一块儿返回给用户,若是有的话。
若是 ISBN 值不存在,咱们也可使用 python 字典对象的 get 方法填充一个缺省值到 book 字典中,请记住,这个 book 字典的关键字将被设置为 ISBN 的值,这会让咱们在后续操做中将表单的数据录入到数据库的操做变得更加容易。
一样要注意,由于表单标签缺乏一个 action 属性, POST 会根据对应的URL地址将值准确地传递到咱们但愿的地址(若是这个表单在 /edit/0-123-456 被加载,POST 将会把咱们的请求送到 /edit/0-123-456;若是这个表单在 /add 被加载, POST 将会把咱们的请求送到 /add)。图4-1 展现了返回的页面信息。
让咱们来讨论一下 BookEditHandler 中 post 方法的细节,这个方法得到的变量来自于 book edit 表单。这里是相关的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<
code
=
"python"
>
def
post
(
self
,
isbn
=
None
)
:
import
time
book_fields
=
[
'isbn'
,
'title'
,
'subtitle'
,
'image'
,
'author'
,
'date_released'
,
'description'
]
coll
=
self
.
application
.
db
.
books
book
=
dict
(
)
if
isbn
:
book
=
coll
.
find_one
(
{
"isbn"
:
isbn
}
)
for
key
in
book_fields
:
book
[
key
]
=
self
.
get_argument
(
key
,
None
)
if
isbn
:
coll
.
save
(
book
)
else
:
book
[
'date_added'
]
=
int
(
time
.
time
(
)
)
coll
.
insert
(
book
)
self
.
redirect
(
"/recommended/"
)
<
/
code
>
|
和 get 方法同样, post 方法有两个职责,编辑已经存在的文档或添加新的文档。若是存在 isbn 变量(例如,请求的路径相似于 /edit/0-123-456) 咱们将会把这个 ISBN 关联的信息填充到返回给用户的编辑页面中,若是不存在关联的信息,咱们将会将返回给用户添加新文档的页面。

咱们从调用 book 中的空字典变量开始,若是咱们要编辑一个已经存在的书籍信息, book 会经过 find_one 方法查找到数据库中对应输入的 ISBN 信息,并将存在的信息加载到页面中。不论发生什么状况, 文档中应该存在 book_fields 列表的值。咱们遍历这个列表,使用 RequestHandler 对象中的 get_argument 方法从 POST 请求中获取对应的值。
在这里,若是数据库中存在 ISBN 对应的信息,咱们将会调用 save 方法将 book 文档的数据更新到数据库中。若是不存在,咱们将会调用 insert 方法将新数据插入到数据库中,请注意额外添加的是 date_added 键值。(这个键值在book添加到数据库以后产生的,咱们传入的请求中没有包括这条信息,它只是便于咱们对数据进行惟一识别,没有实际意义)当咱们完成以后,会使用 RequestHandler 类中的 redirect 方法将书籍信息的页面推送给用户,咱们进行的任何改动都会当即推送给用户。图4-2 展现了这个推送的页面包含的信息。
你应该还注意到,咱们在每一本书籍的页面入口添加了一个“edit”连接,点击连接将会返回每一本书籍对应的编辑页面,下面是改动后的 Book 模块的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<
code
=
"html"
>
<
div
class
=
"book"
style
=
"overflow: auto"
>
<
h3
class
=
"book_title"
>
{
{
book
[
"title"
]
}
}
<
/
h3
>
{
%
if
book
[
"subtitle"
]
!=
""
%
}
<
h4
class
=
"book_subtitle"
>
{
{
book
[
"subtitle"
]
}
}
<
/
h4
>
{
%
end
%
}
<
img
src
=
"{{ book["
image
"] }}"
class
=
"book_image"
/
>
<
div
class
=
"book_details"
>
<
div
class
=
"book_date_released"
>
Released
:
{
{
book
[
"date_released"
]
}
}
<
/
div
>
<
div
class
=
"book_date_added"
>
Added
:
{
{
locale
.
format_date
(
book
[
"date_added"
]
,
relative
=
False
)
}
}
<
/
div
>
<
h5
>
Description
:
<
/
h5
>
<
div
class
=
"book_body"
>
{
%
raw
book
[
"description"
]
%
}
<
/
div
>
<
p
>
<
a
href
=
"/edit/{{ book['isbn'] }}"
>
Edit
<
/
a
>
<
/
p
>
<
/
div
>
<
/
div
>
<
/
code
>
|
这里面最重要的一行是:
1
2
3
|
<
code
=
"html"
>
<
p
>
<
a
href
=
"/edit/{{ book['isbn'] }}"
>
Edit
<
/
a
>
<
/
p
>
<
/
code
>
|
这一行将每一本书籍对应的 isbn 键值 传送到生成的 /edit 超连接中,让咱们可以链接到对应的编辑页面中,若是想知道连接到编辑页面的效果,你能够查看图4-3的执行结果。
在这个章节中,咱们仅仅介绍了可以知足实现web应用的例子一些MongoDB接口,没有办法在这短短的篇幅覆盖全部 MongoDB 的功能。若是你想要学习更多 PyMongo 和 MongoDB 的知识, 这份PyMongo 手册 和 MongoDB 手册将会是很是好的入门文档。
若是你想要将更多 MongoDB 应用的功能应用到 tornado 平台中,你应该掌握 asyncmogo,一个与 PyMongo 相似的库,它可以更完美地支持异步的MongoDB 请求。咱们将会在第五章的异步请求中花费更多篇幅讨论它。
这一次翻译的时间跨度比较大,加上博主对mongdb特性还有诸多不了解的地方,翻译的内容未必可以将做者的原文完整翻译出来,本博文求校订,求指导,若有问题请私信博主。
原创翻译,首发:http://blog.xihuan.de/tech/web/tornado/tornado_database_mongodb.htm