写在前面
从今天起开始学习Flask,主要参考书籍是米格尔·格林贝格的《Flask Web开发》,打算用四天的时间看完这本书的入门部分,把学习的过程每天更新在这篇文章里面,记录自己的学习过程,也给看到的人做一个参考吧。
用的是Python3的环境,主机系统为Windows10
第一天
环境的安装和虚拟环境的搭建
Windows10下使用virtualenv来创建虚拟环境
pip3 install virtualenv
接下来创建虚拟环境
virtualenv venv
如图可以看出,创建了一个名为venv的子文件夹 接下来进入到venc/Scripts,在这里打开cmd窗口(不能是powershell),执行activate命令 再执行
pip install flask
至此,我们便完成了虚拟环境的搭建和Flask的搭建
应用的基本架构及第一个应用
初始化
所有Flask应用都必需创建一个应用实例,实例是一个Flask类的对象,通常由以下代码创建:
from flask import Flask
app = Flask(__name__)
路由
客户端(例如浏览器)把请求发送给服务器,Web服务器再把请求发送给Flask应用,应用需要知道每个URL请求需要运行什么代码,所以我们需要有一个处理URL到函数之间的程序,这个程序就叫做路由。举一个经典写法:
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
此时,当我们访问域名主页时,便会自动执行index()函数。像index()函数这样处理入站请求的函数,就叫做视图函数
当然,我们发现生活中很多URL都含有可变部分,这时我们只需要在装饰器中使用特殊的语法就可。可看下面这个例子:
@app.route('/user/<name>')
def user(name):
return '<h1>Hello,{}!</h1>'.format(name)
如上述代码所示,我们便可以将动态参数name传入应用,路由中的动态部分默认使用字符串,不过也可以是其他类型,例如
/user/<int:id>
便只会匹配动态片段id为整数的URL,Flask支持在路由中使用string,int,float和path类型。path类型是一种特殊的字符串,和string类型不同的是它可以包含正斜线。
启动Web服务器
Flask自带Web开发服务器,我们可以通过app.run方法来启动它。
if __name__=='__main__':
app.run(host="0.0.0.0", port=1336)
其中,host为”0.0.0.0”指定为本机,port指定了端口(默认为5000)
第一个完整程序
有了上面的知识,我们便可以写出一个完整的程序啦!
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "<h>Hello Von!!</h>"
if __name__ == '__main__':
app.run(host='0.0.0.0',port=2000)
此时,我们访问本机的2000端口,便可以看到结果啦!
第二天
模板
在我们之前的第一个例子中,数据的流向是单项的,从服务端直接显示数据到用户端,但是在生活中我们经常需要从用户端获取数据,进行处理后再返回用户端。
这时我们就需要将业务逻辑和表现逻辑分开。这时我们采取模板来实现这样的操作。教科书般的话就不多说了,直接看代码吧。
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/<name>')
def index(name):
return render_template('index.html',name=name)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=2000)
此时,我们还需要在同个文件夹中,再建一个templates文件夹来保存我们的模板。
我们在里面创建一个index.html文件
<h1>Hello,</h1>
此时,当我们访问
127.0.0.1/von
就可以看到效果了。
变量
在模板中我们使用的结构表示一个变量,这时一种特殊的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。
Jinja2可以识别所有类型的变量,变量的值可以使用过滤器修改。
常用变量过滤器有如下:
过滤器名 | 说明 |
---|---|
safe | 渲染值时不转义 |
capitalize | 把值的首字母转换成大写,其他字母转化成小写 |
lower | 把值转化为小写 |
upper | 把值转化为大写 |
title | 把值中每个单词的首字母转化为大写 |
trim | 把值的首尾空格删掉 |
控制结构
条件判断语句
直接看代码:
<h1>Hello, Stranger!</h1>
循环语句
<ul>
</ul>
自定义错误页面
写法如下:
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'),404
第三天
Web表单
在这里我们需要一个拓展flask-wtf
pip install flask-wtf
配置
我们首先需要在代码中配置一个SECRET_KEY来防止CSRF攻击。
app.config['SECRET_KEY']='Flask is great'
app.config字典可用于存储Flask,拓展和应用自身的配置变量。
表单类
使用Flask-WTF时,在服务器端,每个Web表单都由一个继承自FlaskForm的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数用于验证用户提交的数据是否有效。具体的我们来看看代码:
from flask_wtf import FlaskForm
from wtforms import StringField,SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
name = StringField('What is your name?',validators=[DataRequired()])
password = StringField('Password',validators=[DataRequired()])
submit = SubmitField('Submit')
在该示例中,NameForm表单中有一个名为name的文本字段和一个名为submit的提交按钮。StringField类表示属性为type=”text”的HTML<input>元素。SubmitField类表示属性为type=”submit”的HTML<input>元素。字段构造函数的第一个参数是把表单渲染成HTML时使用的标注(label)。
StringField构造函数中的可选参数validators指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数DataRequired()确保提交的字段内容不为空。
WTForms支持的HTML标准字段
字段类型 | 说明 |
---|---|
StringField | 文本字段, 相当于type类型为text的input标签 |
TextAreaField | 多行文本字段 |
BooleanField | 复选框,值为True或False |
DateField | 文本字段,值为datetime.date格式 |
DateTimeField | 文本字段,值为datetime.datetime格式 |
DecimalField | 文本字段,值为decimal.Decimal |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
SubmitField | 表单提交按钮 |
IntegerField | 文本字段, 值为整数 |
FloatField | 文本字段, 值为浮点数 |
FileField | 文件上传字段 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表, 可选择多个值 |
WTForms验证函数 |验证函数|说明| |—|—| |DataRequired|确保字段中有数据| |Email|验证是电子邮件地址| |EqualTo|比较两个字段的值; 常用于要求输入两次密钥进行确认的情况| |IPAddress|验证IPv4网络地址| |Length|验证输入字符串的长度| |NumberRange|验证输入的值在数字范围内| |Optional|无输入值时跳过其它验证函数| |URL|验证URL| |AnyOf|确保输入值在可选值列表中| |NoneOf|确保输入值不在可选列表中|
把表单渲染成HTML
直接看代码:
<form method="POST">
</form>
注意:除了name,password和submit字段,这个表单还有个form.csrf_token()元素。这个元素生成了一个隐藏的字段,供CSRF防护机制使用。
在视图函数中处理表单
老规矩,先看代码:
from flask_wtf import FlaskForm
from wtforms import StringField,SubmitField
from wtforms.validators import DataRequired
app.secret_key ='Von' #secret_key不可缺少
class NameForm(FlaskForm):
name = StringField('What is your name?',validators=[DataRequired()])
password = StringField('Password',validators=[DataRequired()])
submit = SubmitField('Submit')
@app.route('/login',methods=['GET','POST'])
def run():
login_form = NameForm()
return render_template('index.html',form=login_form)
没什么好说的,就是实现表单类然后传入而已,需要注意的是装饰器里面多出的methods参数告诉Flask,在URL映射中把这个视图函数注册为GET和POST请求的处理程序。如果没有指定methods参数,则只把视图函数注册为GET请求的处理程序。
进一步的逻辑验证
@app.route('/login',methods=['GET','POST'])
def run():
login_form = NameForm()
if form.validate_on_submit():
return render_template('index.html',form=login_form)
使用了form.validate_on_submit()可以让系统判断是否满足所有的验证函数。同时,如果我们想获取某个参数的值,我们可以通过例如:
name = form.name.data
来获取
第四天
数据库
在Flask中,我们可以使用Flask-Sqlalchemy来管理数据库。
pip install flask-sqlalchemy
如果连接的是mysql数据库,需要安装mysqldb
pip install flask-mysqldb
Sqlalchemy实际上是对数据库的抽象,让开发者不用直接和SQL语句打交道。 首先我们要在服务器端创建一个数据库。
配置数据库
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
#配置数据库的地址,标准格式为'mysql://username:password@hostname/database'
app.config['SQLALCHEMY_DATABASE_URI']='mysql://root:mysql@127.0.0.1/flask_sql'
#跟踪数据库的修改(不建议开启,未来的版本会被移除)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
db=SQLAlchemy(app)
定义模型
Flask_SQLAlchemy创建的数据库实例为模型提供了一个基类及一系列辅助类和辅助函数,可用于定义模型的结构。 我们通常把表定义成一个模型。
class Role(db.Model):
__tablename__='roles'
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(64),unique=True)
class User(db.Model):
__tablename__='users'
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(64),unique=True,index=True)
__tablename__定义在数据库使用的表名。db.Column类构造函数的第一个参数是数据库列和模型属性的类型。以下列出了一些常用的列类型。
类型名 | Python类型 | 说明 |
---|---|---|
Integer | int | 普通整数,通常为32位 |
SmallInteger | int | 取值范围小的整数,通常为16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
String | str | 变长字符串 |
Text | str | 优化了的变长字符串 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 优化的Unicode字符串 |
Boolean | bool | 布尔值 |
db.Column的其余参数指定属性的配置选项。下表列出了一些可用选项.
选项名 | 说明 |
---|---|
primary_key | 如果设为True,列为表的主键 |
unique | 如果设为True,列不允许出现重复的值 |
index | 如果设为True,为列创建索引,提升查询效率 |
nullable | 如果设为True,列允许使用空值,如果设为False,列不允许使用空值 |
default | 为列定义默认值 |
关系
关系型数据库使用关系将不同表中的行联系起来。我们先来了解一下一对多关系,比如角色到用户的一对多关系,因为一个角色可属于多个用户,而每个用户只有一个角色。
class User(db.Model):
#...
role_id = db.Column(db.Integer,db.ForeignKey('roles.id'))
关系使用users表中的外键连接两行,表明传给db.Foreignkey()的参数’roles.id’这列的值是roles表中相应行的id值。
数据库操作
创建表
db.create_all()函数将寻找所有db.Model的子类,然后在数据库中创建对应的表.但是如果数据库表已存在于数据库中,那么db.create_all()不会重新创建或更新相应的表。如果修改模型后要将改动应用到现有数据库中,这一行为将带来不便,更新现有数据库的蛮力方式是先删除旧表再重新创建。
db.drop_all()
db.create_all()
遗憾的是,这个方法有个我们不想看到的副作用,就是数据库原有的数据都消失了,可谓得不偿失。接下来我们会介绍较好的更新方法。
插入行
我们先建立用户对象
admin_role = Role(name='Admin')
john = Role(name='John')
需要注意的是id属性我们不用设置,因为在多数数据库中主键由数据库本身管理。 接下来把对象提交到会话。
db.session.add(admin_role)
db.session.add(john)
#也可以简写成:
db.session.add_all([admin_role,john])
为了将对象写入数据库,我们要调用commit()方法提交会话:
db.session.commit()
我们直接来看相关代码:
#...
if __name__=='__main__':
db.drop_all()
db.create_all()
admin_role = Role(name='Admin')
guest = Role(name='Guest')
user_admin = User(username='Von',role_id=admin_role.id)
db.session.add_all([admin_role,guest])
db.session.commit()
user_admin = User(username='Von',role_id=admin_role.id)
db.session.add(user_admin)
db.session.commit()
可以看出,已经成功在数据库中添加了相关用户.
修改行
如果我们要修改行模型也很简单
user_admin.username = F1ash
db.session.add(user_admin)
db.session.commit()
删除行
数据库会话还提供了delete()方法。下面我们将”F1ash”角色删除.
db.session.delete(user_admin)
db.session.commit()
注意,删除与插入和更新一样,提交数据库会话后才能执行.
查询行
Flask-SQLAlchemy为每个模型类提供了query对象。最基本的模型查询是使用all()方法取回对应表中的所有记录:
>>> User.query.all()
>>> [<User 'Admin'>, <User 'John'>, <User 'Koo'>, <User 'Su'>, <User 'Von'>]
使用过滤器可以配置query对象进行更精确的数据库查询。
>>> User.query.filter_by(username='Von').all()
>>> [<User 'Von'>]
除了all()查询方法,我们还有其他的查询执行方法.
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果没有结果,返回NONE |
first_or_404 | 返回查询的第一个结果,如果没有结果,则终止请求,返回404响应 |
get() | 返回主键对应的行,如果没有行,则返回NONE |
get_or_404 | 返回主键对应的行,如果没有行,则终止请求,返回404响应 |
count() | 返回查询结果的数量 |
在视图函数中操作数据库
这里是对前面知识的大总结,我们将结合前一天学的表单的知识,实现将用户名密码存储进数据库中,建议大家和我一样手动写一个(书上没有)
# 完整代码如下
from flask import Flask,render_template
from flask_wtf import FlaskForm
from wtforms import StringField,SubmitField
from wtforms.validators import DataRequired,EqualTo
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.secret_key ='linnue'
app.config['SQLALCHEMY_DATABASE_URI']='mysql://root:mysql@127.0.0.1/flask_sql'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
db=SQLAlchemy(app)
class NameForm(FlaskForm):
name = StringField('What is your name?',validators=[DataRequired()])
passw = StringField('Password',validators=[DataRequired()])
submit = SubmitField('Submit')
class User(db.Model):
__tablename__ = 'usernames'
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(64))
password = db.Column(db.String(64))
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'),404
@app.route('/login',methods=['GET','POST'])
def run():
login_form = NameForm()
if login_form.validate_on_submit():
username = login_form.name.data
password = login_form.passw.data
guest = User(username=username,password=password)
db.session.add(guest)
db.session.commit()
return render_template('index.html',form=login_form)
if __name__ == '__main__':
db.drop_all()
db.create_all()
app.run(debug=True,host='0.0.0.0',port=5000)
好了,到这里我们学习Flask入门的过程已经告一段落,我们学会了基本的Flask操作,可以编写简单的Flask程序,下一部分我们将学习用Flask编写项目进行实战。