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.py
flask 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 :(”,我们需要捕获里面的错误进行显示。