在Python3下使用tornado和SQLAlchemy實現一個簡單的MVC網站
本文講述了在Python3下如何使用tornado框架及sqlalchemy對MySQL資料庫進行簡單的增、刪、改、查操作。整個流程走通之後,就可以在實際開發中對本文中的例子進行重構、優化,以滿足產品需要。
環境:Window7 x64、MySQL 5.6.21、Python 3.4.1、tornado 4.0.2、MySQL-Connector-Python 2.0.1、SQLAlchemy 0.9.8
在本文的例子中,我們維護一個使用者資料庫,資料庫中包含了使用者名稱、年齡、性別、得分、主修專業。可以通過WEB頁面來增加、刪除使用者,以及修改、顯示所有的使用者資訊。
一、在MySQL中建立資料庫及相關表
我們的重點是用最簡單的步驟實現整個MVC流程,所以建立資料庫的操作就不使用SQLAlchemy來做了。
先在Windows的命令提示符下連線到MySQL,並建立一個名為UserManager的資料庫。命令如下所示:
e:\>mysql -u root -p
password:********
mysql>CREATE DATABASE UserManager;
注意第一行-u後面的root是資料庫的使用者名稱,應當按照實際情況輸入。password後面的密碼也要按照你自己系統的實際情況填寫。
然後鍵入命令 USE UserManager 進入到UserManager資料庫中,新建一個如下結構的表user:
CREATE TABLE user(
id int(16) not null primary key auto_increment,
user_name varchar(32) not null,
user_age int(3) not null,
user_sex int(1) not null,
user_score int(3) not null,
user_subject varchar(32) not null );
之後我們所有的操作都是針對這張表進行的。
二、建立HTML模板頁
我們使用tornado內建的模板進行HTML的渲染,速度雖然不是最快的,但是簡單實用。熟練之後可以用Jinja2等更專業的模板替換掉tornado內建的模板。
tornado內建模板的語法非常簡單,語法塊預設由{% ... ... %}包含,變數則由{{ ... ... }}包含。當然也以設定為其它樣式的Limiter。
本文的例子中包含兩個WEB頁面:UserManager.html以及EditUserInfo.html。下面分別是這兩個頁面的程式碼。
頁面 UserManager.html:
<html>
<head>
<!--這裡的{{title}}表示title這個變數的內容由伺服器端產生並填寫到該位置-->
<title>{{title}}</title>
<body>
<center><h1>Welcome to UserManager</h1></center>
<hr/>
<center>
<!--表單以POST方式提交到AddUser-->
<form name = 'new_user' action = 'AddUser' method = 'post'>
Name:<input type = 'text' name = 'user_name' />
Age:<input type = 'text' name = 'user_age' />
Sex:<input type = 'text' name = 'user_sex' />
Score:<input type = 'text' name = 'user_score' />
Subject:<input type = 'text' name = 'user_subject' />
<input type = 'submit' value = 'Submit' />
<input type = 'reset' value = 'Reset' />
</form>
<hr/>
<table border = 1>
<tr style = 'font-weight:bold' align = 'center'>
<td width = '200' >Name</td>
<td width = '100' >Age</td>
<td width = '100' >Sex</td>
<td width = '100' >Score</td>
<td width = '200' >Subject</td>
<td width = '100' ></td>
<td width = '100' ></td>
</tr>
<!--users是一個由伺服器端通過查詢資料庫而產生的列表,這裡用for語句可以把使用者資訊全部輸出-->
{% for user in users %}
<tr align = 'center'>
<!--user.user_name即是使用者名稱。下同-->
<td>{{ user.user_name }}</td>
<td>{{ user.user_age }}</td>
<td>{{ user.user_sex }}</td>
<td>{{ user.user_score }}</td>
<td>{{ user.user_subject }}</td>
<!--編輯連結,可以編輯指定使用者名稱的資訊-->
<td><a href = 'EditUser?user_name={{user.user_name}}' >Edit</a></td>
<!--刪除連結,可以刪除指定使用者名稱的資訊-->
<td><a href = 'DeleteUser?user_name={{user.user_name}}' >Delete</a></td>
</tr>
<!--for迴圈結束-->
{% end %}
</table>
</center>
</body>
</html>
上面這個檔案把資料庫中的使用者資訊以表格的方式輸出到頁面上。並提供了增加新使用者的表單,以及編輯使用者資訊、刪除使用者的連結。大概是下圖這個樣子。
頁面 EditUserInfo.html:
<html>
<head>
<!--user_info是一個物件,user_name則是該物件的一個屬性-->
<title>Edit Info for User: {{user_info.user_name}}</title>
</head>
<body>
<h1>Edit User Info</h1>
<hr/>
<center>
<!--以POST方式將修改後的資訊提交到UpdateUserInfo-->
<form name = 'edit_user_info' action = 'UpdateUserInfo' method = 'post' >
Name: <input type = 'text' name = 'user_name' value = {{user_info.user_name }} readonly/>
Age: <input type = 'text' name = 'user_age' value = {{user_info.user_age }} />
Sex: <input type = 'text' name = 'user_sex' value = {{user_info.user_sex }} />
Score: <input type = 'text' name = 'user_score' value = {{user_info.user_score }} />
Subject: <input type = 'text' name = 'user_subject' value = {{user_info.user_subject }} />
<input type = 'submit' value = 'Submit' />
<input type = 'Reset' value = 'Reset' />
</form>
</center>
<hr/>
</body>
</html>
上面這個檔案中,伺服器端根據之前傳來的user_name在資料庫中查到該使用者的所有資訊,並以一個user_info的物件返回到頁面上。頁面表現差不多是下圖這個樣子:
點選提交後,修改後的資訊將自動在資料庫裡面更新。注意Name這個欄位是不應該被修改的,所以這裡雖然顯示為一個TEXT文字框,但是在程式碼中使用了readonly的屬性,該文字框實際上是不能被修改的。
這兩個檔案放在專案目錄的templates下,以備使用。
三、使用SQLAlchemy編寫ORM層
在正式用tornado寫Server主程式之前,我們還要編寫一個基本的ORM(物件關係模型)層,來做一些最底層的SQL查詢、刪改操作。ORM則可以為我們提供一種最自然的方式,即物件對映方式來操作資料表。對資料表的操作實際上就變成了對指定的物件進行操作,資料表中的欄位都被對映到物件的屬性上面。換句話說,物件屬性的變化將直接更新到對應的資料表中去。
以下是ORM層的Python程式碼,命名為orm.py,並存儲在專案目錄下。
#!/usr/bin/env python
from sqlalchemy import *
from sqlalchemy.orm import *
# Settings to connect to mysql database
database_setting = { 'database_type':'mysql', # 資料庫型別
'connector':'mysqlconnector', # 資料庫聯結器
'user_name':'root', # 使用者名稱,根據實際情況修改
'password':'abcdefg', # 使用者密碼,根據實際情況修改
'host_name':'localhost', # 在本機上執行
'database_name':'UserManager',
}
# 下面這個類就是實體類,對應資料庫中的user表
class User( object ):
def __init__( self, user_name, user_age,
user_sex, user_score, user_subject ):
self.user_name = user_name
self.user_age = user_age
self.user_sex = user_sex
self.user_score = user_score
self.user_subject = user_subject
# 這個類就是直接操作資料庫的類
class UserManagerORM():
def __init__( self ):
'''
# 這個方法就是類的建構函式,物件建立的時候自動執行
'''
self.engine = create_engine( # 生成連線字串,有特定的格式
database_setting[ 'database_type' ] +
'+' +
database_setting[ 'connector' ] +
'://' +
database_setting[ 'user_name' ] +
':' +
database_setting[ 'password' ] +
'@' +
database_setting[ 'host_name' ] +
'/' +
database_setting[ 'database_name' ]
)
self.metadata = MetaData( self.engine )
self.user_table = Table( 'user', self.metadata,
autoload = True )
# 將實體類User對映到user表
mapper( User, self.user_table )
# 生成一個會話類,並與上面建立的資料庫引擎繫結
self.Session = sessionmaker()
self.Session.configure( bind = self.engine )
# 建立一個會話
self.session = self.Session()
def CreateNewUser( self, user_info ):
'''
# 這個方法根據傳遞過來的使用者資訊列表新建一個使用者
# user_info是一個列表,包含了從表單提交上來的資訊
'''
new_user = User(
user_info[ 'user_name' ],
user_info[ 'user_age' ],
user_info[ 'user_sex' ],
user_info[ 'user_score' ],
user_info[ 'user_subject' ]
)
self.session.add( new_user ) # 增加新使用者
self.session.commit() # 儲存修改
def GetUserByName( self, user_name ): # 根據使用者名稱返回資訊
return self.session.query( User ).filter_by(
user_name = user_name ).all()[ 0 ]
def GetAllUser( self ): # 返回所有使用者的列表
return self.session.query( User )
def UpdateUserInfoByName( self, user_info ): # 根據提供的資訊更新使用者資料
user_name = user_info[ 'user_name' ]
user_info_without_name = { 'user_age':user_info[ 'user_age' ],
'user_sex':user_info[ 'user_sex' ],
'user_score':user_info[ 'user_score' ],
'user_subject':user_info[ 'user_subject' ]
}
self.session.query( User ).filter_by( user_name = user_name ).update(
user_info_without_name )
self.session.commit()
def DeleteUserByName( self, user_name ): # 刪除指定使用者名稱的使用者
user_need_to_delete = self.session.query( User ).filter_by(
user_name = user_name ).all()[ 0 ]
self.session.delete( user_need_to_delete )
self.session.commit()
上以程式碼非常經典,雖然沒有進行異常捕捉等操作,但是這些程式碼展示瞭如何使用SQLAlchemy進行資料庫操作的一般方法,增加、刪除、修改都有了。關於SQLAlchemy更詳細的資訊可以參考官方文件。四、tornado框架下的server主程式
接下來就是本文最主要的程式碼了。將以下程式碼儲存為server.py,並存儲到專案目錄下。
#!/usr/bin/env python
# This is a Web Server for UserManager
import tornado.httpserver # 引入tornado的一些模組檔案
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
import orm # 引入剛剛編寫的orm層程式碼
define( 'port', default = 9999, help = 'run on the given port', type = int )
user_orm = orm.UserManagerORM() # 建立一個全域性ORM物件
class MainHandler( tornado.web.RequestHandler ): # 主Handler,用來響應首頁的URL
'''
MainHandler shows all data and a form to add new user
'''
def get( self ): # 處理主頁面(UserManager.html)的GET請求
# show all data and a form
title = 'User Manager V0.1' # 這個title將會被髮送到UserManager.html中的{{title}}部分
users = user_orm.GetAllUser() # 使用ORM獲取所有使用者的資訊
# 下面這一行會將title和users兩個變數分別傳送到指定模板的對應變數中去
self.render( 'templates/UserManager.html', title = title, users = users ) # 並顯示該模板頁面
def post( self ):
pass # 這裡不處理POST請求
class AddUserHandler( tornado.web.RequestHandler ): # 響應/AddUser的URL
'''
AddUserHandler collects info to create new user
'''
def get( self ):
pass
def post( self ): # 這個URL只響應POST請求,用來收集使用者資訊並新建帳號
# Collect info and create a user record in the database
user_info = {
'user_name':self.get_argument( 'user_name' ),
'user_age':self.get_argument( 'user_age' ),
'user_sex':self.get_argument( 'user_sex' ),
'user_score':self.get_argument( 'user_score' ),
'user_subject':self.get_argument( 'user_subject' )
}
user_orm.CreateNewUser( user_info ) # 呼叫ORM的方法將新建的使用者資訊寫入資料庫
self.redirect( 'http://localhost:9999' ) # 頁面轉到首頁
class EditUserHandler( tornado.web.RequestHandler ): # 響應/EditUser的URL
'''
Show a page to edit user info,
user name is given by GET method
'''
def get( self ): # /EditUser的URL中,響應GET請求
user_info = user_orm.GetUserByName( self.get_argument( 'user_name' ) ) # 利用ORM獲取指定使用者的資訊
self.render( 'templates/EditUserInfo.html', user_info = user_info ) # 將該使用者資訊傳送到EditUserInfo.html以供修改
def post( self ):
pass
class UpdateUserInfoHandler( tornado.web.RequestHandler ): # 使用者資訊編輯完畢後,將會提交到UpdateUserInfo,由此Handler處理
'''
Update user info by given list
'''
def get( self ):
pass
def post( self ): # 呼叫ORM層的UpdateUserInfoByName方法來更新指定使用者的資訊
user_orm.UpdateUserInfoByName({
'user_name':self.get_argument( 'user_name' ),
'user_age':self.get_argument( 'user_age' ),
'user_sex':self.get_argument( 'user_sex' ),
'user_score':self.get_argument( 'user_score' ),
'user_subject':self.get_argument( 'user_subject' ),
})
self.redirect( 'http://localhost:9999' ) # 資料庫更新後,轉到首頁
class DeleteUserHandler( tornado.web.RequestHandler ): # 這個Handler用來響應/DeleteUser的URL
'''
Delete user by given name
'''
def get( self ):
# 呼叫ORM層的方法,從資料庫中刪除指定的使用者
user_orm.DeleteUserByName( self.get_argument( 'user_name' ) )
self.redirect( 'http://localhost:9999' ) # 資料庫更新後,轉到首頁
def post( self ):
pass
def MainProcess(): # 主過程,程式的入口
tornado.options.parse_command_line()
application = tornado.web.Application( [ # 這裡就是路由表,確定了哪些URL由哪些Handler響應
( r'/', MainHandler ), # 路由表中的URL是用正則表示式來過濾的
( r'/AddUser', AddUserHandler ),
( r'/EditUser', EditUserHandler ),
( r'/DeleteUser', DeleteUserHandler ),
( r'/UpdateUserInfo', UpdateUserInfoHandler ),
])
http_server = tornado.httpserver.HTTPServer( application )
http_server.listen( options.port ) # 在上面的的define中指定了埠為9999
tornado.ioloop.IOLoop.instance().start() # 啟動伺服器
if __name__ == '__main__': # 檔案的入口
MainProcess()
這個server.py檔案是一個典型的tornado伺服器,不管多複雜的tornado應用,都是在上面這些程式碼的基礎上開發的。
以上四個檔案就是本文例子的全部程式碼了,如果專案目錄為UserManager,則server.py和orm.py都應當放在這個目錄裡面。該專案目錄還包含有一個templates模板目錄,UserManager.html和EditUserInfo.html兩個HTML模板檔案則在templates目錄中。
如果所有設定都沒有問題,就可以在專案目錄UserManager下執行 python server.py 命令來啟動伺服器了。啟動後,在瀏覽器地址中輸入 http://localhost:9999 就可以訪問我們剛才建立的應用,你可以試著增加、刪除使用者,以及修改使用者資訊。