您的位置:首页 > 编程语言 > Python开发

Python框架 Flask 项目实战教程

2015-10-25 23:09 1276 查看
本文目的是为了完成一个项目用到的flask基本知识,例子会逐渐加深。最好对着源码,一步一步走。
下载源码,运行
pipinstall-rrequirements.txt建立环境
pythondb_create.py创建自己数据库
pythondb_migrate迁移数据库
————————————————————————————–
flask不仅简介小巧,同时运用的时候十分灵活。下面简单介绍一下如何编写一个flask项目。

涉及调用开发服务器,数据库连接以及ORM映射,还有数据库的迁移,模板使用。后期再完善会话,管理后台,缓存等。

一、安装环境
我们使用flaskweb框架,并用sqlalchemy来做数据库映射,并使用migrate做数据迁移。

$pipinstallflask
$pipinstallSQLAlchemy==0.7.9
$pipinstallflask-sqlalchemy
$pipinstallflask-migrate
$pipinstallsqlalchemy-migrate


二、建立项目
flask没有django那样原生的manage管理工具(flask-admin可以实现,日后再说)。因此我们需要手动建立目录。新建一个myproject目录,在里面建apptmp两个文件夹,然后在app文件夹里面建立static,templates两个文件夹,用来存储静态文件和模板。最后目录结构如下:

├──app
│├──static
│├──templates
└──tmp


三、初始化文件

建立一些文件,在app文件里建立__init__.pymodels.pyviews.py然后在与app同目录下建立config.py和run.py此时的目录结构如下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$tree

├──app
│├──static
│├──templates
│├──__init__.py
│├──models.py
│├──views.py
├──config.py
├──run.py
└──tmp


四、开始项目

1hellowrod
打开(/app/__init.py)文件,写入

#-*-coding:utf-8-*-
fromflaskimportFlask#引入flask
app=Flask(__name__)#实例化一个flask对象
importviews#导入views模块
#fromappimportviews


注意,我们的app文件夹其实是一个python包,fromappimportviews这句话也就是从包app里面导入views模块,所以写成注释的那句话也没错,其他代码实践喜欢写成后面那种

现在我们来开始写我们的视图,打开(/app/views.py)

#-*-coding:utf-8-*-
fromappimportapp
frommodelsimportUser,Post,ROLE_USER,ROLE_ADMIN
@app.route(‘/’)
defindex():
return‘helloworld,helloflask’


下一步我们将要启动我们的开发服务器,打开(/run.py)

#-*-coding:utf-8-*-
fromappimportapp
if__name__==‘__main__’:
app.debug=True#设置调试模式,生产模式的时候要关掉debug
app.run()#启动服务器


这段代码需要注意第一句fromappimportapp。也许会有疑问,我们的app包里,貌似没有app.py这样的模块。其实这是flask方式和python的导入方式。fromapp指导入app包里的__iniy__.py所以这句话的含义是导入__.init__.py里面的app实例。

打开shell运行

$pythonrun.py
(env)admin@admindeMacBook-Air:~/project/python/flask/project$pythonrun.py

*Runningonhttp://127.0.0.1:5000/*Restartingwithreloader


用浏览器打开http://127.0.0.1:5000/将会看到一个输入helloworld的页面

2连接数据库
下面开始连接数据库引擎先要做一个简单的配置打开(/config.py)

#-*-coding:utf-8-*-

importos
basedir=os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI=‘sqlite:///%s’%os.path.join(basedir,‘app.db’)
SQLALCHEMY_MIGRATE_REPO=os.path.join(basedir,‘db_repository’)
CSRF_ENABLED=True
SECRET_KEY=‘you-will-never-guess’


SQLALCHEMY_DATABASE_URI是theFlask-SQLAlchemy必需的扩展。这是我们的数据库文件的路径。

SQLALCHEMY_MIGRATE_REPO是用来存储SQLAlchemy-migrate数据库文件的文件夹。

然后打开(/app/__init__.py)

