Flask学习之三 web表单
本部分Miguel Grinberg教程的翻译地址:http://www.pythondoc.com/flask-mega-tutorial/webforms.html
开源中国的:http://www.oschina.net/translate/the-flask-mega-tutorial-part-iii-web-forms
英文原文地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-forms
PS.建议英语比较好直接看英文博客,因为中文翻译可能会有问题,而且有问题英文博客那边会进行修改,但是中文翻译是不会再修改的。
关于表单:Web 表单是在任何一个 web 应用程序中最基本的一部分。我们将使用表单允许用户写文章,以及登录到应用程序中。
一、配置
教程:为了能够处理 web 表单,我们将使用 Flask-WTF ,该扩展封装了 WTForms 并且恰当地集成进 Flask 中。
因为许多 Flask 扩展需要大量的配置,比较方便的做法是在 microblog 文件夹的根目录下创建一个配置文件 config.py:
CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
教程:CSRF_ENABLED 配置是为了激活 跨站点请求伪造 保护。在大多数情况下,你需要激活该配置使得你的应用程序更安全些。
SECRET_KEY 配置仅仅当 CSRF 激活的时候才需要,它是用来建立一个加密的令牌,用于验证一个表单。当你编写自己的应用程序的时候,请务必设置很难被猜测到密钥。
在写好配置文件之后,我们需要告诉Flask如何读取和使用它。修改app/__init__.py:
from flask import Flask app = Flask(__name__) app.config.from_object('config') from app import views
二、用户登录表单
教程:在 Flask-WTF 中,表单是表示成对象,Form 类的子类。一个表单子类简单地把表单的域定义成类的变量。
这里的登录表单不是用户名/密码类型,而是OpenID。(对于这个东西目前其实我比较的迷茫)
教程:OpenID 登录仅仅需要一个字符串,被称为 OpenID。我们将在表单上提供一个 ‘remember me’ 的选择框,以至于用户可以选择在他们的网页浏览器上种植 cookie ,当他们再次访问的时候,浏览器能够记住他们的登录。
第一个表单(文件 app/forms.py):
from flask.ext.wtf import Form from wtforms import StringField, BooleanField from wtforms.validators import DataRequired class LoginForm(Form): openid = StringField('openid', validators = [DataRequired()]) remember_me = BooleanField('remember_me', default = False)
注意:这里如果看中文翻译的教程,是没办法正常执行的,以上的代码是英文博客。
备注:在flask-wtf的0.9版之后已经不可用。
见 https://flask-wtf.readthedocs.org/en/latest/upgrade.html#version-0-9-0
0.9版之后的flask-wtf的结构有了不小的改动,如果遇到问题,可以先去看看官方文档。
三、表单模板
就是html布局,代码如下(文件 app/templates/login.html):
<!-- extend from base layout --> {% extends "base.html" %} {% block content %} <h1>Sign In</h1> <form action="" method="post" name="login"> {{form.hidden_tag()}} <p> Please enter your OpenID:<br> {{form.openid(size=80)}}<br> </p> <p>{{form.remember_me}} Remember Me</p> <p><input type="submit" value="Sign In"></p> </form> {% endblock %}
在我们所有模板中,都应该继承base.html,用以保证布局的一致性。
教程:模板期望一个实例化自我们刚才创建地表单类的表单对象储存成一个模板参数,称为 form。当我们编写渲染这个模板的视图函数的时候,我们将会特别注意传送这个模板参数到模板中。
form.hidden_tag() 模板参数将被替换为一个隐藏字段,用来是实现在配置中激活的 CSRF 保护。如果你已经激活了 CSRF,这个字段需要出现在你所有的表单中。
我们表单中实际的字段也将会被表单对象渲染,你只必须在字段应该被插入的地方指明一个 {{form.field_name}} 模板参数。某些字段是可以带参数的。在我们的例子中,我们要求表单生成一个 80 个字符宽度的 openid 字段。
因为我们并没有在表单中定义提交按钮,我们必须按照普通的字段来定义。提交字段实际并不携带数据因此没有必要在表单类中定义。
四、表单视图
要在浏览器看到登录页面,还需要一个有登录页面的表单视图。修改(文件 app/views.py):
from flask import render_template, flash, redirect from app import app from forms import LoginForm # index view function suppressed for brevity @app.route('/login', methods = ['GET', 'POST']) def login(): form = LoginForm() return render_template('login.html', title = 'Sign In', form = form)
上面的代码做了一件事就是在路由装饰器中添加一个新方法。让 Flask 明白我们这个视图函数支持 GET 和 POST 请求。否则这个视图函数只会响应 GET 请求。我们需要得到用户填写表单后提交的数据,这些数据是从 POST 请求中传递过来的。
可以登录URL http://127.0.0.1:5000/login 看看效果。现在还没写接收数据,所以怎么按提交按钮都是不会有效果的。
注意:以上是新修改的部分,可以不删除而在下面进行增加的,如下:
from app import app from flask import render_template, flash, redirect from forms import LoginForm @app.route('/') @app.route('/index') def index(): user = { 'nickname': 'Miguel'} # fake user return render_template("index.html", title = 'Home', user = user) @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() return render_template('login.html', title = 'Sign In', form = form)
这样http://127.0.0.1:5000这个URL才能继续使用,否则就只能用 http://127.0.0.1:5000/login 这个URL。只用博客中的代码http://127.0.0.1:5000这个URL就会变成404 NOT FOUND
五、接收数据
修改登录视图函数,它验证并且存储表单数据 (文件 app/views.py):
@app.route('/login', methods = ['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data)) return redirect('/index') return render_template('login.html', title = 'Sign In', form = form)
validate_on_submit 方法做了所有表单处理工作。当表单正在展示给用户的时候调用它,它会返回 False.
如果 validate_on_submit 在表单提交请求中被调用,它将会收集所有的数据,对字段进行验证,如果所有的事情都通过的话,它将会返回 True,表示数据都是合法的。这就是说明数据是安全的,并且被应用程序给接受了。
当 validate_on_submit 返回 True,我们的登录视图函数调用了两个新的函数,导入自 Flask。flash 函数是一种快速的方式下呈现给用户的页面上显示一个消息。在我们的例子中,我将会使用它来调试,因为我们目前还不具备用户登录的必备的基础设施,相反我们将会用它来显示提交的数据。flash 函数在生产服务器上也是十分有作用的,用来提供反馈给用户有关的行动。
redirect 这个函数告诉网页浏览器引导到一个不同的页面而不是请求的页面。在我们的视图函数中我们用它重定向到前面已经完成的首页上。
闪现的消息将不会自动地出现在我们的页面上,我们的模板需要加入展示消息的内容。我们将添加这些消息到我们的基础模板中,这样所有的模板都能继承这个函数。这是更新后的基础模板(文件 app/templates/base.html):
<html> <head> {% if title %} <title>{{title}} - microblog</title> {% else %} <title>microblog</title> {% endif %} </head> <body> <div>Microblog: <a href="/index">Home</a></div> <hr> {% with message = get_flashed_messages() %} {% if messages %} <ul> {% for message in messages %} <li> {{ message }} </li> {% endfor %} </ul> {% endif %} {% endwith %} {% block content %}{% endblock %} </body> </html>
六、加强字段验证
现阶段的应用程序,如果表单提交不合理的数据将不会被接受。相反,会返回表单让用户提交合法的数据。但我们还需要告诉用户他哪里出错了。
Flask-WTF 也能够轻易地做到这一点。
当字段验证失败的时候, Flask-WTF 会向表单对象中添加描述性的错误信息。这些信息是可以在模板中使用的,因此我们只需要增加一些逻辑来获取它。
再修改登录模板(文件 app/templates/login.html):
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>Sign In</h1> <form action="" method="post" name="login"> {{form.hidden_tag()}} <p> Please enter your OpenID:<br> {{form.openid(size=80)}}<br> {% for error in form.errors.openid %} <span style="color: red;">[{{error}}]</span> {% endfor %}<br> </p> <p>{{form.remember_me}} Remember Me</p> <p><input type="submit" value="Sign In"></p> </form> {% endblock %}
七、处理 OpenIDs
教程:
事实上,很多用户并不知道他们已经有一些 OpenIDs。一些大的互联网服务提供商支持 OpenID 认证自己的会员这并不是众所周知的。比如,如果你有一个 Google 的账号,你也就有了一个它们的 OpenID。
为了让用户更方便地使用这些常用的 OpenID 登录到我们的网站,我们把它们的链接转成短名称,用户不必手动地输入这些 OpenID。
首先开始定义一个 OpenID 提供者的列表,把它们写入我们的配置文件中(文件 config ):
WTF_CSRF_ENABLED = True SECRET_KEY = 'you-will-never-guess' OPENID_PROVIDERS = [ {'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'}, {'name': 'Yahoo', 'url': 'https://me.yahoo.com'}, {'name': 'AOL', 'url': 'http://openid.aol.com/<username>'}, {'name': 'Flickr', 'url': 'http://www.flickr.com/<username>'}, {'name': 'MyOpenID', 'url': 'https://www.myopenid.com'}]
修改登录视图函数中(文件 app/views.py):
@app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="%s", remember_me=%s' % (form.openid.data, str(form.remember_me.data))) return redirect('/index') return render_template('login.html', title='Sign In', form=form, providers=app.config['OPENID_PROVIDERS'])
从配置中获取 OPENID_PROVIDERS,接着把它作为 render_template 中一个参数传入模板中。
在登录模板中渲染这些提供商的链接(文件 app/templates/login.html):
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <script type="text/javascript"> function set_openid(openid, pr) { u = openid.search('<username>') if (u != -1) { // openid requires username user = prompt('Enter your ' + pr + ' username:') openid = openid.substr(0, u) + user } form = document.forms['login']; form.elements['openid'].value = openid } </script> <h1>Sign In</h1> <form action="" method="post" name="login"> {{ form.hidden_tag() }} <p> Please enter your OpenID, or select one of the providers below:<br> {{ form.openid(size=80) }} {% for error in form.openid.errors %} <span style="color: red;">[{{error}}]</span> {% endfor %}<br> |{% for pr in providers %} <a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');">{{ pr.name }}</a> | {% endfor %} </p> <p>{{ form.remember_me }} Remember Me</p> <p><input type="submit" value="Sign In"></p> </form> {% endblock %}
一些 OpenIDs 含有用户名,因此对于这些用户,我们必须利用 javascript 的魔力提示用户输入用户名并且组成 OpenIDs。当用户点击一个 OpenIDs 提供商的链接并且(可选)输入用户名,该提供商相应的 OpenID 就被写入到文本域中。
修改后的登录页面:
BY笔者:
我看到最后才大概理解OpenID是什么,感觉大概意思就像是用腾讯或者新浪帐号去登录其他网站或者应用是类似的感觉。