0%

flask学习笔记(六)

表单相关[使用方法]

在flask学习笔记(三)请求与响应一节中提到了表单与视图函数数据交互的过程。在MVC架构中[其实flask不能算严格意义上的MVC,这里暂且将其视作MVC] 我们已经详细介绍了Controller,即视图函数;View,即模板。但我们发现了一个问题,模板与视图函数的交互是双向的:模板渲染视图函数传递的数据;把请求中获取的数据交给视图函数处理,为了更好的学习后者,我们需要学习表单相关内容。

简单的HTML表单&后端交互
index.html

1
2
3
4
5
6
7
<form method="post" action="http://localhost:5000/index">
<label for="username">用户名</label><br>
<input type="text" name="username" placeholder="用户名"><br>
<label for="password">密码</label><br>
<input type="password" name="password" placeholder="密码"><br>
<input type="submit" name="submit" value="登录">
</form>

primary-form.py

1
2
3
4
5
@app.route('/index', methods=['POST', 'GET'])
def index():
print(request.form.get('username'))
print(request.form.get('password'))
return '登录成功'

直接编写html不仅需要花费学习成本,且会增加代码量和降低代码可读性,所以引入了一些插件库来辅助编写表单。
WTForms
app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask, render_template, request
from wtforms import Form, StringField, PasswordField
from wtforms import validators

app = Flask(__name__, template_folder="templates")

class LoginForm(Form):
username = StringField(label="用户名", validators=[validators.DataRequired(message="用户名不能为空"), ],)
password = PasswordField(label="密码", validators=[validators.DataRequired(message="密码不能为空"), ],)


@app.route('/login', methods=["GET", "POST"])
def login():
if request.method == "GET":
form = LoginForm()
return render_template("login.html", form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
if form.username.data == 'lockcy' and form.password.data == 'lockcy':
return render_template('success.html', username=form.username.data)
return render_template('fail.html', username=form.username.data)
else:
print(form.errors, "错误信息")
return render_template("login.html", form=form)

if __name__ == '__main__':
app.run(debug=True)

login.html

1
2
3
4
5
<form method="post">
<p>{{form.username.label}} {{form.username}} {{form.username.errors[0] }}</p>
<p>{{form.password.label}} {{form.password}} {{form.password.errors[0] }}</p>
<input type="submit" value="提交">
</form>

常用的WTForms字段

字段类 说明 对应的HTML标识
BooleanField 复选框,值会被处理为True或False <input type=”checkbox”>
DateField 文本字段,值会被处理为datetime.date对象 <input type=”text”>
DateTimeField 文本字段,值会被处理为datetime.datetime对象 <input type=”text”>
StringField 文本字段 <input type=”text”>
FileField 文件上传字段 <input type=”file”>
FloatField 浮点数字段,值会被处理为浮点型 <input type=”text”>
PasswordField 密码文本字段 <input type=”password”>
StringField 文本字段 <input type=”text”>
IntegerField 整数字段 <input type=”text”>
RadioField 一组单选框 <input type=”radio”>
SelectField 下拉列表 <select><option></option></select>
SelectMutipleField 多选下拉列表 <select multiple><option></option></select>
SubmitField 表单提交按钮 <input type=”submit”>
TextAreaField 多行文本字段 <textarea></textarea>
HiddenField 隐藏文本字段 <input type=”hidden”>

常用的WTForms验证器

验证器 说明
DataRequired(message=None) 验证数据是否有效
Email(message=None) 验证Email地址
EqualTo(fieldname, message=None) 验证两个字段值是否相同
InputRequired(message=None) 验证是否有数据
IPAddress(ipv4=True, ipv6=False, message=None) 验证IP地址是否合法,python2需要ipaddress包支持
Length(min=- 1, max=- 1, message=None) 验证输入的长度是否在给定范围之内
MacAddress(message=None) 验证mac地址是否合法
NumberRange(min=None, max=None, message=None) 验证输入数字是否在给定范围之内
Optional(strip_whitespace=True) 允许输入值为空,并跳过其他验证
Regexp(regex, flags=0, message=None) 正则表达式验证
URL(require_tld=True, message=None) 验证URL
AnyOf(values, message=None, values_formatter=None) 确保输入值在可选值列表中
NoneOf(values, message=None, values_formatter=None) 确保输入值不在可选值列表中

Flask-WTF
Flask-WTF 提供了简单地 WTForms 的集成。
app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, validators
from flask import Flask, render_template, request

app = Flask(__name__, template_folder="templates")
app.secret_key = 'test' # flaskform使用csrf_token,必须设置secret_key
app.debug = True

class LoginForm(FlaskForm):
username = StringField(label="用户名", validators=[validators.DataRequired(message="用户名不能为空"), ],)
password = PasswordField(label="密码", validators=[validators.DataRequired(message="密码不能为空"), ],)

@app.route('/login', methods=["GET", "POST"])
def login():
if request.method == "GET":
form = LoginForm()
return render_template("login.html", form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
if form.username.data == 'lockcy' and form.password.data == 'lockcy':
return render_template('success.html', username=form.username.data)
return render_template('fail.html', username=form.username.data)
else:
print(form.errors, "错误信息")
return render_template("login.html", form=form)

if __name__ == '__main__':
app.run(debug=True)

login.html

1
2
3
4
5
6
<form method="post">
<p>{{form.csrf_token }}</p>
<p>{{form.username.label}} {{form.username}} {{form.username.errors[0] }}</p>
<p>{{form.password.label}} {{form.password}} {{form.password.errors[0] }}</p>
<input type="submit" value="提交">
</form>

是不是感觉和WTForms几乎一样,但仔细看还是能发现不同。
即使我们没有手动设置csrf_token,flaskform在表单中也为我们设置了防止跨站请求的token,csrf_token需要secret_key为其签名,因此需要设置secret_key。再查看html源码

1
2
3
<p><input id="csrf_token" name="csrf_token" type="hidden" value="ImIwMjA1MWU5OTZhNzU4MWZiYzNiOWQ5ODg2ZDNkNjIwZmQ2MTY3ZmYi.YEcsbA.Gcgdx7CHkKZKW4ti13M699HIbD0"></p>
<p><label for="username">用户名</label> <input id="username" name="username" required type="text" value=""> </p>
<p><label for="password">密码</label> <input id="password" name="password" required type="password" value=""> </p>

其他配置
禁用 CSRF 保护:
form = Form(csrf_enabled=False)
全局禁用 CSRF 保护:
WTF_CSRF_ENABLED = False
单独密钥:
WTF_CSRF_SECRET_KEY = ‘test’

app.config[‘WTF_CSRF_ENABLED’] = False
app.config[‘WTF_CSRF_ENABLED’] = False

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from flask import Flask, render_template, flash
from werkzeug.utils import secure_filename
from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_wtf import FlaskForm
from wtforms import SubmitField

app = Flask(__name__)
app.debug = True
app.secret_key = 'test'
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024

class UploadForm(FlaskForm):
"""用户上传文件的表单"""
file = FileField(label="图片", validators=[FileRequired(), FileAllowed(['jpg', 'png'], '只能上传图片')])
submit = SubmitField()

@app.route('/upload/', methods=('GET', 'POST'))
def upload():
form = UploadForm()
if form.validate_on_submit():
filename = secure_filename(form.file.data.filename)
form.file.data.save('uploads/' + filename)
flash('文件上传成功')
else:
filename = None
flash('文件上传失败')
return render_template('upload.html', form=form, filename=filename)

if __name__ == '__main__':
app.run()

upload.html

1
2
3
4
5
6
7
8
9
{% for message in get_flashed_messages() %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
<form method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
{{ form.file }}<br>
{{ form.submit }}<br>
</form>

Flask-CKEditor富文本编辑器
引入CKEditor资源
为了使用CKEditor,我们首先要在模板中引入CKEditor的JavaScript等资源文件。推荐的做法是自己编写资源引用语句,你可以在CKEditor提供的Online Builder构建一个自定义的资源包,下载解压后放到项目的static目录下, 并引入资源包内的ckeditor.js文件,比如(实际路径按需调整):

1
<script src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask_ckeditor import CKEditor, CKEditorField
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, validators
app = Flask(__name__)
app.debug = True
app.secret_key = 'test'
ckeditor = CKEditor(app)

class RichTextForm(FlaskForm):
title = StringField('标题', validators=[validators.DataRequired()])
body = CKEditorField('正文', validators=[validators.DataRequired()])
submit = SubmitField('发布')

@app.route('/ckeditor', methods=["GET", "POST"])
def ckeditor():
form = RichTextForm()
if request.method == 'GET':
return render_template('ckeditor.html', form=form)
else:
print(form.body.data)
return render_template('ckeditor.html', form=form)

if __name__ == '__main__':
app.run()

ckeditor.html

1
2
3
4
5
{{ form.csrf_token }}
{{ form.title.label }} {{ form.title() }}
{{ form.body.label }} {{ form.body() }}
{{ form.submit }}
{{ ckeditor.config(name='body') }}

效果如下

WTForms文档:https://wtforms.readthedocs.io/en/2.3.x/
Flask-wtf文档:http://www.pythondoc.com/flask-wtf/
flask-ckeditor使用: http://greyli.com/flask-ckeditor-integrate-rich-text-editor/