#-*-coding:utf-8-*-
importos
fromflaskimportFlask
fromflask.ext.sqlalchemyimportSQLAlchemy
fromconfigimportbasedir

app=Flask(__name__)
app.config.from_object(‘config’)#载入配置文件
db=SQLAlchemy(app)#初始化db对象

#fromappimportviews,models#引用视图和模型

importviews,models



打开(/app/models.py)

#-*-coding:utf-8-*-

fromappimportdb
ROLE_USER=0
ROLE_ADMIN=1

classUser(db.Model):
id=db.Column(db.Integer,primary_key=True)
nickname=db.Column(db.String(60),index=True,unique=True)
email=db.Column(db.String(120),index=True,unique=True)
role=db.Column(db.SmallInteger,default=ROLE_USER)

def__repr__(self):
return‘<User%r>’%self.nickname


我们的模型建立了一个user类,正好是数据库里面的user表,表有四个字段

现在我们的数据库配置已经好了,可是还没有建立数据库。新建一个文件(/db_create.py)

#-*-coding:utf-8-*-

frommigrate.versioningimportapi
fromconfigimportSQLALCHEMY_DATABASE_URI
fromconfigimportSQLALCHEMY_MIGRATE_REPO
fromappimportdb
importos.path

db.create_all()

ifnotos.path.exists(SQLALCHEMY_MIGRATE_REPO):
api.create(SQLALCHEMY_MIGRATE_REPO,'databaserepository')
api.version_control(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)
else:
api.version_control(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO,api.version(SQLALCHEMY_MIGRATE_REPO))


这个脚本是完全通用的,所有的应用路径名都是从配置文件读取的。当你用在自己的项目时,你可以把脚本拷贝到你的项目目录下就能正常使用了。
打开shell运行

$pythondb_create.py

运行这条命令之后,你就创建了一个新的app.db文件。这是个支持迁移的空sqlite数据库,同时也会生成一个带有几个文件的db_repository目录,这是SQLAlchemy-migrate存储数据库文件的地方,注意如果数据库已存在它就不会再重新生成了。这将帮助我们在丢失了现有的数据库后,再次自动创建出来。
运行
slqite3app.db

>>>.tables

>>>user

或者使用sqlite图形化客户端,没有需要安装(windows下直接下载安装包即可)

$sudoapt-getinstallsqlitebrowser
3数据库迁移
每当我们更改了models。等价于更改了数据库的表结构,这个时候,需要数据库同步。新建(/db_migrate.py)

#-*-coding:utf-8-*-

importimp
frommigrate.versioningimportapi
fromappimportdb
fromconfigimportSQLALCHEMY_DATABASE_URI
fromconfigimportSQLALCHEMY_MIGRATE_REPO

migration=SQLALCHEMY_MIGRATE_REPO+'/versions/%03d_migration.py'%(api.db_version(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)+1)
tmp_module=imp.new_module('old_model')
old_model=api.create_model(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)

execold_modelintmp_module.__dict__

script=api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO,tmp_module.meta,db.metadata)

open(migration,'wt').write(script)

api.upgrade(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)

print'Newmigrationsavedas'+migration
print'Currentdatabaseversion:'+str(api.db_version(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO))


这个脚本看起来很复杂,SQLAlchemy-migrate通过对比数据库的结构(从app.db文件读取)和models结构(从app/models.py文件读取)的方式来创建迁移任务,两者之间的差异将作为一个迁移脚本记录在迁移库中,迁移脚本知道如何应用或者撤销一次迁移,所以它可以方便的升级或者降级一个数据库的格式。

开始数据库迁移

$pythondb_mirgate.py

Newmigrationsavedasdb_repository/versions/001_migration.pyCurrentdatabaseversion:1
相应的,我们继续创建数据库升级和回退的脚本
(/db_upgrade.py)

#-*-coding:utf-8-*-

frommigrate.versioningimportapi
fromconfigimportSQLALCHEMY_DATABASE_URI
fromconfigimportSQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)
print'Currentdatabaseversion:'+str(api.db_version(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO))


(/db_downgrade.py)

