Flask-WTF

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

功能

  • 集成 wtforms。
  • 带有 csrf 令牌的安全表单。
  • 全局的 csrf 保护。
  • 支持验证码(Recaptcha)。
  • 与 Flask-Uploads 一起支持文件上传。
  • 国际化集成。

安装

该部分文档涵盖了 Flask-WTF 安装。使用任何软件包的第一步即是正确安装它。

Distribute & Pip

pip 安装 Flask-WTF 是十分简单的:

$ pip install Flask-WTF

或者,使用 easy_install:

$ easy_install Flask-WTF

但是,你真的 不应该这样做

获取源代码

Flask-WTF 在 GitHub 上活跃开发,代码在 GitHub 上 始终可用

你也可以克隆公开仓库:

git clone git://github.com/lepture/flask-wtf.git

下载 tarball:

$ curl -OL https://github.com/lepture/flask-wtf/tarball/master

或者,下载 zipball:

$ curl -OL https://github.com/lepture/flask-wtf/zipball/master

当你有一份源码的副本后,你很容易地就可以把它嵌入到你的 Python 包中,或是安装到 site-packages:

$ python setup.py install

快读入门

急于上手?本页对 Flask-WTF 给出了一个详尽的介绍。假设你已经安装了 Flask-WTF,如果还未安装的话,请先浏览 安装

创建表单

Flask-WTF 提供了对 WTForms 的集成。例如:

from flask_wtf import Form
from wtforms import StringField
from wtforms.validators import DataRequired

class MyForm(Form):
    name = StringField('name', validators=[DataRequired()])

Note

从 0.9.0 版本开始,Flask-WTF 将不会从 wtforms 中导入任何的内容,用户必须自己从 wtforms 中导入字段。

另外,隐藏的 CSRF 令牌会被自动地创建。你可以在模板这样地渲染它:

<form method="POST" action="/">
    {{ form.csrf_token }}
    {{ form.name.label }} {{ form.name(size=20) }}
    <input type="submit" value="Go">
</form>

尽管如此,为了创建有效的 XHTML/HTML, Form 类有一个 hidden_tag 方法, 它在一个隐藏的 DIV 标签中渲染任何隐藏的字段,包括 CSRF 字段:

<form method="POST" action="/">
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{ form.name(size=20) }}
    <input type="submit" value="Go">
</form>

验证表单

在你的视图处理程序中验证请求:

@app.route('/submit', methods=('GET', 'POST'))
def submit():
    form = MyForm()
    if form.validate_on_submit():
        return redirect('/success')
    return render_template('submit.html', form=form)

注意你不需要把 request.form 传给 Flask-WTF;Flask-WTF 会自动加载。便捷的方法 validate_on_submit 将会检查是否是一个 POST 请求并且请求是否有效。

阅读 创建表单 学习更多的关于表单的技巧。

创建表单

这部分文档涉及表单(Form)信息。

安全表单

无需任何配置,Form 是一个带有 CSRF 保护的并且会话安全的表单。我们鼓励你什么都不做。

但是如果你想要禁用 CSRF 保护,你可以这样:

form = Form(csrf_enabled=False)

如果你想要全局禁用 CSRF 保护,你真的不应该这样做。但是你要坚持这样做的话,你可以在配置中这样写:

WTF_CSRF_ENABLED = False

为了生成 CSRF 令牌,你必须有一个密钥,这通常与你的 Flask 应用密钥一致。如果你想使用不同的密钥,可在配置中指定:

WTF_CSRF_SECRET_KEY = 'a random string'

文件上传

Flask-WTF 提供 FileField 来处理文件上传,它在表单提交后,自动从 flask.request.files 中抽取数据。FileFielddata 属性是一个 Werkzeug FileStorage 实例。

例如:

from werkzeug import secure_filename
from flask_wtf.file import FileField

class PhotoForm(Form):
    photo = FileField('Your photo')

@app.route('/upload/', methods=('GET', 'POST'))
def upload():
    form = PhotoForm()
    if form.validate_on_submit():
        filename = secure_filename(form.photo.data.filename)
        form.photo.data.save('uploads/' + filename)
    else:
        filename = None
    return render_template('upload.html', form=form, filename=filename)

Note

记得把你的 HTML 表单的 enctype 设置成 multipart/form-data,既是:

<form action="/upload/" method="POST" enctype="multipart/form-data">
    ....
</form>

此外,Flask-WTF 支持文件上传的验证。提供了 FileRequiredFileAllowed

FileAllowed 能够很好地和 Flask-Uploads 一起协同工作, 例如:

from flask.ext.uploads import UploadSet, IMAGES
from flask_wtf import Form
from flask_wtf.file import FileField, FileAllowed, FileRequired

images = UploadSet('images', IMAGES)

class UploadForm(Form):
    upload = FileField('image', validators=[
        FileRequired(),
        FileAllowed(images, 'Images only!')
    ])

也能在没有 Flask-Uploads 下挑大梁。这时候你需要向 FileAllowed 传入扩展名即可:

class UploadForm(Form):
    upload = FileField('image', validators=[
        FileRequired(),
        FileAllowed(['jpg', 'png'], 'Images only!')
    ])

HTML5 控件

Note

自 wtforms 1.0.5 版本开始,wtforms 就内嵌了 HTML5 控件和字段。如果可能的话,你可以考虑从 wtforms 中导入它们。

我们将会在 0.9.3 版本后移除 html5 模块。

你可以从 wtforms 中导入一些 HTML5 控件:

from wtforms.fields.html5 import URLField
from wtforms.validators import url

class LinkForm(Form):
    url = URLField(validators=[url()])

验证码

Flask-WTF 通过 RecaptchaField 也提供对验证码的支持:

from flask_wtf import Form, RecaptchaField
from wtforms import TextField

class SignupForm(Form):
    username = TextField('Username')
    recaptcha = RecaptchaField()

这伴随着诸多配置,你需要逐一地配置它们。

RECAPTCHA_PUBLIC_KEY 必须 公钥
RECAPTCHA_PRIVATE_KEY 必须 私钥
RECAPTCHA_API_SERVER 可选 验证码 API 服务器
RECAPTCHA_PARAMETERS 可选 一个 JavaScript(api.js)参数的字典
RECAPTCHA_DATA_ATTRS 可选 一个数据属性项列表 https://developers.google.com/recaptcha/docs/display

RECAPTCHA_PARAMETERS 和 RECAPTCHA_DATA_ATTRS 的示例:

RECAPTCHA_PARAMETERS = {'hl': 'zh', 'render': 'explicit'}
RECAPTCHA_DATA_ATTRS = {'theme': 'dark'}

对于应用测试时,如果 app.testingTrue ,考虑到方便测试,Recaptcha 字段总是有效的。

在模板中很容易添加 Recaptcha 字段:

<form action="/" method="post">
    {{ form.username }}
    {{ form.recaptcha }}
</form>

我们为你提供了例子: recaptcha@github

CSRF 保护

这部分文档介绍了 CSRF 保护。

为什么需要 CSRF?

Flask-WTF 表单保护你免受 CSRF 威胁,你不需要有任何担心。尽管如此,如果你有不包含表单的视图,那么它们仍需要保护。

例如,由 AJAX 发送的 POST 请求,然而它背后并没有表单。在 Flask-WTF 0.9.0 以前的版本你无法获得 CSRF 令牌。这是为什么我们要实现 CSRF。

实现

为了能够让所有的视图函数受到 CSRF 保护,你需要开启 CsrfProtect 模块:

from flask_wtf.csrf import CsrfProtect

CsrfProtect(app)

像任何其它的 Flask 扩展一样,你可以惰性加载它:

from flask_wtf.csrf import CsrfProtect

csrf = CsrfProtect()

def create_app():
    app = Flask(__name__)
    csrf.init_app(app)

Note

你需要为 CSRF 保护设置一个秘钥。通常下,同 Flask 应用的 SECRET_KEY 是一样的。

如果模板中存在表单,你不需要做任何事情。与之前一样:

<form method="post" action="/">
    {{ form.csrf_token }}
</form>

但是如果模板中没有表单,你仍然需要一个 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

无论何时未通过 CSRF 验证,都会返回 400 响应。你可以自定义这个错误响应:

@csrf.error_handler
def csrf_error(reason):
    return render_template('csrf_error.html', reason=reason), 400

我们强烈建议你对所有视图启用 CSRF 保护。但也提供了某些视图函数不需要保护的装饰器:

@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
    # ...
    return 'ok'

默认情况下你也可以在所有的视图中禁用 CSRF 保护,通过设置 WTF_CSRF_CHECK_DEFAULTFalse,仅仅当你需要的时候选择调用 csrf.protect()。这也能够让你在检查 CSRF 令牌前做一些预先处理:

@app.before_request
def check_csrf():
    if not is_oauth(request):
        csrf.protect()

AJAX

不需要表单,通过 AJAX 发送 POST 请求成为可能。0.9.0 版本后这个功能变成可用的。

