翻譯:introduce to tornado - Databases

在這一章,咱們將會給出幾個關於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

Code    View Copy Print
  1. >>> import pymongo   
  2. >>> conn = pymongo.Connection(「localhost」, 27017)  

上面的代碼將會鏈接到一個名爲your_mongohq_db的主機上面,你須要輸入你的用戶名和密碼,想要了解更多MongoDB 統一資源標識符(URI)的信息能夠查看這裏: git

http://www.mongodb.org/display/DOCS/Connections 一個MongoDB服務器能夠同時運行多個數據庫,它也容許鏈接對象去鏈接服務器上面任意一個數據庫讀取數據。你能夠獲得一個具備特定數據庫或對象屬性的鏈接,若是數據庫不存在,它將會自動建立一個。 github

Code    View Copy Print
  1. >>> db = conn.example or: db = conn['example']  

一個數據庫能夠有任意個集合,一個集合只須要存儲到ige相關的文檔中,大多數狀況下,咱們執行MongoDB的操做(查找、保存、刪除)都只針對其中的一組對象,你能夠經過數據庫對象中collection_names方法去獲取列表的信息。 web

  1. >>> db.collection_names()   
  2. []  

固然咱們尚未添加任何數據到數據庫中,因此這個集合是空的。當咱們插入第一個文檔時MongoDB會自動爲咱們建立一個集合。你就能夠獲得一個返回的對象,這個對象表明一個集合,我麼能夠經過它訪問數據庫上面對應的集合,而後經過調用對象的insert方法指定一個pyhon字典。例如在下面的代碼中調用widgets插入一個文件到集合中,由於它以前沒有建立,因此MongoDB將會在添加文檔時自動建立出來。 正則表達式

Code    View Copy Print
  1. >>> widgets = db.widgets or: widgets = db['widgets'] (see below)   
  2. >>> widgets.insert({「foo」: 」bar」})   
  3. ObjectId(’4eada0b5136fc4aa41000000′)   
  4. >>> db.collection_names()   
  5. [u'widgets', u'system.indexes']  

system.indexes將會爲MongoDB建立一個內部使用的索引,在本章中,你能夠忽略掉他。 在這個簡單的例子中能夠看到,你能夠經過集合去訪問一個數據庫對象的屬性,也能夠經過集合名做爲關鍵字訪問字典的形式去訪問數據庫對象。例如,若是DB是一個pymongo數據庫對象,db.widgets和db['widgets']都是等價的集合。 mongodb

處理文檔

 MongoDB 集合將數據作爲文檔進行存儲,這是一種相對鬆散的數據結構,MongoDB是一種結構化的數據庫,一般文檔在同一個集合中擁有相同的結構,可是MongoDB中並不強制設定結構,在MongoDB內部使用一種叫作BSON,相似於二進制JSON數據流的格式存儲文檔。Pymongo 容許咱們像Python字典同樣去寫入和檢索文檔。 在集合中建立一個新的文檔,須要用字典做爲變量調用文檔的一個insert方法:

Code    View Copy Print
  1. >>> widgets.insert({「name」: 」flibnip」, 」description」: 」grade-A industrial flibnip」, »   
  2. 「quantity」: 3})   
  3. ObjectId(’4eada3a4136fc4aa41000001′)  

如今這個文檔在數據庫中,咱們可使用集合對象的 find_one 方法去檢索它,你能夠看到使用 find_one 來匹配你在這個表達式中的變量,並經過文檔的字段名做爲字典的關鍵字去調用一個特定的文檔。例如,返回的文檔字段名等價於filbnip(剛建立的文檔),咱們將會像下面這樣調用 find_one 方法:

  1. >>> widgets.find_one({「name」: 」flibnip」})   
  2. {u’description’: u’grade-A industrial flibnip’,   
  3.  u’_id’: ObjectId(’4eada3a4136fc4aa41000001′),   
  4.  u’name’: u’flibnip’, u’quantity’: 3}  

請注意 _id 字段,MongoDB會自動給全部新建的文檔添加這個字段,它的變量值是一個 ObjectID, 一種用於保證文檔惟一性的特殊類型: BSON 對象。你可能已經注意到這個 ObjectID 的變量是經過 insert 方法成功建立一個新的文檔後返回的值(你能夠在建立它的時候,經過放入一個 _id 關鍵字到文檔的中,去重寫自動建立的內容) 這個返回值是經過 find_one 從一個簡單的 python 字典得到的。你能夠像操做其餘 python 字典函數同樣,經過關聯對它進行迭代遍歷或修改數值。