#-*-coding:utf-8-*-

frommigrate.versioningimportapi
fromconfigimportSQLALCHEMY_DATABASE_URI
fromconfigimportSQLALCHEMY_MIGRATE_REPO

v=api.db_version(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO,v–1)

print'Currentdatabaseversion:'+str(api.db_version(SQLALCHEMY_DATABASE_URI,SQLALCHEMY_MIGRATE_REPO))


4操作数据库
接下来,我们定义一个新的modelsclass并做第二次迁移归并
打开(/app/models.py)

#-*-coding:utf-8-*-

fromappimportdb

ROLE_USER=0
ROLE_ADMIN=1

classUser(db.Model):
id=db.Column(db.Integer,primary_key=True)
nickname=db.Column(db.String(60),index=True,unique=True)
email=db.Column(db.String(120),index=True,unique=True)
role=db.Column(db.SmallInteger,default=ROLE_USER)
posts=db.relationship(‘Post’,db.backref=‘author’,db.lazyload=‘dynamic’)

def__repr__(self):
return‘<User%r>’%self.nickname

classPost(db.Model):
id=db.Column(db.Integer,primary_key=True)
body=db.Column(db.String(140))
timestamp=db.Column(db.DateTime)
user_id=db.Column(db.Integer,db.ForeignKey(‘user.id’))

def__repr__(self):
return‘<Post%r>’%(self.body)


这里我们进行了两个class的关联。

$pythondb_mirgrate.py

Newmigrationsavedasdb_repository/versions/002_migration.pyCurrentdatabaseversion:2

打开(/app/views.py)

#-*-coding:utf-8-*-

fromflaskimportrender_template,flash,redirect,session,url_for,request,g
fromappimportapp,db
frommodelsimportUser,Post,ROLE_USER,ROLE_ADMIN

@app.route(‘/’)
defindex():
returnrender_template(‘index.html’)

@app.route(‘/adduser/<nickname>/<email>’)
defadduser(nickname,email):
u=User(nickname=nickname,email=email)
try:
db.session.add(u)
db.session.commit()
return‘addsuccessful’
exceptException,e:
return‘somethinggowrong’

@app.route(‘/getuser/<nickname>’)
defgetuser(nickname):
user=User.query.filter_by(nickname=nickname).first()
returnrender_template(‘user.html’,user=user)

@app.errorhandler(404)
definternal_error(error):
returnrender_template(‘404.html’),404

@app.errorhandler(500)
definternal_error(error):
db.session.rollback()
returnrender_template(‘500.html’),500


这次我们使用了模板,新建(/app/templates/user.html)

<html>
<head>
<title>user</title>
</head>
<body>
<h1>user</h1>
<ul>
<li>user:{{user.nickname}}</li>
<li>email:{{user.email}}</li>
</ul>
</body>
</html>


最后运行

$pythonrun.py
用浏览器访问http://127.0.0.1:5000/adduser/username/useremail最后还可以用sqlite客户端打开查看我们的数据库变换。关于更多的数据库操作方法,请阅读sqlalchemy文档。
最后我们的代码目录结构如下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$tree
.
├──app
│├──__init__.py
│├──models.py
│├──static
│├──templates
││├──404.html
││├──500.html
││├──index.html
││└──user.html
│├──views.py
├──app.db
├──config.py
├──db_create.py
├──db_downgrade.py
├──db_migrate.py
├──db_repository
│├──__init__.py
│├──manage.py
│├──migrate.cfg
│├──README
│└──versions
│├──001_migration.py
│├──002_migration.py
│├──__init__.py
├──db_upgrade.py
├──requirements.txt
├──run.py
├──tmp


5表单
接下来,我们将要接触表单方面的内容。flask原生提供的表单处理,也比较简单。当然,有了轮子,我们就直接用好了。需要安装一个
Flask-WTF==0.9.4。
安装完之后,打开(/app/forms.py)

#-*-coding:utf-8-*-

