1. 程式人生 > >在Python3下使用tornado和SQLAlchemy實現一個簡單的MVC網站

在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 就可以訪問我們剛才建立的應用,你可以試著增加、刪除使用者,以及修改使用者資訊。