Code    View Copy Print
  1. >>> doc = db.widgets.find_one({「name」: 」flibnip」})   
  2. >>> type(doc)   
  3. <type ’dict‘>   
  4. >>> print doc['name']   
  5. flibnip   
  6. >>> doc['quantity'] = 4  

不過,pymongo不會自動將字典修改的數值保存到數據庫中,若是你想將字典改動的內容保存,須要調用集合保存的方法,將修改的字典做爲參數傳遞:

  1. >>> doc['quantity'] = 4   
  2. >>> db.widgets.save(doc)   
  3. >>> db.widgets.find_one({「name」: 」flibnip」})   
  4. {u’_id’: ObjectId(’4eb12f37136fc4b59d000000′),   
  5. u’description’: u’grade-A industrial flibnip’,   
  6. u’quantity’: 4, u’name’: u’flibnip’}  

讓咱們添加一些新的文檔到集合中:

Code    View Copy Print
  1. >>> widgets.insert({「name」: 」smorkeg」, 」description」: 」for external use only」, »   
  2. 「quantity」: 4})   
  3. ObjectId(’4eadaa5c136fc4aa41000002′)   
  4. >>> widgets.insert({「name」: 」clobbasker」, 」description」: »   
  5. 「properties available on request」, 」quantity」: 2})   
  6. ObjectId(’4eadad79136fc4aa41000003′)  

咱們還能夠經過調用集合的 find 方法,將集合中的全部文檔列出來。下面是迭代的結果:

Code    View Copy Print
  1. >>> for doc in widgets.find():   
  2. … print doc   
  3. …   
  4. {u’_id’: ObjectId(’4eada0b5136fc4aa41000000′), u’foo’: u’bar’}   
  5. {u’description’: u’grade-A industrial flibnip’,   
  6. u’_id’: ObjectId(’4eada3a4136fc4aa41000001′),   
  7. u’name’: u’flibnip’, u’quantity’: 4}   
  8. {u’description’: u’for external use only’,   
  9. u’_id’: ObjectId(’4eadaa5c136fc4aa41000002′),   
  10. u’name’: u’smorkeg’, u’quantity’: 4}   
  11. {u’description’: u’properties available on request’,   
  12. u’_id’: ObjectId(’4eadad79136fc4aa41000003′),   
  13. u’name’: u’clobbasker’,   
  14. u’quantity’: 2}  

若是你只是想獲取一個只有子集的文檔,能夠像使用 find_one 方法同樣將字典參數傳遞給 find 方法。例如咱們想只查找 quantity 關鍵字等於4的子集:

Code    View Copy Print
  1. >>> for doc in widgets.find({「quantity」: 4}):   
  2. … print doc   
  3. …   
  4. {u’description’: u’grade-A industrial flibnip’,   
  5. u’_id’: ObjectId(’4eada3a4136fc4aa41000001′),   
  6. u’name’: u’flibnip’, u’quantity’: 4}   
  7. {u’description’: u’for external use only’,   
  8. u’_id’: ObjectId(’4eadaa5c136fc4aa41000002′),   
  9. u’name’: u’smorkeg’,   
  10. u’quantity’: 4}  

最後,咱們可使用集合的 remove 方法將文檔從集合中刪除,這個 remove 方法像 find_one 方法同樣接受一個字典參數,而後將指定的文檔刪除。例如,刪除全部關鍵字等於 flipnip 的文檔:

Code    View Copy Print
  1. >>> widgets.remove({「name」: 」flibnip」})  

而後將集合中的全部文檔打印出來,確認這個文檔已經被成功移除:

Code    View Copy Print
  1. >>> for doc in widgets.find():   
  2. … print doc   
  3. …   
  4. {u’_id’: ObjectId(’4eada0b5136fc4aa41000000′),   
  5. u’foo’: u’bar’}   
  6. {u’description’: u’for external use only’,   
  7. u’_id’: ObjectId(’4eadaa5c136fc4aa41000002′),   
  8. u’name’: u’smorkeg’, u’quantity’: 4}   
  9. {u’description’: u’properties available on request’,   
  10. u’_id’: ObjectId(’4eadad79136fc4aa41000003′),   
  11. u’name’: u’clobbasker’,   
  12. u’quantity’: 2}  

MongoDB的文檔和JSON 當咱們的 web 應用工做的時候,你會頻繁地使用python的字典並將它做爲JSON對象進行序列化(例如,響應一個AJAX的請求),由於PyMongo只能將MongoDB的文檔按照字典操做,你也許須要經過 json 模塊的 dumps 函數將它轉化成JSON,不過這裏有一個問題:

Code    View Copy Print
  1. >>> doc = db.widgets.find_one({「name」: 」flibnip」})   
  2. >>> import json   
  3. >>> json.dumps(doc)   
  4. Traceback (most recent call last):   
  5. File 」「, line 1, in    
  6. [stack trace omitted]   
  7. TypeError: ObjectId(’4eb12f37136fc4b59d000000′) is not JSON serializable  

這個問題的緣由是,Python 的 json 模塊不知道如何將MongoDB的特定值 ObjectID 類型轉換成 JSON,這裏有幾種方法進行處理。 最簡單的方法(也是咱們在本章將使用的方法)是在序列化以前,從文檔中將 _id 這個關鍵字刪除:

Code    View Copy Print
  1. >>> del doc["_id"]   
  2. >>> json.dumps(doc)   
  3. ‘{「description」: 」grade-A industrial flibnip」, 」quantity」: 4, 」name」: 」flibnip」}’  

更復雜的解決方案是在PyMongo中導入 json_util 庫。這樣也能夠幫助咱們將 MongoDB 中特定的類型轉換成 JSON 序列。想要了解更多關於這個庫的使用能夠查看這裏:

http://api.mongodb.org/python/current/api/bson/json_util.html 


一個簡單的持久化 web 服務 

如今咱們掌握了經過 MongoDB 數據庫訪問數據的方法,這已經可以幫助咱們完成一個 web 服務了。首先咱們須要編寫一個只從 MongoDB 中讀取數據的 web service 服務。而後咱們將會編寫一個可以讀取和寫入數據的。 

只讀字典 

下面咱們將會建立一個簡單的基礎 web 應用——字典。 你須要接收請求中的特定單詞,而後返回一個單詞的定義,這裏是典型的樣例:

Code    View Copy Print
  1. $ curl http://localhost:8000/oarlock   
  2. {definition: 」A device attached to a rowboat to hold the oars in place」,   
  3. 「word」: 」oarlock」}  

這個 web 服務將會從MongoDB中抓取數據,須要明確的是,咱們將要從文檔中查看這些單詞的屬性,在咱們開始查看 web 應用程序的源代碼以前,先讓咱們將一些單詞經過交互的命令行添加到數據庫中。

Code    View Copy Print
  1. >>> import pymongo   
  2. >>> conn = pymongo.Connection(「localhost」, 27017)   
  3. >>> db = conn.example   
  4. >>> db.words.insert({「word」: 」oarlock」, 」definition」:»   
  5. 「A device attached to a rowboat to hold the oars in place」})   
  6. ObjectId(’4eb1d1f8136fc4be90000000′)   
  7. >>> db.words.insert({「word」: 」seminomadic」, 」definition」: 」Only partially nomadic」})   
  8. ObjectId(’4eb1d356136fc4be90000001′)   
  9. >>> db.words.insert({「word」: 」perturb」, 」definition」: 」Bother, unsettle, modify」})   
  10. ObjectId(’4eb1d39d136fc4be90000002′)  

查看例子4-1 web 應用——字典的源代碼,他將會查找咱們剛纔添加的單詞,並將定義返回給客戶端。 例子4-1 definitions_readonly.py

Code    View Copy Print
  1. import tornado.httpserver   
  2. import tornado.ioloop   
  3. import tornado.options   
  4. import tornado.web   
  5. import pymongo   
  6. from tornado.options import define, options   
  7. define(「port」, default=8000, help=」run on the given port」, type=int)   
  8.   
  9. class Application(tornado.web.Application):   
  10.     def __init__(self):   
  11.         handlers = [(r"/(\w+)", WordHandler)]   
  12.         conn = pymongo.Connection(「localhost」, 27017)   
  13.         self.db = conn["example"]   
  14.         tornado.web.Application.__init__(self, handlers, debug=True)   
  15.   
  16. class WordHandler(tornado.web.RequestHandler):   
  17.     def get(self, word):   
  18.     coll = self.application.db.words   
  19.     word_doc = coll.find_one({「word」: word})   
  20.     if word_doc:   
  21.         del word_doc["_id"]   
  22.         self.write(word_doc)   
  23.     else:   
  24.         self.set_status(404)   
  25.         self.write({「error」: 」word not found」})   
  26. if __name__ == 」__main__「:   
  27.     tornado.options.parse_command_line()   
  28.     http_server = tornado.httpserver.HTTPServer(Application())   
  29.     http_server.listen(options.port)   
  30.     tornado.ioloop.IOLoop.instance().start()  

在命令行中經過這條命令運行這個程序:

  1. $ python definitions_readonly.py  

如今咱們能夠經過 curl 命令或瀏覽器嚮應用提交請求。

  1. $ curl http://localhost:8000/perturb   
  2. {「definition」: 」Bother, unsettle, modify」, 」word」: 」perturb」}  