importre
fromflask_wtfimportForm
fromflask_wtf.html5importEmailField
fromwtformsimportTextField,PasswordField,BooleanField
fromwtforms.validatorsimportDataRequired,ValidationError
frommodelsimportUser
fromhashlibimportmd5

classRegisterFrom(Form):
nickname=TextField(‘nickname’,validators=[DataRequired()])
email=EmailField(’email’,validators=[DataRequired()])
password=PasswordField(‘password’,validators=[DataRequired()])
conform=PasswordField(‘conform’,validators=[DataRequired()])

defvalidate_nickname(self,field):
nickname=field.data.strip()
iflen(nickname)<3orlen(nickname)>20:
raiseValidationError(‘nicknamemustbe3letteratleast’)
elifnotre.search(r’^\w+$’,nickname):
raiseValidationError(‘Usernamescancontainonlyalphanumericcharactersandunderscores.’)
else:
#验证是否已经注册
u=User.query.filter_by(nickname=nickname).first()
ifu:
raiseValidationError(‘Thenicknamealreadyexists’)

defvalidate_email(self,field):
email=field.data.strip()
email=User.query.filter_by(email=email).first()
ifemail:
raiseValidationError(‘Theemailalreadyexists’)

defvalidate_password(self,field):
password=field.data.strip()
iflen(password)<6:
raiseValidationError(‘passwordmustbe3letteratleast’)

defvalidate_conform(self,field):
conform=field.data.strip()
ifself.data[‘password’]!=conform:
raiseValidationError(‘thepasswordandconformaredifferent’)


上述代码定义了一个RegisterFromclass,正好是一个表单,class的字段映射为表单的域。其中wtforms中有TextField,PasswordField,BooleanField这些表单类型,flask_wtf.html5中EmailField则是html5规范的。
表单有form.validate_on_submit()方法,这个方法执行之前会验证表单域。验证方法和django类似,都是validate_field的方法。其中一个参数field正好是表单的value值需要注意验证两次密码是否相同的时候,需要用到self.data。这是一个字典,包含了表单域的name和value

6用户注册
打开(/app/views.py)添加注册方法。

@app.route(‘/account/signup’,methods=[‘GET’,‘POST’])
defsignup():
form=RegisterFrom()
ifrequest.method==‘POST’:
ifform.validate_on_submit():
psdmd5=md5(form.data[‘password’])
password=psdmd5.hexdigest()
u=User(nickname=form.data[‘nickname’],email=form.data[’email’],password=password)
try:
db.session.add(u)
db.session.commit()
flash(‘signupsuccessful’)

exceptException,e:
return‘somethinggoeswrong’
returnredirect(url_for(‘signin’))

returnrender_template(‘signup.html’,form=form)


模版新建(/app/tempaltes/signup.html)

<!–extendbaselayout–>
{%extends“base.html”%}

{%blockcontent%}
<formmethod=”POST”action=”>
{{form.hidden_tag()}}
<p>{{form.nickname.label}}{{form.nickname(size=20)}}</p>
<p>{{form.email.label}}{{form.email(size=20)}}</p>
<p>{{form.password.label}}{{form.password(size=20)}}</p>
<p>{{form.conform.label}}{{form.conform(size=20)}}</p>
<inputtype=”submit”value=”SignUp”>
<inputtype=”reset”value=”Reset”>
</form>

{{form.errors}}
{%endblock%}


其中,主要是用到form对象的一些属性。form.nickname.label是指表单类定义的名字,form.nickname会转变成表单域的html代码即<inputtype=”text”id=”nickname”name=”nickaname”value=””/>

7用户登录
先添加一个用户登录的表单,打开(/app/forms.py)

classLoginForm(Form):
nickname=TextField(‘nickname’,validators=[DataRequired()])
password=PasswordField(‘password’,validators=[DataRequired()])
remember_me=BooleanField(‘remember_me’,default=False)

defvalidate_nickname(self,field):
nickname=field.data.strip()
iflen(nickname)<3orlen(nickname)>20:
raiseValidationError(‘nicknamemustbe3letteratleast’)
elifnotre.search(r’^\w+$’,nickname):
raiseValidationError(‘Usernamescancontainonlyalphanumericcharactersandunderscores.’)
else:
returnnickname


