結合使用 Oracle Database 11g 和 Python
本教程介紹如何結合使用 Python 和 Oracle Database 11g。
所需時間
概述
Python 是一種流行的通用動態腳本語言。隨着框架的興起,Python 也成為 Web 應用程序開發的常用工具。如果您希望結合使用 Python 和 Oracle 數據庫,本教程將通過一些示例幫助您快速入門。如果您是初次接觸 Python,請參閱 附錄:Python 入門,了解這種語言。
前提條件
. |
Oracle Database 11gR2,用戶名為“pythonhol”,口令(區分大小寫)為“welcome”。該模式中的示例表取自 Oracle 的 Human Resources(即“HR”)模式。 |
---|---|
. |
帶有 cx_Oracle 5.0.2 擴展的 Python 2.4。 |
. |
Django 1.1 框架。 |
. |
本教程使用的所有文件都位於您登錄的 /home/pythonhol 目錄中。 |
連接到 Oracle
. |
查看 $HOME 目錄的 connect.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') print con.version con.close() 為了提供用於訪問 Oracle 數據庫的 API,導入了 cx_Oracle 模塊。可以用這種方法在 Python 腳本中包括多個內置的和第三方模塊。 用戶名“pythonhol”、口令“welcome”和連接字符串傳遞給 connect() 方法。在本示例中,使用了 Oracle 的簡單連接 (Easy Connect) 連接字符串語法。該字符串由您計算機的 IP 和數據庫服務名稱“orcl”組成。 close() 方法關閉連接。腳本結束時,將自動釋放所有非顯式關閉的連接。 在命令行終端,運行: python connect.py 如果連接成功,則輸出版本號。如果連接失敗,則引發異常。
|
---|---|
. |
在 Python 中使用縮進指明代碼結構。與許多其他語言不同,沒有語句終止符,也不使用 begin/end 關鍵字或花括號指明代碼塊。 在編輯器中打開 connect.py。將輸出語句縮進兩個空格,保存該文件: import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') print con.version con.close() 運行該腳本: python connect.py 用於縮進的空格或 Tab 鍵的數量並不重要,只要每個代碼塊中保持一致即可。這個示例中,Python 解釋器認為 connect() 調用后面不應有新的代碼塊級,因此對存在的不同縮進發出警告。 在其他情況下,例如,在“if”和循環塊中(稍后介紹),需要注意使每個塊中的所有語句都具有相同的縮進。 如果您從本教程復制和粘貼代碼,請在運行每個示例前檢查粘貼的縮進是否正確。
|
. |
Python 將一切都當作對象來處理。“con”對象有一個“version”屬性,是一個字符串。 對該腳本進行更改,使其也使用“split”字符串方法: import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') ver = con.version.split(".") print ver con.close() 在命令行終端重新運行該腳本: python connect.py 輸出是一個“列表”,“列表”是 Python 使用的一種數組的名稱。
|
. |
可以通過索引訪問 Python 列表。 將 connect.py 更改為: import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') ver = con.version.split(".") print ver print ver[0] print ver[-1] print ver[1:4] con.close() 在命令行終端重新運行該腳本: python connect.py Python 列表是以零為基數的,因此 ver[0] 輸出該列表的第一個元素。該列表的最后一個元素是 ver[-1]。ver[1:4] 創建了一個列表切片。這將返回從位置 1 開始到位置 4 的元素,但不包括位置 4 的元素。
|
. |
Python 列表有一些方法,也可使用運算符來操作列表。 將 connect.py 更改為: import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') ver = con.version.split(".") print ver print ver.index("1") ver.remove("2") print ver ver1 = ["11", "g"] ver2 = ["R", "2"] print ver1 + ver2 con.close() 在命令行終端重新運行該腳本: python connect.py index("1") 方法返回“1”元素的索引,從零開始計數。remove("2") 方法從列表中刪除一個元素。“+”運算符可用於聯接兩個列表。 Python 的其他數據類型還包括字典(這是一些關聯數組)和名為字節組的類型(類似於列表,但不能更改)。 可以使用循環來迭代列表。 將 connect.py 更改為: import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') ver = con.version.split(".") for v in ver: print v if v == "11": print "It's 11" else: print "Not 11" con.close() 確保縮進正確! 使用冒號“:”表示代碼塊。第一個 print 和 if 位於同一個縮進級別,因為它們兩個都在循環中。 在命令行終端重新運行該腳本: python connect.py 該循環依次輸出和測試該列表中的每個值。
|
使用數據庫駐留連接池
數據庫駐留連接池是 Oracle Database 11g 的一個新特性。它對 Web 應用程序常用的短期腳本非常有用。它允許隨着 Web 站點吞吐量的增長對連接數量進行擴充。它還支持多台計算機上的多個 Apache 進程共享一個小規模的數據庫服務器進程池。沒有 DRCP,Python 連接必須啟動和終止一個服務器進程。
下圖的左側是非池化的示意圖。每個腳本都有自己的數據庫服務器進程。不承擔任何數據庫工作的腳本一直保留在連接上,直到連接關閉並且服務器終止。下圖的右側是使用了 DRCP 的示意圖。所有腳本都可使用來自服務器池的數據庫服務器,不再需要時將退回服務器。
![]() |
![]() |
本教程介紹新應用程序或現有應用程序如何在不編寫和更改任何應用程序邏輯的情況下使用 DRCP。執行以下步驟:
. |
查看 $HOME 目錄的 connect_drcp.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol', 'welcome', '127.0.0.1:/orcl:pooled', cclass = "HOL", purity = cx_Oracle.ATTR_PURITY_SELF) print con.version con.close() 該腳本與 connect.py 非常類似,但連接字符串后面添加了“:pooled”。還向 connect() 方法中傳遞了一個連接類“HOL”,並且將該連接的“purity”定義為 ATTR_PURITY_SELF 常量。 該連接類告訴數據庫服務器池這些連接是相關的。不同連接調用之間的會話信息(如默認的數據格式)可能會保留,以改善系統性能。如果之后其他應用程序通過自己的連接類名重用某個池化服務器,則會話信息將作廢。 那些永不應該共享會話信息的應用程序,應使用其他連接類和/或使用 ATTR_PURITY_NEW 強制創建新會話。這雖然降低了整體可伸縮性,但避免了應用程序誤用會話信息。 運行 connect_drcp.py python connect_drcp.py
輸出仍舊只是數據庫版本。 無需更改腳本邏輯即可從使用 DRCP 連接池獲得好處。
|
---|
創建簡單查詢
開發 Web 應用程序時的一個常見任務是,查詢一個數據庫然后在 Web 瀏覽器中顯示結果。您可以使用許多函數查詢一個 Oracle 數據庫,但查詢的基礎始終是相同的:
1. 分析要執行的語句。
2. 綁定數據值(可選)。
3. 執行語句。
4. 從數據庫中獲取結果。
. |
查看 $HOME 目錄的 query.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() cur.execute('select * from departments order by department_id') for result in cur: print result cur.close() con.close() cursor() 方法打開語句要使用的游標。 execute() 方法分析並執行語句。 循環從游標獲取每一行並輸出該行。 在終端窗口運行該腳本: python query.py
查詢結果顯示為 Python“字節組”,“字節組”是一些不能更改的數組。
|
---|
獲取數據
從 Oracle 數據庫中獲取數據的方式有多種。執行以下步驟。
. |
查看 $HOME 目錄的 query_one.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.01/orcl') cur = con.cursor() cur.execute('select * from departments order by department_id') row = cur.fetchone() print row row = cur.fetchone() print row cur.close() con.close() 該腳本使用 fetchone() 方法只返回一行作為一個字節組。多次調用該方法后,返回連續的多行: 在終端窗口運行該腳本: python query_one.py 兩次 fetchone() 調用輸出兩條記錄。
|
---|---|
. |
查看 $HOME 目錄的 query_many.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() cur.execute('select * from departments order by department_id') res = cur.fetchmany(numRows=3) print res cur.close() fetchmany() 方法返回一個字節組列表。其中的 numRows 參數指定應返回三行。 在終端窗口運行該腳本: python query_many.py 以字節組列表形式返回了表的頭三行。 查看 $HOME 目錄的 query_all.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() cur.execute('select * from departments order by department_id') res = cur.fetchall() print res cur.close() con.close() 在終端窗口運行該腳本: python query_all.py 該腳本使用 fetchall() 方法返回所有行。輸出仍然是一個字節組列表(Python 使用的一種數組的名稱)。每個字節組包含一行的數據。
|
. |
可以用任何 Python 方式操作該列表。編輯 query_all.py,將代碼進行如下更改(黑體部分),然后再次運行該腳本。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() cur.execute('select * from departments order by department_id') res = cur.fetchall() for r in res: cur.close() con.close() 現在,每個字節組分別輸出。選擇使用哪種獲取方法主要取決於您希望如何處理返回的數據。
|
提高查詢性能
本節將演示通過增加每批從 Oracle 返回到 Python 程序的行數來提高查詢性能的方法。執行以下步驟:
. |
首先,創建一個包含許多行的表。查看下面的 query_arraysize.sql 腳本。 set echo on / show errors 在終端窗口中,使用 SQL*Plus 運行該腳本: sqlplus pythonhol/welcome@127.0.0.1/orcl
|
---|---|
. |
查看 $HOME 目錄的 query_arraysize.py 文件中包含的以下代碼。 import time import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') start = time.time() cur = con.cursor() cur.arraysize = 100 cur.execute('select * from bigtab') res = cur.fetchall() # print res # uncomment to display the query results elapsed = (time.time() - start) print elapsed, " seconds" cur.close() con.close() 該腳本使用“time”模塊測量查詢所花的時間。默認值被設置為 100。這導致每次從數據庫向 Python 的緩存返回 100 條記錄。這就減少了對數據庫的“忘返”次數,通常還會降低網絡負載並減少數據庫服務器上下文切換次數。從數據庫請求更多的數據之前,fetchone()、fetchmany()、甚至 fetchall() 方法都將從緩存讀取數據。 在終端窗口中,運行: python query_arraysize.py 重新加載幾次,看一下平均時間。
|
. |
編輯 query_arraysize.py,將 arraysize 從 cur.arraysize = 100 更改為 cur.arraysize = 2000 重新再運行幾次腳本,比較這兩種 arraysize 設置的性能。通常,較大的數組大小可提高性能。根據您系統的速度,為了明顯提高查詢速度,您可能需要使用與這里給出的 arraysize 不同的設置值。 python query_arraysize.py cx_Oracle 使用的默認 arraysize 為 50。要增加 arraysize,需要在時間/空間方面進行權衡。arraysizes 越大,Python 中用於緩存記錄需要的內存也越大。
|
使用綁定變量
綁定變量允許您使用新值重新執行語句,避免了重新分析語句的開銷。綁定變量提高了代碼可重用性,降低了 SQL 注入攻擊的風險。
. |
查看 $HOME 目錄的 bind_query.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() cur.prepare('select * from departments where department_id = :id') cur.execute(None, {'id': 210}) res = cur.fetchall() print res cur.execute(None, {'id': 110}) res = cur.fetchall() print res cur.close() con.close() 該語句包含一個綁定變量“:id”。該語句只准備了一次,但針對 WHERE 子句的不同值執行了兩次。 因為 prepare() 方法已經對該語句進行了設置,因此對 execute() 使用特殊符號“None”代替該語句的文本參數。execute() 調用的第二個參數是 Python 字典。在第一個 execute 調用中,該關聯數組的“id”關鍵字的值為 210。 第一個 execute 使用值 210 進行查詢。第二個 execute 使用值 110。 在終端窗口中,運行: python bind_query.py 輸出顯示了兩個部門的詳細信息。
|
---|---|
. |
cx_Oracle 驅動程序支持 INSERT 語句的數組綁定,這樣可以大大提高單行插入的性能。 查看下面用於創建要插入數據的表的命令: sqlplus pythonhol/welcome@127.0.0.1/orcl drop table mytab; 運行 SQL*Plus,剪切並粘貼命令。
|
. |
查看 $HOME 目錄的 bind_insert.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') rows = [ (1, "First" ), (2, "Second" ), (3, "Third" ), (4, "Fourth" ), (5, "Fifth" ), (6, "Sixth" ), (7, "Seventh" ) ] cur = con.cursor() cur.bindarraysize = 7 cur.setinputsizes(int, 20) cur.executemany("insert into mytab(id, data) values (:1, :2)", rows) #con.commit() # Now query the results back cur2 = con.cursor() cur2.execute('select * from mytab') res = cur2.fetchall() print res cur.close() cur2.close() con.close() “rows”數組包含要插入的數據。 其中的 bindarraysize 設置為 7,這意味着一步就插入全部七行。setinputsizes() 調用描述了列的情況。第一列是整數。第二列最多為 20 個字節。 executemany() 調用插入全部七行。 commit() 調用被注釋掉了,因此不會執行。 腳本的最后一部分是查詢返回的結果,並將其顯示為一個字節組列表。 在終端窗口中,運行: python bind_insert.py 在腳本的最后自動回滾新結果,因此重新運行該腳本將在表中始終顯示相同的行數。
|
創建事務
在 Oracle 數據庫中操作數據(插入、更新或刪除數據)時,更改的數據或新數據在提交至數據庫前僅在數據庫會話中可用。更改的數據提交至數據庫,然后可供其他用戶和會話使用。這是一個數據庫事務。執行以下步驟:
. |
編輯上一節中使用的 bind_insert.py 腳本,取消對 commit 調用的注釋(下面的黑體字部分): import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') rows = [ (1, "First" ), (2, "Second" ), (3, "Third" ), (4, "Fourth" ), (5, "Fifth" ), (6, "Sixth" ), (7, "Seventh" ) ] cur = con.cursor() cur.bindarraysize = 7 cur.setinputsizes(int, 20) cur.executemany("insert into mytab(id, data) values (:1, :2)", rows) con.commit() # Now query the results back cur2 = con.cursor() cur2.execute('select * from mytab') res = cur2.fetchall() print res cur.close() cur2.close() con.close() commit() 是針對連接執行,而非針對游標。 重新運行該腳本幾次,看一下每次運行在表中增加的行數: python bind_insert.py 如果您需要在腳本中啟動回滾,則可使用 con.rollback() 方法。 通常,您希望提交所有數據或者不提交任何數據。進行您自己的事務控制具有性能和數據完整性優勢。
|
---|
使用 PL/SQL 存儲函數和過程
PL/SQL 是 Oracle 對 SQL 的過程語言擴展。PL/SQL 過程和函數在數據庫中存儲和運行。使用 PL/SQL 允許所有數據庫應用程序重用邏輯,無論應用程序以何種方式訪問數據庫。許多與數據相關的操作在 PL/SQL 中的執行速度比將數據提取到一個程序中(例如,Python)然后再進行處理的速度快。Oracle 還支持 Java 存儲過程。
在本教程中,您將創建一個 PL/SQL 存儲函數和過程並在 Python 腳本中調用它們。執行以下步驟:
. |
啟動 SQL*Plus,用以下命令創建一個新表 ptab: sqlplus pythonhol/welcome@127.0.0.1/orcl
|
---|---|
. |
查看 create_func.sql 腳本,它創建一個 PL/SQL 存儲函數 myfunc(),以便向 ptab 表中插入一行並且返回插入的值兩倍: set echo on 啟動 SQL*Plus 並運行該腳本: sqlplus pythonhol/welcome@127.0.0.1/orcl @create_func exit
|
. |
查看 $HOME 目錄的 plsql_func.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() res = cur.callfunc('myfunc', cx_Oracle.NUMBER, ('abc', 2)) print res cur.close() con.close() 該腳本使用 callfunc() 執行此函數。常量 cx_oracle.NUMBER 指示返回值是數字。PL/SQL 函數的兩個參數作為一個字節組傳輸並綁定到該函數的參數。 在終端窗口中,運行: python plsql_func.py 輸出的是 PL/SQL 函數計算的結果。
|
. |
要調用 PL/SQL 過程,使用 cur.callproc() 方法。 查看 create_proc.sql 腳本,它將創建一個接受兩個參數的 PL/SQL 過程 myproc()。第二個參數包含一個 OUT 返回值。 set echo on 啟動 SQL*Plus 並運行該腳本: sqlplus pythonhol/welcome@127.0.0.1/orcl @create_proc exit
|
. |
查看 $HOME 目錄的 plsql_proc.py 文件中包含的以下代碼。 import cx_Oracle con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl') cur = con.cursor() myvar = cur.var(cx_Oracle.NUMBER) cur.callproc('myproc', (123, myvar)) print myvar.getvalue() cur.close() con.close() 該腳本創建了一個容納 OUT 參數的數值變量 myvar。使用一個字節組將數字 123 和返回變量名稱綁定到這個過程調用參數。 在終端窗口中,運行: python plsql_proc.py getvalue() 方法顯示返回值。
|
連續查詢通知
連續查詢通知(也稱為數據庫更改通知)允許應用程序在表更改時(例如,向表中插入行)接收通知。在許多情況下這一功能非常有用,包括中間層緩存無效的情況。緩存可能會容納一些與表數據有依賴關系的值。如果表發生更改,緩存的值也必須按照新信息進行更新。
本示例說明了如何在 Python 中處理 DCN 事件。執行以下步驟:
. |
查看 $HOME 目錄的 dcn.py 文件中包含的以下代碼。 import cx_Oracle def DCNCallback(message): print "Notification:" for tab in message.tables: print "Table:", tab.name for row in tab.rows: if row.operation & cx_Oracle.OPCODE_INSERT: print "INSERT of rowid:", row.rowid if row.operation & cx_Oracle.OPCODE_DELETE: print "DELETE of rowid:", row.rowid con = cx_Oracle.Connection("pythonhol/welcome@127.0.0.1/orcl", events = True) subscriptionInsDel = con.subscribe(callback = DCNCallback, operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE, rowids = True) subscriptionInsDel.registerquery('select * from mytab') raw_input("Hit Enter to conclude this demo\n") 該腳本創建一個名為 DCNCallback() 的函數。表發生更改時將調用該函數。“message”參數是一個 cx_Oracle 對象,將包含有關更改的信息。該函數只輸出已經發生的各種更改以及受影響的 rowid。 該腳本的主體位於該函數之下,注意,“con = ...”的縮進級別與“def ...”相同。該主體用“events = True”參數創建數據庫連接,這樣在表發生更改時允許數據庫向 Python 發送事件通知。 發生 INSERT 或 UPDATE 時,subscribe() 調用在新線程中注冊要調用的 DCNCallback()。任何給定的時間只能運行一個 Python 線程。Python 根據需要在不同線程間切換。 rowids = True 參數使 rowid 在該回調中可以被訪問。 registerquery() 調用注冊了從 MYTAB 表選擇任何內容的查詢。對該表的任何更改,無論是UPDATE 還是 DELETE,都將導致調用 DCNCallback()。 MYTAB 表是在本教程的前面部分創建的。 該腳本的最后是 raw_input() 調用,它會等待用戶輸入才終止該腳本的運行。 要運行 DCN 示例,打開兩個終端窗口。在第一個窗口中運行: python dcn.py 這將輸出一條消息,在沒有返回提示符的情況下處於等待狀態: 使其繼續運行片刻,然后繼續下一步。
|
---|---|
. |
在第二個終端窗口中,輸入以下命令: sqlplus pythonhol/welcome@127.0.0.1/orcl 發生提交時,Python 腳本(切換回第一個終端窗口)將收到通知並輸出更改通知消息: 您系統上的 rowid 將會不同。
|
. |
切換到您的 SQL*Plus 終端窗口,執行以下 SQL 命令刪除新行: delete from mytab where id = 11; 輸出一個新通知,此時 Python 終端類似於: 再體驗幾個操作。在提交之前,嘗試一次 INSERT 操作,之后再進行一次 DELETE 操作。這次顯示收到了每個單獨操作的通知。
|
. |
完成這些操作后,在 Python 中按 Enter 結束該演示並退出該腳本。
|
. |
對 dcn.py 進行擴展,使其在 MYTAB 發生 UPDATE 時也發出通知。 在 subscribe() 調用中,將 operations 參數更改為 operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE 向 DCNCallback 函數添加了一個新的“if”測試: if row.operation & cx_Oracle.OPCODE_UPDATE: print "UPDATE of rowid:", row.rowid dcn.py 腳本現在應如下所示(以黑體表示所作的更改): import cx_Oracle 在 Python 終端,重啟該腳本:
|
. |
在 SQL*Plus 終端,創建一行並對其進行更新: insert into mytab (id) values (11); update mytab set id = 12 where id = 11; commit; 應顯示新消息。
|
. |
完成這些操作后,在 Python 中按 Enter 結束該演示並退出該腳本。 數據庫更改通知是監視表更改的一個高效方法。它還可用於在給定查詢所選行的子集發生更改時發出通知。
|
使用 Django 框架
Django 框架是創建 Python Web 應用程序的幾個流行 Python 框架之一。
該練習將創建一個簡單的 Django Web 應用程序。首先介紹一個簡單報表。
. |
To start, create a Django project. From a terminal window, run: django-admin.py startproject myproj
|
---|---|
. |
要在這個新項目中創建應用程序,運行: cd myproj 現在, myproj 目錄中包含一個主干應用程序: __init__.py — 將目錄視作一個 Python 程序包 myapp 目錄包含: __init__.py — 將目錄視作一個 Python 程序包 要使應用程序連接到數據庫,編輯 myproj/settings.py。在靠近該文件頂部的地方,更新要讀取的數據庫連接參數: DATABASE_ENGINE = ' oracle' DATABASE_NAME = ' 127.0.0.1/orcl' DATABASE_USER = ' pythonhol' DATABASE_PASSWORD = ' welcome' DATABASE_HOST 和 DATABASE_PORT 的值可以保留為空。
|
. |
在該文件的底部,向 INSTALLED_APPS 添加一行代碼以將應用程序與項目關聯: INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'myproj.myapp' ) 保存文件並關閉編輯器。
|
. |
運行主干應用程序。在終端窗口中,在 myproj 目錄下執行以下命令: python manage.py runserver 這將在端口 8000 上啟動一個內置的開發 Web 服務器並等待處理 Web 請求。沒有返回 shell 提示符。讓其運行一會兒。
|
. |
打開一個瀏覽器,輸入以下 URL: http://127.0.0.1:8000/ 將顯示默認的 Django 應用程序頁面。該階段尚未建立任何數據庫連接。
|
. |
要構造應用程序,首先需要編輯 myproj/myapp/models.py class locations(models.Model): location_id = models.IntegerField(primary_key = True) street_address = models.CharField(max_length = 40) postal_code = models.CharField(max_length = 12) city = models.CharField(max_length = 30) state_province = models.CharField(max_length = 25) country_id = models.CharField(max_length = 2) class Meta: db_table = "locations" 這將為 LOCATIONS 表創建一個對象關系映射。該類的每個屬性對應於表中現有的一列。primary_key = True 這個設置指定了 location_id 列是模型的主鍵。每個字符字段都指定了自己的長度。
|
. |
您需要在 myproj/myapp/views.py 中添加一個新函數來查詢該模型並生成報表輸出。將以下內容添加到該文件的結尾: from django.template import Context, loader from django.http import HttpResponse from myproj.myapp.models import locations def index(request): location_list = locations.objects.all().order_by('location_id') tmpl = loader.get_template("index.html") cont = Context({'locations': location_list}) return HttpResponse(tmpl.render(cont)) 這將從 models.py 中導入 'locations' 模型。 創建了一個按 LOCATION_ID 排序的查詢集。使用它呈現 index.html 模板文件。生成的 HTML 頁面傳遞給 Django 並最終顯示給用戶。
|
. |
需要創建 index.html 模板文件,以便在“index”呈現該模型時輸出查詢集。輸入以下命令創建 templates 目錄。 cd myproj/myapp mkdir templates
|
. |
使用編輯器創建 myproj/myapp/templates/index.html 文件。內容如下: <html> <head> <title>Office Locations</title> </head> <body bgcolor="#ffffff"> 這使用了由“{%”和“%}”對及“{{”和“}}”對表示的 Django 模板化語法。locations 變量是由視圖中的 Context 調用設置的值。循環針對查詢集的每行構造了一個 HTML 表行。Django 對 index.html 文件進行預處理,並創建發送給瀏覽器的最終 HTML。
|
. |
最后,您需要告訴 Django 如何調用我們的“index”視圖函數。 編輯 myproj/urls.py,在末尾處添加下面的“url()”行。 url(r'^myapp/', include('myproj.myapp.urls')), r'^myapp/' 字符串是一個正則表達式。這將使 URL 包含要傳遞給 myproj/mypass/urls.py 文件的“myapp/”,以便進一步處理。正則表達式表示“myapp/”必須像下面那樣位於 URL 的主機名之后: http://.../myapp/
|
. |
創建一個新文件 myproj/myapp/urls.py,用於分發應用程序 URL。添加以下代碼: from django.conf.urls.defaults import * from myapp.views import index urlpatterns = patterns('', url(r'^$', index), ) 這將調用 views.py 中的“index”函數。注意,正則表達式不再包含“myapp”,因為“myapp”已經在項目級進行匹配,所以不會傳遞到該映射文件中。
|
. |
在您的終端窗口中,使用 ^C 停止 Web 服務器並使用以下命令重啟: python manage.py runserver
|
. |
在您的瀏覽器中加載應用程序的 URL: http://127.0.0.1:8000/myapp/ 應用程序頁面顯示 LOCATIONS 表的三個所需字段的數據: 該應用程序概括如下: 1. 對 http://.../myapp 的瀏覽器頁面請求調用 Django Web 服務器 2. Django 的 URL 調度程序通過 myproj/urls.py 文件中的 URL 模式運行,並選擇與該 URL 匹配的第一個 URL。接着調用 myproj/myapp/urls.py 調度程序。然后將調用“index”視圖,該視圖是一個 Python 回調函數。 3. 該視圖使用數據模型獲取數據庫數據。然后使用 LOCATIONS 表數據呈現“index.html”模板。 4. “index”視圖返回一個用呈現的模板填充的 HttpResponse 對象。 5. Django 將 HttpResponse 對象呈現到瀏覽器。 本示例中使用的視圖是為了查詢數據。視圖也可以創建保存到數據庫中的對象。使用 locations 模型在一個視圖中插入一個新對象或新行的代碼可如下所示: new_loc = locations( location_id = 7000, street_address = "123 ABC", postal_code = "9999", city = "My City", state_province = "My State", country_id = "US") new_loc.save() 如下所示進行刪除操作: loc = locations.objects.get(location_id__exact=1000) loc.delete() 這也說明了生成一個匹配單條記錄的查詢的語法。
|
結合使用 AJAX 和 Python
本節介紹如何使用 AJAX 技術在不重新加載整個頁面的情況下更改部分 HTML 頁面。本示例將更改“index”視圖,以便在單擊一條記錄時顯示該位置的完整地址。執行以下步驟:
. |
在第一部分中,將創建一個返回給定位置的完整地址(包括國家/地區)的新視圖。您將從建立底層模型開始。編輯 myproj/myapp/models.py: 在現有的“locations”模型基礎上為 COUNTRIES 表添加一個模型: class countries(models.Model): country_id = models.CharField(max_length = 2, primary_key = True) country_name = models.CharField(max_length = 40) region_id = models.IntegerField() class Meta: db_table = "countries" 要明確與 LOCATIONS 表的關系,需要更改 locations 模型,將以下內容: country_id = models.CharField(max_length = 2) 替換為: country = models.ForeignKey(countries) 注意不是“country_id”了。Django 自動為外鍵添加“_id”后綴,並在 COUNTRIES 和 LOCATIONS 表中使用 COUNTRY_ID 作為聯接列。 完整的 models.py 文件(用黑體字表示所作的更改)如下: from django.db import models # Create your models here. class countries(models.Model): country_id = models.CharField(max_length = 2, primary_key = True) country_name = models.CharField(max_length = 40) region_id = models.IntegerField() class Meta: db_table = "countries" class locations(models.Model): location_id = models.IntegerField(primary_key = True) street_address = models.CharField(max_length = 40) postal_code = models.CharField(max_length = 12) city = models.CharField(max_length = 30) state_province = models.CharField(max_length = 25) country = models.ForeignKey(countries) class Meta: db_table = "locations"
|
---|---|
. |
編輯 myproj/myapp/views.py,添加一個新視圖方法“address”來構造地址。完整的文件如下: # Create your views here. from django.template import Context, loader from django.http import HttpResponse from myproj.myapp.models import locations def index(request): location_list = locations.objects.all().order_by('location_id') tmpl = loader.get_template("index.html") cont = Context({'locations': location_list}) return HttpResponse(tmpl.render(cont)) def address(request, lid): address_list = locations.objects.select_related().filter(location_id=lid) s = "" for loc in address_list: s = '[{"STREET_ADDRESS":"' + loc.street_address + \ '","CITY":"' + loc.city + \ '","POSTAL_CODE":"' + loc.postal_code + \ '","COUNTRY":"' + loc.country.country_name + '"}]' return HttpResponse(s) 注意:確保縮進與上面顯示的和下面的屏幕截圖中的完全一樣。 對於 address 視圖,像稍后說明的那樣,將從調用該視圖的 URL 傳遞“lid”參數值。 Django 使用“select_related()”方法實現了 LOCATIONS 和 COUNTRIES 兩表之間的內聯接,並且將針對給定位置標示符的所有查詢結果都檢索至 address_list 查詢集對象中。Django 還綁定“lid”的值以提高效率和安全性。 “for”循環是一個用於訪問檢索到的查詢集的首個元素的簡便方法。因為 LOCATION_ID 是主鍵,因此該循環將只執行一次。 變量“s”是用字符串連接組成的,用於包含行的 JSON 格式版本。JSON 是一種文本格式,通常用作瀏覽器中的 Javascript 和服務器端腳本之間傳輸數據的輕型協議。較新版本的 Python 具有 JSON 編碼和解碼的方法,可以使用這些方法代替此處使用的顯式字符串連接。 與已有的“index”視圖不同,該腳本不使用 HTML 模板。HttpResponse() 只是將 JSON 字符串回送給瀏覽器。
|
. |
要使新的“address”視圖可調用,編輯 myproj/myapp/urls.py(不是 myproj/urls.py),添加: url(r'^addr/(?P<lid>\w+)$', address), 還要將下一行: from myapp.views import index 更改為 from myapp.views import index , address 完整的文件類似於(用黑體表示所作的更改): from django.conf.urls.defaults import * from myapp.views import index , address urlpatterns = patterns('', url(r'^addr/(?P<lid>\w+)$', address), url(r'^$', index), ) 這個新規則匹配表單的 URL: http://.../myapp/addr/1234 在本示例中,參數“lid”將設置為 1234 並傳遞到“address”視圖中。
|
. |
要測試這個新視圖,在瀏覽器中顯式調用它: http://127.0.0.1:8000/myapp/addr/1000
|
. |
現在,可以將“index”視圖更改為使用“address”視圖以便動態更改顯示的頁面。 編輯 myapp/templates/index.html,進行以下更改。稍后將顯示完整文件以便復制。 將表下面添加一個新的 HTML 節: <p> <div id="outputNode"></div> </p> 這樣,最初不包含任何要輸出的文本。加載該頁面時,在下表中什么都看不到。完成該示例后,單擊一個鏈接時將運行 Javascript 代碼,並將更新該 outputNode 節的文本內容。這樣,瀏覽器無需刷新整個頁面就可以顯示其新值。 現在,添加一個要單擊的鏈接。將模板文本從: <td>{{loc.location_id}}</td> 更改為 <td><a href="http://this_link_goes_nowhere/" onClick="makeRequest({{loc.location_id}}); return false;"> {{loc.location_id}}<a></td> 這將使所有位置標示符都變成鏈接。鏈接是一個無效的 URL,但沒關系,因為從不會調用這個鏈接。相反,onClick 事件將調用我們將創建的一個新的 Javascript 函數。location_id 值(替換為 Django 的模板擴展)傳遞給該函數。“return false;”語句避免了 HREF 調用(本示例中)無效 URL 的默認操作。 最后,在 <head> 標記中,添加發出異步 HTTP 請求的 Javascript 函數: <script type="text/javascript"> function makeRequest(id) </script> 這是單擊位置標示符時調用的函數。它將 URL 設置為作為 http://127.0.0.1:8000/myapp/addr/ 加上位置標示符進行調用。 Javascript 的最后一行: httpRequest.send(null); 發起 HTTP 請求,但在這之前,onreadystatechange 操作被設置為一個匿名函數,以隨時准備處理任何返回的數據。 該請求狀態改變時,將異步調用狀態更改函數。該函數會檢查請求是否完成。如果完成,eval 將把檢索的 JSON 字符串轉換為一個 Javascript 對象。可以在 Javascript 中直接使用結果數組 myArray。在本示例中,將只執行一次循環,因為 LOCATION_ID 是主鍵。循環體將地址數據字段連接成為一個字符串。 這樣使用 eval() 會帶來安全問題。許多第三方 JSON 分析器對生產系統來說都是比較安全的。 下面的代碼行: document.getElementById("outputNode").innerHTML = txt; 將導致 outputNode div 節的頁面內容更改為地址。 完整的 myproj/myapp/templates/index.html 文件(用黑體表示所作的更改)如下: <html> function makeRequest(id) </script> <h1>Office Locations</h1> <p> </body>
|
. |
現在通過在瀏覽器中加載索引 URL 運行該應用程序: http://127.0.0.1:8000/myapp/ 現在,所有位置標示符都變成了鏈接。
|
. |
將鼠標放在任何一個鏈接上。瀏覽器底部的狀態欄將顯示 URL http://this_link_goes_nowhere/ 單擊最后一個位置標示符“3200”。不會調用這個無效的 URL。相反,將調用 Javascript 函數,並在表下方顯示墨西哥辦事處的完整地址(您可能需要向下滾動)。使用 Django 中包括的速度較慢的開發 Web 服務器刷新該表可能需要 1 到 2 秒鍾的時間: 審慎使用時,AJAX 技術可以提高 Web 應用程序的性能和適用性。還可以使用 AJAX 顯示或發送數據,從而創建復雜應用程序。
|
總結
- 創建連接
- 使用數據庫駐留連接池
- 創建簡單查詢
- 獲取數據
- 提高查詢性能
- 使用綁定變量
- 創建事務
- 使用 PL/SQL 存儲函數和過程
- 使用連續查詢通知
- 使用 Django 框架
- 結合使用 Python 和 AJAX
附錄:Python 入門
Python 是一種動態類型的腳本語言。它通常用於運行命令行腳本,但也在 Web 應用程序中使用。
'A string constant' "another constant"
""" This is your string """
count = 1 ename = 'Arnie'
a2 = {'PI':3.1415, 'E':2.7182}
a3 = [101, 4, 67]
字節組類似於列表,但是,一旦創建就不能更改。使用圓括號創建字節組:
a4 = (3, 7, 10)
print 'Hello, World!' print 'Hi', x
print "There are %d %s" % (v1, v2)
Python 中的一切都是一個對象:以上面給出的列表 a3 的值為例,可以使用 append() 方法向該列表添加一個值。
a3.append(23)
可以通過測試和循環來控制代碼流。if/elif/else 語句如下所示:
if sal > 900000: print 'Salary is way too big' elif sal > 500000: print 'Salary is huge' else: print 'Salary might be OK'
這也說明了如何使用冒號分割這些子句以及如何縮進每個子代碼塊。
for i in range(0, 10): print i
a5 = ['Aa', 'Bb', 'Cc'] for v in a5: print v
def myfunc(p1, p2): "Function documentation: add two numbers" print p1, p2 return p1 + p2
v3 = myfunc(1, 3)
函數也是對象,也有屬性。可以使用內置的 __doc__ 屬性查找函數說明:
print myfunc.__doc__
可以使用 import 語句將子文件包括在 Python 腳本中。
import os import sys
# a short comment
""" a longer comment """