假如你提交的請求的單詞沒變添加到數據庫中,咱們將會獲得一個404錯誤的響應信息。

  1. $ curl http://localhost:8000/snorkle   
  2. {「error」: 」word not found」}  

那麼這個程序是如何工做的呢?讓咱們從一些關鍵的代碼行開始討論。首先咱們須要在一開始將 pymongo 導入到咱們的程序中。而後咱們經過 tornado application 對象中的 __init__ 方法去初始化pymongo 的鏈接對象。 咱們建立一個應用的數據庫屬性對象,它會指向 MongoDB 中的實例數據庫。下面是相關的代碼:

  1. conn = pymongo.Connection(「localhost」, 27017)   
  2. self.db = conn["example"]  

一旦咱們將數據庫屬性添加到應用中,咱們就能夠經過RequestHandler 對象的 self.application.db 去訪問咱們的數據庫。實際上,咱們要作的就是經過 WordHandler 中的 get 方法,去檢索 pymongo 集合中的 word 集合,下面是這個代碼的 get 方法:

  1. def get(self, word):   
  2.     coll = self.application.db.words   
  3.     word_doc = coll.find_one({「word」: word})   
  4.     if word_doc:   
  5.         del word_doc["_id"]   
  6.         self.write(word_doc)   
  7.     else:   
  8.         self.set_status(404)   
  9.         self.write({「error」: 」word not found」})  

咱們將會經過 find_one 方法去獲取用戶從 HTTP 請求路徑中傳過來的特定單詞,而後關聯到集合對象的變量中,若是咱們找到了這個單詞,就能夠將 _id 關鍵字從字典中刪除 (這樣就可使用 python 的json庫將其序列化),而後將它傳遞給 RequestHandler 的 write 方法,write 方法將會把其轉換成 JSON 序列。 若是 find_one 方法沒有查找到匹配的對象, 它就會返回一個 None 。在這種狀況下,咱們能夠設置一個 404錯誤的狀態響應和一個更短小的 JSON 將信息傳遞給用戶:這個單詞在指定的數據庫中沒有找到。

寫入字典 

查找字典中的單詞確實很是有趣,可是它有一個麻煩的地方,若是要添加單詞,必需要提早在交互的命令行中進行添加。咱們將會在下面的例子中讓它能夠經過HTTP給 web 服務提交建立和修改單詞的請求。它是這樣工做的: 發送一個 POST 請求將一個包括單詞及其定義的請求,這個單詞將會做爲關鍵字進行查找,若是它不存在,就進行建立。例如,建立一個新的單詞:

  1. $ curl -d definition=a+leg+shirt http://localhost:8000/pants   
  2. {「definition」: 」a leg shirt」, 」word」: 」pants」}  

當建立了這個單詞以後,咱們能夠經過 get 請求進行驗證:

  1. $ curl http://localhost:8000/pants   
  2. {「definition」: 」a leg shirt」, 」word」: 」pants」}  

咱們能夠經過 POST 請求將一個單詞的定義進行修改(和咱們定義新單詞的參數同樣)

  1. curl -d definition=a+boat+wizard http://localhost:8000/oarlock   
  2. {「definition」: 」a boat wizard」, 」word」: 」oarlock」}  

看一下例子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

在第三章中,咱們以 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 關聯的信息填充到返回給用戶的編輯頁面中,若是不存在關聯的信息,咱們將會將返回給用戶添加新文檔的頁面。

![圖4-1](form for adding a new book)

咱們從調用 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的執行結果。

圖4-2
圖4-3

MongoDB 下一步

在這個章節中,咱們僅僅介紹了可以知足實現web應用的例子一些MongoDB接口,沒有辦法在這短短的篇幅覆蓋全部 MongoDB 的功能。若是你想要學習更多 PyMongo 和 MongoDB 的知識, 這份PyMongo 手冊 和 MongoDB 手冊將會是很是好的入門文檔。
若是你想要將更多 MongoDB 應用的功能應用到 tornado 平臺中,你應該掌握 asyncmogo,一個與 PyMongo 相似的庫,它可以更完美地支持異步的MongoDB 請求。咱們將會在第五章的異步請求中花費更多篇幅討論它。


這一次翻譯的時間跨度比較大,加上博主對mongdb特性還有諸多不瞭解的地方,翻譯的內容未必可以將做者的原文完整翻譯出來,本博文求校訂,求指導,若有問題請私信博主。

原創翻譯,首發:http://blog.xihuan.de/tech/web/tornado/tornado_database_mongodb.htm

上一篇:翻譯:introduce to tornado - Extending Templates

下一篇:翻譯:introduce to tornado - Asynchronous Web Services

相關文章
相關標籤/搜索