用户登录,最简单的方法就是验证用户名,如果通过,则添加session,登出的时候,清除session即可,模版里面可以直接使用request.session用来判断用户登录状态
打开(/app/views.py)添加登录登出方法

@app.route(‘/account/signin’,methods=[‘GET’,‘POST’])
defsignin():
form=LoginForm()
ifrequest.method==‘POST’:
ifform.validate_on_submit():
nickname=form.data[‘nickname’]
psdmd5=md5(form.data[‘password’])
password=psdmd5.hexdigest()
u=User.query.filter_by(nickname=nickname,password=password).first()
ifu:
session[‘signin’]=True
flash(‘signinsuccessful’)
returnredirect(url_for(‘index’))
else:
flash(u’用户名或者密码错误’)
returnrender_template(‘signin.html’,form=form)

@app.route(‘/account/signout’)
defsignout():
session.pop(‘signin’,None)
flash(‘signoutsuccessful’)
returnredirect(‘index’)


注意,我们使用flask的时候,用了中文,就必须是unicode
除了原生的登录方式,我们还可以用flask-login。安装flask-login
此时需要初始化一个login_manager对象,打开(/app/__init__.py)

#-*-coding:utf-8-*-

importos
fromflaskimportFlask
fromflask.ext.sqlalchemyimportSQLAlchemy
fromflask.ext.loginimportLoginManager
fromconfigimportbasedir

app=Flask(__name__)

app.config.from_object(‘config’)

db=SQLAlchemy(app)#初始化数据库管理对象
login_manager=LoginManager()#初始化登录管理对象
login_manager.init_app(app)
login_manager.login_view=“signin”#登录跳转视图
login_manager.login_message=u”Bonvoluensalutiporuzitiopaĝo.”#登录跳转视图前的输出消息

#fromappimportviews,models

importviews,models


然后打开(/app/views.py)
添加下面方法,注释掉之前的登入登出方法

fromflask.ext.loginimportlogin_user,logout_user,current_user,login_required
fromappimportapp,db,login_manager

@login_manager.user_loader
defload_user(userid):
returnUser.query.get(userid)

@app.route(‘/account/signin’,methods=[‘GET’,‘POST’])
defsignin():
form=LoginForm()
ifrequest.method==‘POST’:
ifform.validate_on_submit():
nickname=form.data[‘nickname’]
psdmd5=md5(form.data[‘password’])
password=psdmd5.hexdigest()
remember_me=form.data[‘remember_me’]
user=User.query.filter_by(nickname=nickname,password=password).first()
ifuser:
login_user(user,remember=remember_me)
flash(‘signinsuccessful’)
returnredirect(request.args.get(“next”)orurl_for(“index”))
else:
flash(u’用户名或者密码错误’)
returnrender_template(‘signin.html’,form=form)

@app.route(‘/account/signout’)

@login_required
defsignout():
logout_user()
flash(‘signoutsuccessful’)
returnredirect(‘index’)


模版也会响应的改,具体看源码吧。
base.html

<html>
<head>
<metahttp-equiv=”Content-Type”content=”text/html;charset=utf-8″/>
{%iftitle%}
<title>{{title}}–microblog</title>
{%else%}
<title>microblog</title>
{%endif%}
</head>
<body>
<divclass="metanav">

<div>
<ahref=”{{url_for(‘index’)}}”>Home</a>
{%ifcurrent_user.is_authenticated()%}
<ahref=”{{url_for(‘signout’)}}”>signout</a>
{%else%}
<ahref=”{{url_for(‘signin’)}}”>signin</a>
{%endif%}
</div>

{%formessageinget_flashed_messages()%}
<divclass="flash">{{message}}</div>
{%endfor%}
{%blockcontent%}{%endblock%}
</body>
</html>


代码下载:https://github.com/rsj217/flask-extend/tree/master/project
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: