0x00 安装环境
sudo apt install python3-venv
0x01 虚拟环境
- 创建目录:
mkdir my_flask_app && cd my_flask_app - 创建新的虚拟环境:
python3 -m venv venv - 运行
activate脚本来激活它:source venv/bin/activate - 安装Flask:
pip install Flask
0x02 HelloFlask
from flask import Flask
# 创建Flask应用程序实例
app = Flask(__name__)
# Flask中定义路由是通过装饰器实现的
@app.route('/')
def hello_world():
#除了字符串还可以返回模板(网页)内容,后面会学。
return 'Hello World!'
if __name__ == '__main__':
app.run()
export FLASK_APP=hello.pyflask run
输出类似如下的内容:
* Serving Flask app "hello.py" * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
定义其他的路径:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "Hello World!<br>I am cominig!"
@app.route('/test')
def test():
return "This is a test!"
if __name__ == '__main__':
app.run()
排错:
- 物理机访问不了:
- sudo ufw allow 5000
- flask run –host=[虚拟机IP地址]
0x03 请求方式
默认只支持GET,如果需要增加,需要以列表方式自行指定。
from flask import Flask
app = Flask(__name__)
@app.route('/',methods=['GET'])
def hello_world():
return "Hello World!<br>I am cominig!"
@app.route('/test',methods=['GET','POST'])
def test():
return "This is a test!"
if __name__ == '__main__':
app.run()
例如这时,以POST形式访问根目录会出现:
Method Not Allowed The method is not allowed for the requested URL.
0x04 传参示例
使用同一个视图函数,来显示不同页数的文本内容。路由传递的参数默认当string处理。
from flask import Flask
app = Flask(__name__)
@app.route('/',methods=['GET'])
def hello_world():
return "A Easy Book."
# <>定义路由的参数,<>内需要起一个名字
@app.route('/books/<book_id>',methods=['GET','POST'])
def get_book_id(book_id):
# 需要在视图函数()内填入形参名,后面的代码才能使用。
return "what you look is page %s" % book_id
if __name__ == '__main__':
app.run()
优化,对传参的参数类型进行一个限定。
@app.route('/books/<int:book_id>',methods=['GET','POST'])
这样接收到数字以外的字符会返回404 NOT FOUND。
除了int,还可以改为float等。
0x05 返回模板
在my_flask_app中新建templates文件夹,现在路径树如下:
├── hello.py ├── templates └── venv
在 templates文件夹中新建index.html,随便写写:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ShawRoot</title> </head> <body> <h1>My First WebSite</h1> <p>Could not more easy than this :)</p> </body> </html>
在hello.py中导入render_template:
from flask import Flask , render_template
app = Flask(__name__)
@app.route('/',methods=['GET'])
def hello_world():
return render_template('index.html')
if __name__ == '__main__':
app.run()
0x06 填充数据
如果不想一开始就把模板写死,可以使用{{ }},例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<h1>My First WebSite</h1>
<p>My Blog:{{ urlstr }}</p>
</body>
</html>
然后在hello.py中给urlstr赋值。
from flask import Flask , render_template
app = Flask(__name__)
@app.route('/',methods=['GET'])
def hello_world():
url = "shawroot.cc"
return render_template('index.html',urlstr=url)
if __name__ == '__main__':
app.run()
这样访问网站根目录{{ urlstr }}就被替代为shawroot.cc。
0x07 变量进阶
- 注释:
{#我是一个没什么用的注释。#}
- 取列表(
list_example = [1,3,5,7,9])的下标:{{ list_example.2 }}或{{ list.example[2] }}
- 取字典,hello.py中内容如下
from flask import Flask , render_template
app = Flask(__name__)
@app.route('/',methods=['GET'])
def hello_world():
url = "shawroot.cc"
my_dict = {
'name':'ShawRoot',
'Age':17,
'Job':'Stuent'
}
return render_template('index.html',urlstr=url,info=my_dict)
if __name__ == '__main__':
app.run()
调用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<h1>My First WebSite</h1>
<p>My Blog:{{ urlstr }}</p>
<p>My Name:{{ info['name'] }}</p>
<p>My Age:{{ info['Age'] }}</p>
</body>
</html>
0x08 控制代码
- 使用{% %}来控制代码块。例如,实现字典的循环遍历,给它打印出来(列表同理)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<h1>My First WebSite</h1>
<hr>
{#简单来一个for循环。#}
{% for i in info%}
{{ i }} <br>
{% endfor %}
</body>
</html>
- 参考:https://www.codenong.com/24163579/。实现字典的循环遍历,输出长度大于4的内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<h1>My First WebSite</h1>
<hr>
{% for i in info%}
{% if i|length >=4 %}
{{ i }} <br>
{% endif %}
{% endfor %}
</body>
</html>
0x09 用过滤器
过滤器本质就是函数。在模板中不能调用python的某些方法,这时就用到了过滤器。
- 过滤器使用的格式:
变量名|过滤器 - 常见的过滤器:https://blog.csdn.net/rusi__/article/details/102805022
- 过滤器支持链式调用,例如
{{ 'shawroot' | reverse | upper }}
0x0A 表单验证
例如,如果想用普通方法实现一个简单的登录表单,模板中的内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<form method="post">
<label>username:</label><input type="text" name="username"><br>
<label>password:</laber><input type="password" name="password"><br>
<label>confirm password:</laber><input type="password" name="password2"><br>
<input type="submit" value="submit"><br>
</form>
</body>
</html>
在视图函数中需要检查三个字段(username、password、password2)是否为空,以及“输入密码”和“确认密码”字段的内容是否相同。引入request库,判断请求类型,获取参数值的方法如下所示:
from flask import Flask , render_template , request
app = Flask(__name__)
@app.route('/',methods=['GET','POST'])
def index():
# 判断请求方式
if request.method == 'POST':
# 获取请求参数
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
# 判断请求参数是否为空
if username != "" and password != "" and password2 != "":
# 两次输入的密码是否相同
if password != password2:
return 'Please Check Out Your Password.'
else:
return 'Login Success!'
else:
return 'NOT ALLOWED.'
return render_template('index.html')
if __name__ == '__main__':
app.run()
如果希望给模板中传递消息(即return 'Login Success!'更改为flash( 'Login Success!' )),可以导入flash库。
然后在模板底部需要遍历所有消息:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<form method="post">
<label>username:</label><input type="text" name="username"><br>
<label>password:</laber><input type="password" name="password"><br>
<label>conform password:</laber><input type="password" name="password2"><br>
<input type="submit" value="submit"><br>
{# 使用遍历获取闪现的消息。#}
{# 因为是函数,所以别忘记get_flashed_messages后要加()。#}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor%}
</form>
</body>
</html>
flash闪现的消息是需要保密的,要设置一个secret_key,做一个加密消息的混淆(不设置会报错):
from flask import Flask , render_template , request , flash
app = Flask(__name__)
app.secret_key = "shawroot"
@app.route('/',methods=['GET','POST'])
def index():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
if username != "" and password != "" and password2 != "":
if password != password2:
flash('Please Check Out Your Password.')
else:
flash('Login Success!')
else:
flash('NOT ALLOWED.')
return render_template('index.html')
if __name__ == '__main__':
app.run()
这样,内容即可在表单下面回显。
0x0B WTF表单
- 安装Flask-WTF扩展:
pip install Flask-WTF - 在Flask中,为了处理web表单,一般使用Flask-WTF扩展。它封装了WTForms,具有验证表单数据的功能。
- 常用的支持的HTML标准字段如下:
| 字段类型 | 说明 |
| StringField | 文本字段 |
| TextAreaField | 多行文本字段 |
| PasswordField | 密码文本字段 |
| HiddenField | 隐藏文本字段 |
| DateField | 文本字段,值为datetime.date格式 |
| IntegerField | 文本字段,值为整数 |
| FloatField | 文本字段,值为浮点数 |
| BooleanField | 复选框,值为True和False |
| RadioField | 一组单选框 |
| SelectField | 下拉列表 |
| SelectMultipleField | 下拉列表,可选择多个值 |
| FileField | 文件上传字段 |
| SubmitField | 表单提交按钮 |
使用WTF时,会在视图函数中定义类,在类中说明要写入的表单的格式:
from flask import Flask,render_template,request,flash
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
app = Flask(__name__)
app.secret_key = "shawroot"
class LoginForm(FlaskForm):
username = StringField('UserName:')
password = PasswordField('Password:')
confirm = PasswordField('Confirm Password:')
submit = SubmitField('Submit')
@app.route('/',methods=['GET','POST'])
def login():
login_form = LoginForm()
return render_template('index.html',form=login_form)
if __name__ == '__main__':
app.run()
然后在模板中使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<form method="post">
{{ form.username.label }}{{ form.username }} <br>
{{ form.password.label }}{{ form.password }} <br>
{{ form.confirm.label }}{{ form.confirm }} <br>
{{ form.submit }}
</form>
</body>
</html>
0x0C WTF验证
0x0b只介绍了WTF表单如何放置在模板中,但是没有介绍如何进行逻辑验证。
因为是登录表单,要确保用户名、密码、确认密码字段不能为空。这时需要引用WTForms提供的Validators来导入验证函数:
from wtforms.validators import DataRequired,EqualTo
常见的验证函数如下:
| 验证函数 | 说明 |
| DataRequired | 确保字段中有数据 |
| 验证电子邮件地址 | |
| EqualTo | 比较两个字段的值,常用于要求输入两次密码进行确认的情况 |
| IPAddress | 验证IPv4网络地址 |
| Length | 验证输入字符串的长度 |
| NumberRange | 验证输入的值在数字范围内 |
| Optional | 无输入值时跳过其他验证函数 |
| Regexp | 使用正则表达式验证输入值 |
| URL | 验证URL |
| AnyOf | 确保输入值在可选值列表中 |
| NoneOf | 确保输入值不在可选列表中 |
一个变量可以导入多个验证函数,EqualTo('需要比较的另一个字段','不一致时显示的消息'):
class LoginForm(FlaskForm):
username = StringField('UserName:',validators=[DataRequired()])
password = PasswordField('Password:',validators=[DataRequired()])
confirm = PasswordField('Confirm Password:',validators=[DataRequired(),EqualTo('password','NOT MATCH!')])
submit = SubmitField('Submit')
还需要判断请求方式,WTF表单可以一句代码搞定验证逻辑。
- 如果校验成功(
if login_form.validate_on_submit())判断就会返回真。 - 校验成功的前提还需要加上CSRF Token,需要在表单前面开启CSRF(
{{ form.csrf_token() }})。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>
<form method="post">
{{ form.csrf_token() }}
{{ form.username.label }}{{ form.username }} <br>
{{ form.password.label }}{{ form.password }} <br>
{{ form.confirm.label }}{{ form.confirm }} <br>
{{ form.submit }}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor%}
</form>
</body>
</html>
视图函数代码如下:
from flask import Flask,render_template,request,flash
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
from wtforms.validators import DataRequired,EqualTo
app = Flask(__name__)
app.secret_key = "shawroot"
class LoginForm(FlaskForm):
username = StringField('UserName:',validators=[DataRequired()])
password = PasswordField('Password:',validators=[DataRequired()])
confirm = PasswordField('Confirm Password:',validators=[DataRequired(),EqualTo('password','NOT MATCH!')])
submit = SubmitField('Submit')
@app.route('/',methods=['GET','POST'])
def login():
login_form = LoginForm()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
confirm = request.form.get('confirm')
if login_form.validate_on_submit():
return 'Login Success!'
else:
flash('Something Wrong :(')
return render_template('index.html',form=login_form)
if __name__ == '__main__':
app.run()
需要注意的是,如果这时在表单输入不一致的密码,返回的不是“NOT MATCH”而是“Something Wrong :(”,我们需要捕获里面的错误进行显示。