假设你已经使用了 CsrfProtect(app),你可以通过 {{ csrf_token() }} 获取 CSRF 令牌。这个方法在每个模板中都可以使用,你并不需要担心在没有表单时如何渲染 CSRF 令牌字段。

我们推荐的方式是在 <meta> 标签中渲染 CSRF 令牌:

<meta name="csrf-token" content="{{ csrf_token() }}">

<script> 标签中渲染同样可行:

<script type="text/javascript">
    var csrftoken = "{{ csrf_token() }}"
</script>

下面的例子采用了在 <meta> 标签渲染的方式, 在 <script> 中渲染会更简单,你无须担心没有相应的例子。

无论何时你发送 AJAX POST 请求,为其添加 X-CSRFToken 头:

var csrftoken = $('meta[name=csrf-token]').attr('content')

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken)
        }
    }
})

故障排除

当你定义你的表单的时候,如果犯了 这个错误_ : 从 wtforms 中导入 Form 而不是从 flask.ext.wtf 中导入,CSRF 保护的大部分功能都能工作(除了 form.validate_on_submit()),但是 CSRF 保护将会发生异常。在提交表单的时候,你将会得到 Bad Request/CSRF token missing or incorrect 错误。这个错误的出现就是因为你的导入错误,而不是你的配置问题。

配置

这里是所有配置的全表。

表单和 CSRF

Flask-WTF 完整的配置清单。通常,你不必配置它们。默认的配置就能正常工作。

WTF_CSRF_ENABLED 禁用/开启表单的 CSRF 保护。默认是开启。
WTF_CSRF_CHECK_DEFAULT 默认下启用 CSRF 检查针对所有的视图。 默认值是 True。
WTF_I18N_ENABLED 禁用/开启 I18N 支持。需要和 Flask-Babel 配合一起使用。默认是开启。
WTF_CSRF_HEADERS 需要检验的 CSRF 令牌 HTTP 头。默认是 [‘X-CSRFToken’, ‘X-CSRF-Token’]
WTF_CSRF_SECRET_KEY 一个随机字符串生成 CSRF 令牌。 默认同 SECRET_KEY 一样。
WTF_CSRF_TIME_LIMIT CSRF 令牌过期时间。默认是 3600 秒。
WTF_CSRF_SSL_STRICT 使用 SSL 时进行严格保护。这会检查 HTTP Referrer, 验证是否同源。默认为 True 。
WTF_CSRF_METHODS 使用 CSRF 保护的 HTTP 方法。默认是 [‘POST’, ‘PUT’, ‘PATCH’]
WTF_HIDDEN_TAG 隐藏的 HTML 标签包装的名称。 默认是 div
WTF_HIDDEN_TAG_ATTRS 隐藏的 HTML 标签包装的标签属性。 默认是 {‘style’: ‘display:none;’}

验证码

你已经在 验证码 中了解了这些配置选项。该表为了方便速查。

RECAPTCHA_USE_SSL 允许/禁用 Recaptcha 使用 SSL。默认是 False。
RECAPTCHA_PUBLIC_KEY 必须 公钥。
RECAPTCHA_PRIVATE_KEY 必须 私钥。
RECAPTCHA_OPTIONS 可选 配置选项的字典。 https://www.google.com/recaptcha/admin/create

开发者接口

该部分文档涵盖了 Flask-WTF 的全部接口。

表单和字段

class flask_wtf.Form(formdata=<class flask_wtf.form._Auto at 0x10627ed50>, obj=None, prefix='', csrf_context=None, secret_key=None, csrf_enabled=None, *args, **kwargs)

Flask-specific subclass of WTForms SecureForm class.

If formdata is not specified, this will use flask.request.form. Explicitly pass formdata = None to prevent this.

Parameters:

  • csrf_context – a session or dict-like object to use when making CSRF tokens. Default: flask.session.
  • secret_key

    a secret key for building CSRF tokens. If this isn’t specified, the form will take the first of these that is defined:

    • SECRET_KEY attribute on this class
    • WTF_CSRF_SECRET_KEY config of flask app
    • SECRET_KEY config of flask app
    • session secret key
  • csrf_enabled – whether to use CSRF protection. If False, all csrf behavior is suppressed. Default: WTF_CSRF_ENABLED config value

hidden_tag(*fields)

Wraps hidden fields in a hidden DIV tag, in order to keep XHTML compliance.

New in version 0.3.

Parameters: fields – list of hidden field names. If not provided will render all hidden fields, including the CSRF field.

is_submitted()

Checks if form has been submitted. The default case is if the HTTP method is PUT or POST.

validate_csrf_data(data)

Check if the csrf data is valid.

Parameters: data – the csrf string to be validated.

validate_on_submit()

Checks if form has been submitted and if so runs validate. This is a shortcut, equivalent to form.is_submitted() and form.validate()

class flask_wtf.RecaptchaField(label='', validators=None, **kwargs)

class flask_wtf.Recaptcha(message=None)

Validates a ReCaptcha.

class flask_wtf.RecaptchaWidget

class flask_wtf.file.FileField(label=None, validators=None, filters=(), description=u'', id=None, default=None, widget=None, _form=None, _name=None, _prefix=u'', _translations=None, _meta=None)

Werkzeug-aware subclass of wtforms.FileField

Provides a has_file() method to check if its data is a FileStorage instance with an actual file.

has_file()

Return True iff self.data is a FileStorage with file data

class flask_wtf.file.FileAllowed(upload_set, message=None)

Validates that the uploaded file is allowed by the given Flask-Uploads UploadSet.

Parameters:

  • upload_set – A list/tuple of extention names or an instance of flask.ext.uploads.UploadSet
  • message – error message

You can also use the synonym file_allowed.

class flask_wtf.file.FileRequired(message=None)

Validates that field has a file.

Parameters: message – error message

You can also use the synonym file_required.

class flask_wtf.html5.SearchInput(input_type=None)

Renders an input with type “search”.

class flask_wtf.html5.SearchField(label=None, validators=None, filters=(), description=u'', id=None, default=None, widget=None, _form=None, _name=None, _prefix=u'', _translations=None, _meta=None)

Represents an <input type="search">.

class flask_wtf.html5.URLInput(input_type=None)

Renders an input with type “url”.

class flask_wtf.html5.URLField(label=None, validators=None, filters=(), description=u'', id=None, default=None, widget=None, _form=None, _name=None, _prefix=u'', _translations=None, _meta=None)

Represents an <input type="url">.

class flask_wtf.html5.EmailInput(input_type=None)

Renders an input with type “email”.

class flask_wtf.html5.EmailField(label=None, validators=None, filters=(), description=u'', id=None, default=None, widget=None, _form=None, _name=None, _prefix=u'', _translations=None, _meta=None)

Represents an <input type="email">.

class flask_wtf.html5.TelInput(input_type=None)

Renders an input with type “tel”.

class flask_wtf.html5.TelField(label=None, validators=None, filters=(), description=u'', id=None, default=None, widget=None, _form=None, _name=None, _prefix=u'', _translations=None, _meta=None)

Represents an <input type="tel">.

class flask_wtf.html5.NumberInput(step=None)

Renders an input with type “number”.

class flask_wtf.html5.IntegerField(label=None, validators=None, **kwargs)

Represents an <input type="number">.

class flask_wtf.html5.DecimalField(label=None, validators=None, places=<unset value>, rounding=None, **kwargs)

Represents an <input type="number">.

class flask_wtf.html5.RangeInput(step=None)

Renders an input with type “range”.

class flask_wtf.html5.IntegerRangeField(label=None, validators=None, **kwargs)

Represents an <input type="range">.

class flask_wtf.html5.DecimalRangeField(label=None, validators=None, places=<unset value>, rounding=None, **kwargs)

Represents an <input type="range">.

CSRF 保护

class flask_wtf.csrf.CsrfProtect(app=None)

Enable csrf protect for Flask.

Register it with:

app = Flask(__name__)
CsrfProtect(app)

And in the templates, add the token input:

<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>

If you need to send the token via AJAX, and there is no form:

<meta name="csrf_token" content="{{ csrf_token() }}" />

You can grab the csrf token with JavaScript, and send the token together.

error_handler(view)

A decorator that set the error response handler.

It accepts one parameter reason:

@csrf.error_handler
def csrf_error(reason):
    return render_template('error.html', reason=reason)

By default, it will return a 400 response.

exempt(view)

A decorator that can exclude a view from csrf protection.

Remember to put the decorator above the route:

csrf = CsrfProtect(app)

@csrf.exempt
@app.route('/some-view', methods=['POST'])
def some_view():
    return

flask_wtf.csrf.generate_csrf(secret_key=None, time_limit=None)

Generate csrf token code.

Parameters:

  • secret_key – A secret key for mixing in the token, default is Flask.secret_key.
  • time_limit – Token valid in the time limit, default is 3600s.

flask_wtf.csrf.validate_csrf(data, secret_key=None, time_limit=None)

Check if the given data is a valid csrf token.

