本次主要介绍下Web表单。
尽管Flask的请求对象提供的对象足够用于处理Web表单(如:request.form能获取POST请求中提交的表单数据),但有些任务很单调,而且要重复操作。比如生成表单的HTML代码和验证提交的表单数据。
Flask-WTF(http://pythonhosted.org/Flask-WTF)扩展可以把处理Web表单的过程编程一种愉快的体验。这个扩展对独立的WTForms(http://wtforms.simplecodes.com)包进行了包装,方便集成到Flask程序中。
0.安装Flask-WTF
pip install flask-wtf
1.跨站请求伪造保护
默认情况下,Flask-WTF能保护所有表单面免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。恶意网站把请求发送到被攻击者已登录的其他网站是就会引发CSRF攻击。
为了实现CSRF保护,Flask-WTF需要程序设置一个密钥。Flask-WTF使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。
设置方式
app = Flask(__name__)
app.config['SECRET_KEY'] = '这里设置密钥'
注意:为了增强安全性,密钥不应该直接写入代码,而要保存在环境变量中。
2.表单类
index.html文件:
<html>
<head>
<title>首页</title>
</head>
<body>
{% if name %}
<h1>Hello,{{ name }}!</h1>
{% else %}
<h1>Hello Stranger!</h1>
{% endif %}
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
</body>
</html>
解释下:
0.如果有
name
则输出Hello,xxx!
,没有name
则输出Hello,Stranger!
1.
form.hidden_tag()
是一个隐藏字段的标识字符串2.
form.name()
是下面app.py中NameForm类的一个name字段(输入文本框),form.name.label
则是name字段中的第一个参数值字符串3.同理
form.submit()
是提交按钮
app.py 文件:
from flask import Flask,render_template
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
name = nameForm.name.data
nameForm.name.data = ''
return render_template('index.html',form=nameForm,name=name)
if __name__ == '__main__':
app.run(debug=True)
解释下:
0.NameForm类,声明了一个输入姓名文本框和提交按钮。字段构造函数第一个参数吧表单渲染成HTML时使用的标号。其中输入姓名的文本框需要必须填写。
1.app.route修饰器中添加的methods参数告诉Flask在URL映射中把这个视图函数注册为GET和POST请求的处理程序,如果没有指定methods参数,就只把视图函数注册为GET请求处理程序。因为Web表单提交为POST方式,故需要显示标明。
2.
nameForm.validate_on_submit()
方法,提交表单后,如果数据鞥被所有验证函数接受,那么nameForm.validate_on_submit()
方法返回True,否则返回False。
到这里就可以看见下图:
flask_04_01.png在文本框中输入Tom看见下图:
flask_04_02.png3.优化
显示的界面以及功能都比较粗糙,主要存在以下几个问题:
0.界面很简陋,没有优美的CSS样式
1.提交完数据后,按快捷键刷新浏览器会出现弹框提示:是否重新提交表单数据
2.添加提示指示
0.界面显示优化
前面介绍了Flask-Bootstrap这个优秀的前端显示方案,完全可以拿过来用,并且Flask-Bootstrap还提供了一个非常高端的辅助函数,可以使用Bootstrap中预先定义好的表单演示渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。
前面介绍了base.html这里就不提及了。
index.html文件:
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}首页 {% endblock %}
{% block page_content %}
<div class="page-header">
{% if name %}
<h1> Hello,{{ name }}! </h1>
{% else %}
<h1>Hello,Stranger!</h1>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
解释下:
0.
wtf.quick_form()
函数的参数为Flask-WTF表单对象,使用Bootstrap样式渲染传入的表单。
运行效果如下图:
flask_04_03.png1.解决重复提交出现弹框问题
之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。
解决方案:不让Web程序把POST请求作为浏览器发送的最后一个请求——重定向。
修改app.py文件
from flask import Flask,render_template,session,url_for,redirect
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
session['name'] = nameForm.name.data
nameForm.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',form=nameForm,name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)
解释下:
0.
session['name']
存储了同一个会话的中文本框中的内容1.
url_for()
,当然redirect(url_for('index'))
也可以写成redirect('/')
,但是推荐使用url_for()
生成URL,因为这个函数使用URL映射生成URL,从而保证URL和定义的路由兼容,而且修改路由名字后依然后依然可用。``url_for()```函数的第一个且唯一必须指定的参数是端点名,即路由的内部名字,默认情况下,路由的端点是相应视图函数的名字,在这个示例中,处理跟地址的视图函数是index(),因此传给url_for()函数的名字是index。2.
session.get('name')
直接从会话中中读出name参数的值,也可以使用session['name']
读取,但是推荐使用session.get('name')
,使用get()获取字典中键对应的值以避免未找到键的异常情况,因为对于不存在的键,get()会返回默认值None.
2.添加提示
请求完成后,有时需要让用户知道状态发生了变化。
这种提示功能是Flask的核心特性,flash()
函数可实现这种效果。
app.py
from flask import Flask,render_template,session,url_for,redirect,flash
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
newName = nameForm.name.data
if newName == session.get('name'):
session['name'] = nameForm.name.data
nameForm.name.data = ''
return redirect(url_for('index'))
else:
flash('名字输入错误!')
return render_template('index.html',form=nameForm,name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)
base.html 文件
{% extends "bootstrap/base.html" %}
{% block title %}Flasky {% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
实现功能:如果用户输入的名字,不是第一次输入表单中的名字,则会出现名字输入错误提示。
解释下:
get_flashed_messages()
函数用来获取并渲染的消息。
网友评论
在浏览器输入名字 ,submit后就会报错:
TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given
求教是什么原因?
reload(sys)
sys.setdefaultencoding('utf-8')这段代码就行了,请问还有其他解决方法吗?这个是什么原因导致的?因为这个问题我之前也遇到过,但是解决方法不一样