本文讲述了在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
以上这些软件的安装方法在我的另一篇博文里,如有需要请移步至:构建一个完整的基于Python3的Web开发环境。
在本文的例子中,我们维护一个用户数据库,数据库中包含了用户名、年龄、性别、得分、主修专业。可以通过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:
{{title}}
Welcome to UserManager
Name
Age
Sex
Score
Subject
{% for user in users %}
{{ user.user_name }}
{{ user.user_age }}
{{ user.user_sex }}
{{ user.user_score }}
{{ user.user_subject }}
Edit
Delete
{% end %}
上面这个文件把数据库中的用户信息以表格的方式输出到页面上。并提供了增加新用户的表单,以及编辑用户信息、删除用户的链接。大概是下图这个样子。
页面 EditUserInfo.html:
Edit Info for User: {{user_info.user_name}}
Edit User Info
上面这个文件中,服务器端根据之前传来的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 就可以访问我们刚才建立的应用,你可以试着增加、删除用户,以及修改用户信息。