Parameters:

  • data – The csrf token value to be checked.
  • secret_key – A secret key for mixing in the token, default is Flask.secret_key.
  • time_limit – Check if the csrf token is expired. default is True.

升级到新版本

Flask-WTF 像其它软件一样随时间推移而改动。大多数改动是良性的,就是当你升级到新版而无需做出任何改动的良性。

尽管如此,每隔一段时间,就会有需要你对代码做出改动或是允许你改善你自己的代码质量来从 Flask-WTF 新特性获益的改动。

本节文档列举所有 Flask-WTF 版本中的所有变更以及如何进行无痛苦的升级。

如果你想用 pip 命令升级 Flask-WTF,确保传递 -U 参数:

$ pip install -U Flask-WTF

版本 0.9.0

移除 wtforms 的导入是一个重大的改变,这可能会给你带来许多痛苦,但这些导入项难以维护。你需要从原始的 WTForms 中导入 Fields ,而不是从 Flask-WTF 中导入:

from wtforms import TextField

配置选项 CSRF_ENABLED 改为 WTF_CSRF_ENABLED 。如果你没有设置任何配置选项,那么你无须做任何变动。

这个版本有很多的特色功能,如果你不需要他们,他们不会对你的任何代码有影响。

Flask-WTF 更新历史

Flask-WTF 的所有发布版本的变更列表。

Version 0.12

Released 2015/07/09

  • Abstract protect_csrf() into a separate method
  • Update reCAPTCHA configuration
  • Fix reCAPTCHA error handle

Version 0.11

Released 2015/01/21

  • Use the new reCAPTCHA API via #164.

Version 0.10.3

Released 2014/11/16

  • Add configuration: WTF_CSRF_HEADERS via #159.
  • Support customize hidden tags via #150.
  • And many more bug fixes

Version 0.10.2

Released 2014/09/03

  • Update translation for reCaptcha via #146.

Version 0.10.1

Released 2014/08/26

  • Update RECAPTCHA API SERVER URL via #145.
  • Update requirement Werkzeug>=0.9.5
  • Fix CsrfProtect exempt for blueprints via #143.

Version 0.10.0

Released 2014/07/16

  • Add configuration: WTF_CSRF_METHODS
  • Support WTForms 2.0 now
  • Fix csrf validation without time limit (time_limit=False)
  • CSRF exempt supports blueprint #111.

Version 0.9.5

Released 2014/03/21

  • csrf_token for all template types #112.
  • Make FileRequired a subclass of InputRequired #108.

Version 0.9.4

Released 2013/12/20

  • Bugfix for csrf module when form has a prefix
  • Compatible support for wtforms2
  • Remove file API for FileField

Version 0.9.3

Released 2013/10/02

  • Fix validation of recaptcha when app in testing mode #89.
  • Bugfix for csrf module #91

Version 0.9.2

Released 2013/9/11

  • Upgrade wtforms to 1.0.5.
  • No lazy string for i18n #77.
  • No DateInput widget in html5 #81.
  • PUT and PATCH for CSRF #86.

Version 0.9.1

Released 2013/8/21

This is a patch version for backward compitable for Flask<0.10 #82.

Version 0.9.0

Released 2013/8/15

  • Add i18n support (issue #65)
  • Use default html5 widgets and fields provided by wtforms
  • Python 3.3+ support
  • Redesign form, replace SessionSecureForm
  • CSRF protection solution
  • Drop wtforms imports
  • Fix recaptcha i18n support
  • Fix recaptcha validator for python 3
  • More test cases, it’s 90%+ coverage now
  • Redesign documentation

Version 0.8.4

Released 2013/3/28

  • Recaptcha Validator now returns provided message (issue #66)
  • Minor doc fixes
  • Fixed issue with tests barking because of nose/multiprocessing issue.

Version 0.8.3

Released 2013/3/13

  • Update documentation to indicate pending deprecation of WTForms namespace facade
  • PEP8 fixes (issue #64)
  • Fix Recaptcha widget (issue #49)

Version 0.8.2 and prior

Initial development by Dan Jacob and Ron Duplain. 0.8.2 and prior there was not a change log.

作者

Flask-WTF 是由 Dan Jacob 创建,现在是由 Hsiaoming Yang 维护。

贡献者

贡献过补丁和建议的人们:

  • Dan Jacob
  • Ron DuPlain
  • Daniel Lepage
  • Anthony Ford
  • Hsiaoming Yang

更多的贡献者可以在 GitHub 上找到。

BSD 许可证

Copyright (c) 2010 by Dan Jacob. Copyright (c) 2013 - 2014 by Hsiaoming Yang.

Some rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.