- Python第三方庫開發應用實戰
- 張有菊編著
- 3062字
- 2020-06-24 18:12:45
1.4 數據庫操作
在下面的內容中,假設已經在計算機上安裝了MongoDB,并通過PyMongo作為驅動來連接MongoDB。本節將介紹在Tornado框架中實現數據庫操作的知識。
1.4.1 實現持久化Web服務
假設我們需要編寫一個只從MongoDB讀取數據的Web服務,然后編寫一個可以讀寫數據的服務。例如,將要創建的應用是一個基于Web的簡單字典,在發送一個指定單詞的請求后返回這個單詞的定義。一個典型的交互如下。
$ curl http://localhost:8000/oarlock {definition: "連接到一個設備", "word": "oarlock"}
這個Web服務將從MongoDB數據庫中取得數據。具體來說,將根據word屬性查詢文檔。在查看Web應用本身的源碼之前,先從Python解釋器向數據庫中添加一些單詞。例如,通過如下文件001.py,可以向MongoDB數據庫中添加指定的單詞。
源碼路徑:daima\1\1-4\001.py
import pymongo conn = pymongo.MongoClient("localhost", 27017) db = conn.example db.words.insert({"word": "oarlock", "definition": "A device attached to a rowboat to hold the oars in place"}) db.words.insert({"word": "seminomadic", "definition": "Only partially nomadic"}) db.words.insert({"word": "perturb", "definition": "Bother, unsettle, modify"})
通過如下命令開啟MongoDB服務:
mongod --dbpath "h:\data"
在上述命令中,“h:\data”是一個保存MongoDB數據庫數據的目錄,讀者可以隨意在本地計算機硬盤中創建,并且還可以自定義目錄名字。然后運行文件001.py,執行后會向MongoDB數據庫中添加指定的單詞。
為了驗證上述添加的單詞,我們編寫如下所示的文件definitions_readonly.py,在Tornado框架中實現對MongoDB數據庫的訪問。
源碼路徑:daima\1\1-4\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.MongoClient("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()
運行上述實例文件definitions_readonly.py,然后在瀏覽器中輸入“http://localhost:8000/perturb”后會顯示:
{"word": "perturb", "definition": "Bother, unsettle, modify"}
這說明在Tornado框架中實現了對MongoDB數據庫數據的訪問功能。如果在瀏覽器中請求一個數據庫中沒有添加的單詞,會得到一個404錯誤以及一條錯誤消息。
{"error": "word not found"}
那么,這個程序是如何工作的呢?讓我們看看這個程序的主線。開始,我們在程序的最上面導入了pymongo庫。然后在TornadoApplication對象的init方法中實例化了一個pymongo連接對象。我們在Application對象中創建了一個db屬性,指向MongoDB的example數據庫。下面是相關的代碼。
conn = pymongo.MongoClient ("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來提示用戶這個單詞在數據庫中沒有找到。
在上述實例中,雖然可以很簡單地在字典中查詢單詞,但是在交互解釋器中添加單詞的過程會非常麻煩。其實我們完全可以使HTTP在請求網站服務時創建和修改單詞:首先發出一個特定單詞的POST請求,然后根據請求中給出的定義修改已經存在的定義。如果這個單詞并不存在,則創建它。例如,通過如下過程創建一個新的單詞“pants”。
http://localhost:8000/pants {"definition": "a leg shirt", "word": "pants"}
下面的實例文件definitions_readwrite.py演示了實現一個可讀寫Web服務的過程。
源碼路徑:daima\1\1-4\definitions_readwrite.py
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.MongoClient("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()
在上述代碼中,使用get_argument()函數獲取了POST請求中傳遞的definition參數,然后使用find_one()函數從數據庫中加載給定單詞的文檔。如果發現這個單詞的文檔,將definition條目的值設置為從POST參數中取得的值,然后調用集合對象的save()函數將改變寫到數據庫中。如果沒有發現文檔則創建一個新的,并使用insert()函數將文檔保存到數據庫中。無論上述哪種情況,都要在數據庫操作執行之后在響應中寫文檔(注意,首先要刪掉_id屬性)。
1.4.2 圖書管理系統
接下來,通過一個圖書管理系統的實現過程,介紹在Tornado框架中使用MongoDB數據庫實現動態Web的過程。
源碼路徑:daima\1\1-4\BookManger
(1)在MongoDB服務器中創建一個數據庫和集合,并用圖書內容進行填充。例如,下面的演示過程。
>>> import pymongo >>> conn = pymongo.MongoClient () >>> db = conn["bookstore"] >>> db.books.insert({ ... "title":"Python開發從入門到精通", ... "subtitle": "Python", ... "image":"123.gif", ... "author": "浪潮", ... "date_added":20171231, ... "date_released": "August 2007", ... "isbn":"978-7-596-52932-1", ... "description":"<p>[...]</p>" ... }) ObjectId('4eb6f1a6136fc42171000000') >>> db.books.insert({ ... "title":"PHP從入門到精通", ... "subtitle": "Web服務", ... "image":"345.gif", ... "author": "學習PHP", ... "date_added":20171231, ... "date_released": "May 2007", ... "isbn":"978-7-534-52926-0", ... "description":"<p>[...]>/p>" ... }) ObjectId('4eb6f1cb136fc42171000001')
(2)編寫Python程序文件burts_books_db.py。首先在程序中添加一個db屬性來連接MongoDB服務器,然后使用連接的find()函數從數據庫中取得圖書文檔的列表,并在渲染recommended.html時將這個列表傳遞給RecommendedHandler的get方法。文件burts_books_db.py的具體實現代碼如下。
#!/usr/bin/env python 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=8001, help="請運行在給定的端口", 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.MongoClient("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 = "圖書管理| 主頁", header_text = "歡迎使用圖書管理系統!", ) class RecommendedHandler(tornado.web.RequestHandler): def get(self): coll = self.application.db.books books = coll.find() self.render( "recommended.html", page_title = "圖書系統 | 圖書信息", header_text = "圖書信息", 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 "css/recommended.css" def javascript_files(self): return "js/recommended.js" def main(): tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": main()
如果此時在瀏覽器中輸入http://localhost:8001/recommended/,會讀取并顯示數據庫中的圖書信息。執行效果如圖1-14所示。

圖1-14 執行效果
(3)編寫Python文件burts_books_rwdb.py實現圖書添加和修改兩個功能。具體實現代碼如下。
define("port", default=8001, help="請運行在給定的端口", type=int) class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", MainHandler), (r"/recommended/", RecommendedHandler), (r"/edit/([0-9Xx\-]+)", BookEditHandler), (r"/add", BookEditHandler) ] 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.MongoClient("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 = "圖書管理 | 主頁", header_text = "歡迎使用圖書管理系統!", ) 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/") 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 "css/recommended.css" def javascript_files(self): return "js/recommended.js" def main(): tornado.options.parse_command line() http server = tornado.httpserver.HTTPServer(Application()) http server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": main()
在上述代碼中,BookEditHandler主要完成如下兩個功能。
?GET請求渲染顯示一個已存在圖書數據的HTML表單(在模板book_edit.html中)。
?POST請求從表單中取得數據,更新數據庫中已存在的圖書記錄或根據提供的數據添加一本新的圖書。
BookEditHandler實現了兩個不同路徑模式的請求:其中一個是實現圖書添加功能的/add,用于提供不存在信息的編輯表單,因此你可以向數據庫中添加一本新的圖書;另一個是實現圖書修改功能的/edit/([0-9Xx-]+),用于根據圖書的ISBN參數修改已存在圖書的信息。
函數get()的功能是從數據庫中取出圖書信息,如果該函數作為/add請求的結果被調用,Tornado將調用一個沒有第二個參數的get方法(因為路徑中沒有正則表達式的匹配組)。在這種情況下,默認將一個空的book字典傳遞給book_edit.html模板。如果該方法作為/edit/0-123-456請求的結果被調用,那么isbn參數被設置為0-123-456。在這種情況下,我們從程序實例中取得books集合,并用它查詢isbn匹配的圖書,然后傳遞book字典給模板。
函數post()的功能是將表單中的數據保存到數據庫中,具體來說有兩個功能:處理修改已存在圖書信息的請求以及添加新圖書信息的請求。如果有isbn參數(即路徑的請求類似于/edit/0-123-4567),則編輯給定isbn。如果這個參數沒有提供,則添加新圖書信息。先設置一個空的字典變量book,如果正在編輯已存在的圖書信息,則使用book集合的find_one()函數從數據庫中加載和傳入與isbn值對應的文檔。無論是哪一種情況,book_fields列表都指定哪些域應該出現在圖書文檔中,再迭代這個列表,使用RequestHandler對象的get_argument方法從POST請求中抓取對應的值。
圖書修改功能的模板文件是book_edit.html。具體實現代碼如下所示。
{% extends "main.html" %} {% autoescape None %} {% block body %} <form method="POST"> ISBN <input type="text" name="isbn" value="{{ book.get('isbn', '') }}"><br> 書名 <input type="text" name="title" value="{{ book.get('title', '') }}"><br> 標題 <input type="text" name="subtitle" value="{{ book.get('subtitle', '') }}"><br> 圖片 <input type="text" name="image" value="{{ book.get('image', '') }}"><br> 作者 <input type="text" name="author" value="{{ book.get('author', '') }}"><br> 出版時間 <input type="text" name="date_released" value="{{ book.get('date_released', '') }}"><br> 內容簡介<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-4567進行加載,POST請求將轉向/edit/0-123-4567;如果頁面以/add進行加載,則POST將轉向/add。
添加新圖書的界面如圖1-15所示。

圖1-15 添加新圖書的界面
單擊圖1-14中的“編輯”鏈接后會彈出圖書修改界面,在此界面中顯示修改此圖書信息的界面。執行效果如圖1-16所示。

圖1-16 圖書修改界面
- OpenStack Cloud Computing Cookbook(Third Edition)
- Bootstrap Site Blueprints Volume II
- 簡單高效LATEX
- Visual C++實例精通
- Unity Virtual Reality Projects
- Getting Started with CreateJS
- x86匯編語言:從實模式到保護模式(第2版)
- Java Web開發技術教程
- Kali Linux Wireless Penetration Testing Beginner's Guide(Third Edition)
- Visual Basic程序設計(第三版)
- 大學計算機基礎實驗指導
- C++從入門到精通(第6版)
- Visual Basic程序設計實驗指導及考試指南
- Kotlin進階實戰
- ASP.NET開發寶典