<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                在本章中,我們將給出幾個使用數據庫的Tornado Web應用的例子。我們將從一個簡單的RESTful API例子起步,然后創建3.1.2節中的Burt's Book網站的完整功能版本。 本章中的例子使用MongoDB作為數據庫,并通過pymongo作為驅動來連接MongoDB。當然,還有很多數據庫系統可以用在Web應用中:Redis、CouchDB和MySQL都是一些知名的選擇,并且Tornado自帶處理MySQL請求的庫。我們選擇使用MongoDB是因為它的簡單性和便捷性:安裝簡單,并且能夠和Python代碼很好地融合。它結構自然,預定義數據結構不是必需的,很適合原型開發。 在本章中,我們假設你已經在機器上安裝了MongoDB,能夠運行示例代碼,不過也可以在遠程服務器上使用MongoDB,相關的代碼調整也很容易。如果你不想在你的機器上安裝MongoDB,或者沒有一個適合你操作系統的MongoDB版本,你也可以選擇一些MongoDB主機服務。我們推薦使用[MongoHQ](http://www.mongohq.com/)。在我們最初的例子中,假設你已經在你的機器上運行了MongoDB,但使用遠程服務器(包括MongoHQ)運行的MongoDB時,調整代碼也很簡單。 我們同樣還假設你已經有一些數據庫的經驗了,盡管并不一定是特定的MongoDB數據庫的經驗。當然,我們只會使用MongoDB的一點皮毛;如果想獲得更多信息請查閱MongoDB文檔([http://www.mongodb.org/display/DOCS/Home](http://www.mongodb.org/display/DOCS/Home))讓我們開始吧! [TOC=2,3] ## 4.1 使用PyMongo進行MongoDB基礎操作 在我們使用MongoDB編寫Web應用之前,我們需要了解如何在Python中使用MongoDB。在這一節,你將學會如何使用PyMongo連接MongoDB數據庫,然后學習如何使用pymongo在MongoDB集合中創建、取出和更新文檔。 PyMongo是一個簡單的包裝MongoDB客戶端API的Python庫。你可以在[http://api.mongodb.org/python/current/](http://api.mongodb.org/python/current/)下載獲得。一旦你安裝完成,打開一個Python解釋器,然后跟隨下面的步驟。 ### 4.1.1 創建連接 首先,你需要導入PyMongo庫,并創建一個到MongoDB數據庫的連接。 ~~~ >>> import pymongo >>> conn = pymongo.Connection("localhost", 27017) ~~~ 前面的代碼向我們展示了如何連接運行在你本地機器上默認端口(27017)上的MongoDB服務器。如果你正在使用一個遠程MongoDB服務器,替換localhost和27017為合適的值。你也可以使用MongoDB URI來連接MongoDB,就像下面這樣: ~~~ >>> conn = pymongo.Connection( ... "mongodb://user:password@staff.mongohq.com:10066/your_mongohq_db") ~~~ 前面的代碼將連接MongoHQ主機上的一個名為your_mongohq_db的數據庫,其中user為用戶名,password為密碼。你可以在[http://www.mongodb.org/display/DOCS/Connections](http://www.mongodb.org/display/DOCS/Connections)中了解更多關于MongoDB URI的信息。 一個MongoDB服務器可以包括任意數量的數據庫,而Connection對象可以讓你訪問你連接的服務器的任何一個數據庫。你可以通過對象屬性或像字典一樣使用對象來獲得代表一個特定數據庫的對象。如果數據庫不存在,則被自動建立。 ~~~ >>> db = conn.example or: db = conn['example'] ~~~ 一個數據庫可以擁有任意多個集合。一個集合就是放置一些相關文檔的地方。我們使用MongoDB執行的大部分操作(查找文檔、保存文檔、刪除文檔)都是在一個集合對象上執行的。你可以在數據庫對象上調用collection_names方法獲得數據庫中的集合列表。 ~~~ >>> db.collection_names() [] ~~~ 當然,我們還沒有在我們的數據庫中添加任何集合,所以這個列表是空的。當我們插入第一個文檔時,MongoDB會自動創建集合。你可以在數據庫對象上通過訪問集合名字的屬性來獲得代表集合的對象,然后調用對象的insert方法指定一個Python字典來插入文檔。比如,在下面的代碼中,我們在集合widgets中插入了一個文檔。因為widgets集合并不存在,MongoDB會在文檔被添加時自動創建。 ~~~ >>> widgets = db.widgets or: widgets = db['widgets'] (see below) >>> widgets.insert({"foo": "bar"}) ObjectId('4eada0b5136fc4aa41000000') >>> db.collection_names() [u'widgets', u'system.indexes'] ~~~ (system.indexes集合是MongoDB內部使用的。處于本章的目的,你可以忽略它。) 在之前展示的代碼中,你既可以使用數據庫對象的屬性訪問集合,也可以把數據庫對象看作一個字典然后把集合名稱作為鍵來訪問。比如,如果db是一個pymongo數據庫對象,那么db.widgets和db['widgets']同樣都可以訪問這個集合。 ### 4.1.2 處理文檔 MongoDB以文檔的形式存儲數據,這種形式有著相對自由的數據結構。MongoDB是一個"無模式"數據庫:同一個集合中的文檔通常擁有相同的結構,但是MongoDB中并不強制要求使用相同結構。在內部,MongoDB以一種稱為BSON的類似JSON的二進制形式存儲文檔。PyMongo允許我們以Python字典的形式寫和取出文檔。 為了在集合中 創建一個新的文檔,我們可以使用字典作為參數調用文檔的insert方法。 ~~~ >>> widgets.insert({"name": "flibnip", "description": "grade-A industrial flibnip", "quantity": 3}) ObjectId('4eada3a4136fc4aa41000001') ~~~ 既然文檔在數據庫中,我們可以使用集合對象的find_one方法來取出文檔。你可以通過傳遞一個鍵為文檔名、值為你想要匹配的表達式的字典來告訴find_one找到 一個特定的文檔。比如,我們想要返回文檔名域name的值等于flibnip的文檔(即,我們剛剛創建的文檔),可以像下面這樣調用find_oen方法: ~~~ >>> widgets.find_one({"name": "flibnip"}) {u'description': u'grade-A industrial flibnip', u'_id': ObjectId('4eada3a4136fc4aa41000001'), u'name': u'flibnip', u'quantity': 3} ~~~ 請注意_id域。當你創建任何文檔時,MongoDB都會自動添加這個域。它的值是一個ObjectID,一種保證文檔唯一的BSON對象。你可能已經注意到,當我們使用insert方法成功創建一個新的文檔時,這個ObjectID同樣被返回了。(當你創建文檔時,可以通過給_id鍵賦值來覆寫自動創建的ObjectID值。) find_one方法返回的值是一個簡單的Python字典。你可以從中訪問獨立的項,迭代它的鍵值對,或者就像使用其他Python字典那樣修改值。 ~~~ >>> doc = db.widgets.find_one({"name": "flibnip"}) >>> type(doc) <type 'dict'> >>> print doc['name'] flibnip >>> doc['quantity'] = 4 ~~~ 然而,字典的改變并不會自動保存到數據庫中。如果你希望把字典的改變保存,需要調用集合的save方法,并將修改后的字典作為參數進行傳遞: ~~~ >>> doc['quantity'] = 4 >>> db.widgets.save(doc) >>> db.widgets.find_one({"name": "flibnip"}) {u'_id': ObjectId('4eb12f37136fc4b59d000000'), u'description': u'grade-A industrial flibnip', u'quantity': 4, u'name': u'flibnip'} ~~~ 讓我們在集合中添加更多的文檔: ~~~ >>> widgets.insert({"name": "smorkeg", "description": "for external use only", "quantity": 4}) ObjectId('4eadaa5c136fc4aa41000002') >>> widgets.insert({"name": "clobbasker", "description": "properties available on request", "quantity": 2}) ObjectId('4eadad79136fc4aa41000003') ~~~ 我們可以通過調用集合的find方法來獲得集合中所有文檔的列表,然后迭代其結果: ~~~ >>> for doc in widgets.find(): ... print doc ... {u'_id': ObjectId('4eada0b5136fc4aa41000000'), u'foo': u'bar'} {u'description': u'grade-A industrial flibnip', u'_id': ObjectId('4eada3a4136fc4aa41000001'), u'name': u'flibnip', u'quantity': 4} {u'description': u'for external use only', u'_id': ObjectId('4eadaa5c136fc4aa41000002'), u'name': u'smorkeg', u'quantity': 4} {u'description': u'properties available on request', u'_id': ObjectId('4eadad79136fc4aa41000003'), u'name': u'clobbasker', u'quantity': 2} ~~~ 如果我們希望獲得文檔的一個子集,我們可以在find方法中傳遞一個字典參數,就像我們在find_one中那樣。比如,找到那些quantity鍵的值為4的集合: ~~~ >>> for doc in widgets.find({"quantity": 4}): ... print doc ... {u'description': u'grade-A industrial flibnip', u'_id': ObjectId('4eada3a4136fc4aa41000001'), u'name': u'flibnip', u'quantity': 4} {u'description': u'for external use only', u'_id': ObjectId('4eadaa5c136fc4aa41000002'), u'name': u'smorkeg', u'quantity': 4} ~~~ 最后,我們可以使用集合的remove方法從集合中刪除一個文檔。remove方法和find、find_one一樣,也可以使用一個字典參數來指定哪個文檔需要被刪除。比如,要刪除所有name鍵的值為flipnip的文檔,輸入: ~~~ >>> widgets.remove({"name": "flibnip"}) ~~~ 列出集合中的所有文檔來確認上面的文檔已經被刪除: ~~~ >>> for doc in widgets.find(): ... print doc ... {u'_id': ObjectId('4eada0b5136fc4aa41000000'), u'foo': u'bar'} {u'description': u'for external use only', u'_id': ObjectId('4eadaa5c136fc4aa41000002'), u'name': u'smorkeg', u'quantity': 4} {u'description': u'properties available on request', u'_id': ObjectId('4eadad79136fc4aa41000003'), u'name': u'clobbasker', u'quantity': 2} ~~~ ### 4.1.3 MongoDB文檔和JSON 使用Web應用時,你經常會想采用Python字典并將其序列化為一個JSON對象(比如,作為一個AJAX請求的響應)。由于你使用PyMongo從MongoDB中取出的文檔是一個簡單的字典,你可能會認為你可以使用json模塊的dumps函數就可以簡單地將其轉換為JSON。但,這還有一個障礙: ~~~ >>> doc = db.widgets.find_one({"name": "flibnip"}) >>> import json >>> json.dumps(doc) Traceback (most recent call last): File "<stdin>", line 1, in <module> [stack trace omitted] TypeError: ObjectId('4eb12f37136fc4b59d000000') is not JSON serializable ~~~ 這里的問題是Python的json模塊并不知道如何轉換MongoDB的ObjectID類型到JSON。有很多方法可以處理這個問題。其中最簡單的方法(也是我們在本章中采用的方法)是在我們序列化之前從字典里簡單地刪除_id鍵。 ~~~ >>> del doc["_id"] >>> json.dumps(doc) '{"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](http://api.mongodb.org/python/current/api/bson/json_util.html)了解更多關于這個庫的信息。 ## 4.2 一個簡單的持久化Web服務 現在我們知道編寫一個Web服務,可以訪問MongoDB數據庫中的數據。首先,我們要編寫一個只從MongoDB讀取數據的Web服務。然后,我們寫一個可以讀寫數據的服務。 ### 4.2.1 只讀字典 我們將要創建的應用是一個基于Web的簡單字典。你發送一個指定單詞的請求,然后返回這個單詞的定義。一個典型的交互看起來是下面這樣的: ~~~ $ curl http://localhost:8000/oarlock {definition: "A device attached to a rowboat to hold the oars in place", "word": "oarlock"} ~~~ 這個Web服務將從MongoDB數據庫中取得數據。具體來說,我們將根據word屬性查詢文檔。在我們查看Web應用本身的源碼之前,先讓我們從Python解釋器中向數據庫添加一些單詞。 ~~~ >>> import pymongo >>> conn = pymongo.Connection("localhost", 27017) >>> db = conn.example >>> db.words.insert({"word": "oarlock", "definition": "A device attached to a rowboat to hold the oars in place"}) ObjectId('4eb1d1f8136fc4be90000000') >>> db.words.insert({"word": "seminomadic", "definition": "Only partial ly nomadic"}) ObjectId('4eb1d356136fc4be90000001') >>> db.words.insert({"word": "perturb", "definition": "Bother, unsettle , modify"}) ObjectId('4eb1d39d136fc4be90000002') ~~~ 代碼清單4-1是我們這個詞典Web服務的源碼,在這個代碼中我們查詢剛才添加的單詞然后使用其定義作為響應。 代碼清單4-1 一個詞典Web服務:definitions_readonly.py ~~~ 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["example"] 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) self.write({"error": "word not found"}) if __name__ == "__main__": tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() ~~~ 在命令行中像下面這樣運行這個程序: ~~~ $ python definitions_readonly.py ~~~ 現在使用curl或者你的瀏覽器來向應用發送一個請求。 ~~~ $ curl http://localhost:8000/perturb {"definition": "Bother, unsettle, modify", "word": "perturb"} ~~~ 如果我們請求一個數據庫中沒有添加的單詞,會得到一個404錯誤以及一個錯誤信息: ~~~ $ curl http://localhost:8000/snorkle {"error": "word not found"} ~~~ 那么這個程序是如何工作的呢?讓我們看看這個程序的主線。開始,我們在程序的最上面導入了import pymongo庫。然后我們在我們的TornadoApplication對象的__init__方法中實例化了一個pymongo連接對象。我們在Application對象中創建了一個db屬性,指向MongoDB的example數據庫。下面是相關的代碼: ~~~ conn = pymongo.Connection("localhost", 27017) self.db = conn["example"] ~~~ 一旦我們在Application對象中添加了db屬性,我們就可以在任何RequestHandler對象中使用self.application.db訪問它。實際上,這正是我們為了取出pymongo的words集合對象而在WordHandler中get方法所做的事情。 ~~~ 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) self.write({"error": "word not found"}) ~~~ 在我們將集合對象指定給變量coll后,我們使用用戶在HTTP路徑中請求的單詞調用find_one方法。如果我們發現這個單詞,則從字典中刪除_id鍵(以便Python的json庫可以將其序列化),然后將其傳遞給RequestHandler的write方法。write方法將會自動序列化字典為JSON格式。 如果find_one方法沒有匹配任何對象,則返回None。在這種情況下,我們將響應狀態設置為404,并且寫一個簡短的JSON來提示用戶這個單詞在數據庫中沒有找到。 ### 4.2.2 寫字典 從字典里查詢單詞很有趣,但是在交互解釋器中添加單詞的過程卻很麻煩。我們例子的下一步是使HTTP請求網站服務時能夠創建和修改單詞。 它的工作流程是:發出一個特定單詞的POST請求,將根據請求中給出的定義修改已經存在的定義。如果這個單詞并不存在,則創建它。例如,創建一個新的單詞: ~~~ $ curl -d definition=a+leg+shirt http://localhost:8000/pants {"definition": "a leg shirt", "word": "pants"} ~~~ 我們可以使用一個GET請求來獲得已創建單詞的定義: ~~~ $ curl http://localhost:8000/pants {"definition": "a leg shirt", "word": "pants"} ~~~ 我們可以發出一個帶有一個單詞定義域的POST請求來修改一個已經存在的單詞(就和我們創建一個新單詞時使用的參數一樣): ~~~ $ curl -d definition=a+boat+wizard http://localhost:8000/oarlock {"definition": "a boat wizard", "word": "oarlock"} ~~~ 代碼清單4-2是我們的詞典Web服務的讀寫版本的源代碼。 代碼清單4-2 一個讀寫字典服務:definitions_readwrite.py ~~~ 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() ~~~ 除了在WordHandler中添加了一個post方法之外,這個源代碼和只讀服務的版本完全一樣。讓我們詳細看看這個方法吧: ~~~ 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方法取得POST請求中傳遞的definition參數。然后,就像在get方法一樣,我們嘗試使用find_one方法從數據庫中加載給定單詞的文檔。如果發現這個單詞的文檔,我們將definition條目的值設置為從POST參數中取得的值,然后調用集合對象的save方法將改變寫到數據庫中。如果沒有發現文檔,則創建一個新文檔,并使用insert方法將其保存到數據庫中。無論上述哪種情況,在數據庫操作執行之后,我們在響應中寫文檔(注意首先要刪掉_id屬性)。 ## 4.3 Burt's Books 在[第三章](http://dockerpool.com/static/books/introduction_to_tornado_cn/ch3.html)中,我們提出了Burt's Book作為使用Tornado模板工具構建復雜Web應用的例子。在本節中,我們將展示使用MongoDB作為數據存儲的Burt's Books示例版本呢。 ### 4.3.1 讀取書籍(從數據庫) 讓我們從一些簡單的版本開始:一個從數據庫中讀取書籍列表的Burt's Books。首先,我們需要在我們的MongoDB服務器上創建一個數據庫和一個集合,然后用書籍文檔填充它,就像下面這樣: ~~~ >>> 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":"<p>[...]>/p>" ... }) ObjectId('4eb6f1cb136fc42171000001') ~~~ (我們為了節省空間已經忽略了這些書籍的詳細描述。)一旦我們在數據庫中有了這些文檔,我們就準備好了。代碼清單4-3展示了Burt's Books Web應用修改版本的源代碼burts_books_db.py。 代碼清單4-3 讀取數據庫:burts_books_db.py ~~~ import os.path import tornado.locale 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() ~~~ 正如你看到的,這個程序和[第三章](http://dockerpool.com/static/books/introduction_to_tornado_cn/ch3.html)中Burt's Books Web應用的原始版本幾乎完全相同。它們之間只有兩個不同點。其一,我們在我們的Application中添加了一個db屬性來連接MongoDB服務器: ~~~ conn = pymongo.Connection("localhost", 27017) self.db = conn["bookstore"] ~~~ 其二,我們使用連接的find方法來從數據庫中取得書籍文檔的列表,然后在渲染recommended.html時將這個列表傳遞給RecommendedHandler的get方法。下面是相關的代碼: ~~~ 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中添加的文檔和原始的硬編碼字典擁有相同的域,所以我們之前寫的模板代碼并不需要修改。 像下面這樣運行應用: ~~~ $ python burts_books_db.py ~~~ 然后讓你的瀏覽器指向[http://localhost:8000/recommended/](http://localhost:8000/recommended/)。這次,頁面和硬編碼版本的Burt's Books看起來幾乎一樣(參見圖3-6)。 ### 4.3.2 編輯和添加書籍 我們的下一步是添加一個接口用來編輯已經存在于數據庫的書籍以及添加新書籍到數據庫中。為此,我們需要一個讓用戶填寫書籍信息的表單,一個服務表單的處理程序,以及一個處理表單結果并將其存入數據庫的處理函數。 這個版本的Burt's Books和之前給出的代碼幾乎是一樣的,只是增加了下面我們要討論的一些內容。你可以跟隨本書附帶的完整代碼閱讀下面部分,相關的程序名為burts_books_rwdb.py。 #### 4.3.2.1 渲染編輯表單 下面是BookEditHandler的源代碼,它完成了兩件事情: 1. GET請求渲染一個顯示已存在書籍數據的HTML表單(在模板book_edit.html中)。 2. POST請求從表單中取得數據,更新數據庫中已存在的書籍記錄或依賴提供的數據添加一個新的書籍。 下面是處理程序的源代碼: ~~~ 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__方法的相關代碼部分: ~~~ handlers = [ (r"/", MainHandler), (r"/recommended/", RecommendedHandler), (r"/edit/([0-9Xx\-]+)", BookEditHandler), (r"/add", BookEditHandler) ] ~~~ 正如你所看到的,BookEditHandler處理了兩個*不同*路徑模式的請求。其中一個是/add,提供不存在信息的編輯表單,因此你可以向數據庫中添加一本新的書籍;另一個/edit/([0-9Xx\-]+),根據書籍的ISBN渲染一個已存在書籍的表單。 #### 4.3.2.2 從數據庫中取出書籍信息 讓我們看看BookEditHandler的get方法是如何工作的: ~~~ 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) ~~~ 如果該方法作為到/add請求的結果被調用,Tornado將調用一個沒有第二個參數的get方法(因為路徑中沒有正則表達式的匹配組)。在這種情況下,默認將一個空的book字典傳遞給book_edit.html模板。 如果該方法作為到類似于/edit/0-123-456請求的結果被調用,那么isdb參數被設置為0-123-456。在這種情況下,我們從Application實例中取得books集合,并用它查詢ISBN匹配的書籍。然后我們傳遞結果book字典給模板。 下面是模板(book_edit.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 %} ~~~ 這是一個相當常規的HTML表單。如果請求處理函數傳進來了book字典,那么我們用它預填充帶有已存在書籍數據的表單;如果鍵不在字典中,我們使用Python字典對象的get方法為其提供默認值。記住input標簽的name屬性被設置為book字典的對應鍵;這使得與來自帶有我們期望放入數據庫數據的表單關聯變得簡單。 同樣還需要記住的是,因為form標簽沒有action屬性,因此表單的POST將會定向到當前URL,這正是我們想要的(即,如果頁面以/edit/0-123-456加載,POST請求將轉向/edit/0-123-456;如果頁面以/add加載,則POST將轉向/add)。圖4-1所示為該頁面渲染后的樣子。 ![圖4-1](https://box.kancloud.cn/2015-09-04_55e96dc2b9744.jpg) 圖4-1 Burt's Books:添加新書的表單 #### 4.3.2.3 保存到數據庫中 讓我們看看BookEditHandler的post方法。這個方法處理書籍編輯表單的請求。下面是源代碼: ~~~ 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/") ~~~ 和get方法一樣,post方法也有兩個任務:處理編輯已存在文檔的請求以及添加新文檔的請求。如果有isbn參數(即,路徑的請求類似于/edit/0-123-456),我們假定為編輯給定ISBN的文檔。如果這個參數沒有被提供,則假定為添加一個新文檔。 我們先設置一個空的字典變量book。如果我們正在編輯一個已存在的書籍,我們使用book集合的find_one方法從數據庫中加載和傳入的ISBN值對應的文檔。無論哪種情況,book_fields列表指定哪些域應該出現在書籍文檔中。我們迭代這個列表,使用RequestHandler對象的get_argument方法從POST請求中抓取對應的值。 此時,我們準備好更新數據庫了。如果我們有一個ISBN碼,那么我們調用集合的save方法來更新數據庫中的書籍文檔。如果沒有的話,我們調用集合的insert方法,此時要注意首先要為date_added鍵添加一個值。(我們沒有將其包含在我們的域列表中獲取傳入的請求,因為在圖書被添加到數據庫之后date_added值不應該再被改變。)當我們完成時,使用RequestHandler類的redirect方法給用戶返回推薦頁面。我們所做的任何改變可以立刻顯現。圖4-2所示為更新后的推薦頁面。 ![圖4-2](https://box.kancloud.cn/2015-09-04_55e96dc89bf34.jpg) 圖4-2 Burt's Books:帶有新添加書籍的推薦列表 你還將注意到我們給每個圖書條目添加了一個"Edit"鏈接,用于鏈接到列表中每個書籍的編輯表單。下面是修改后的圖書模塊的源代碼: ~~~ <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> ~~~ 其中最重要的一行是: ~~~ <p><a href="/edit/{{ book['isbn'] }}">Edit</a></p> ~~~ 編輯頁面的鏈接是把圖書的isbn鍵的值添加到字符串/edit/后面組成的。這個鏈接將會帶你進入這本圖書的編輯表單。你可以從圖4-3中看到結果。 ![圖4-3](https://box.kancloud.cn/2015-09-04_55e96dc917ced.jpg) 圖4-3 Burt's Books:帶有編輯鏈接的推薦列表 ## 4.4 MongoDB:下一步 我們在這里只覆蓋了MongoDB的一些基礎知識--僅僅夠實現本章中的示例Web應用。如果你對于學習更多更用的PyMongo和MongoDB知識感興趣的話,PyMongo教程([http://api.mongodb.org/python/2.0.1/tutorial.html](http://api.mongodb.org/python/2.0.1/tutorial.html))和MongoDB教程([http://www.mongodb.org/display/DOCS/Tutorial](http://www.mongodb.org/display/DOCS/Tutorial))是不錯的起點。 如果你對使用Tornado創建在擴展性方面表現更好的MongoDB應用感興趣的話,你可以自學asyncmongo([https://github.com/bitly/asyncmongo](https://github.com/bitly/asyncmongo)),這是一種異步執行MongoDB請求的類似PyMongo的庫。我們將在[第5章](http://dockerpool.com/static/books/introduction_to_tornado_cn/ch5.html)中討論什么是異步請求,以及為什么它在Web應用中擴展性更好。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看