Flask之表单?flask获取post参数表单

Flask之表单 前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除  目录
一、HTML表单
二、使用Flask-WTF处理表单
2.1、定义WTFo

前言:本博客仅供记录和学习之用,部分照片取自网络。如果侵犯了您的权利,请联系我们删除。

目录

1.HTML表单

2.使用Flask-WTF处理表单

2.1. 定义WTForms 表单类

2.2. 输出HTML代码

2.3. 使用模板渲染表单

3. 处理表单数据

3.1. 提交表格

3.2. 检查表单数据

3.3. 模板中渲染错误信息

4. 高级形式练习

4.1. 设置错误消息的语言

4.2. 使用宏渲染表单

4.3. 自定义验证器

4.4. 上传文件

4.5. 使用Flask-CKEditor 集成富文本编辑器

4.6. 单个表单中的多个提交按钮

4.7. 单个页面上的多个表单

WTForms 是一个用Python 编写的表单库。

一、HTML表单

表单是使用form 标签创建的,表单中的字段是使用input 标签定义的。

表单方法=\’post\’

标签for=\’用户名\’用户名/labelbr

输入类型=\’文本\’名称=\’用户名\’占位符=\’赫克托·里维拉\’br

标签=\’密码\’密码/labelbr

输入类型=\’密码\’名称=\’密码\’占位符=\’123456789\’br

输入id=\’记住\’ name=\’记住\’ type=\’checkbox\’ 选中

label for=\’remember\’small记住我/small/labelbr

输入类型=\’提交\’名称=\’提交\’值=\’登录\’

/形状

输入标签代表各种输入字段,标签标签用于定义字段的标签文本。您可以使用各种表单属性和输入标签来配置表单。

二、使用Flask-WTF处理表单

增强版的Flask-WTF 集成了WTForms,它可以让您将表单数据解析、CSRF 保护和文件上传等功能集成到Flask 中,以及reCAPTCHA(Google 开发的免费验证码服务,目前在中国不支持)。

pip 安装Flask-wtf

Flask-WTF 默认为每个表单启用CSRF 保护,并自动生成和验证CSRF 令牌。默认情况下,Flask-WTF 使用您的程序密钥来签署CSRF 令牌,因此请设置您的程序的密钥。

app.secret_key=\’秘密字符串\’

2.1、定义WTForms表单类

当您使用WTForms 创建表单时,该表单由一个Python 类表示,该类继承自从WTForms 导入的Form 基类。表单由多个输入字段(类似于表单中的输入框、按钮和其他组件)组成,这些输入字段由表单类属性表示。

从wtforms 表单、StringField、PasswordField、BooleanField、SubmitField 导入

从wtforms.validators 导入DataRequired,长度

班级登录表(表格):

用户名=StringField(\’用户名\’, Validator=[DataRequired()])

密码=PasswordField(\’密码\’, validator=[DataRequired(), Length(8, 128)])

记住=BooleanField(\’记住我\’)

提交=SubmitField(\’登录\’)

每个字段的属性通过实例化WTForms 提供的字段来表示。 field属性的name用作对应HTML输入元素的name和id属性值。 (属性名称区分大小写)

常用的WTForms 字段有:

与字段类描述对应的HTML 表示BooleanField 复选框。该值被视为输入类型=\’checkbox\’DateField 文本字段,并且该值被视为datetime.date 对象输入类型=\’文本。 \’DateTimeField 文本字段。该值被视为datetime.datetime 对象。 input type=\’text\’FileField 文件上传字段input type=\’file\’FloatField 浮点字段。该值被视为浮点类型输入type=\’。 text\’IntegerField 整数字段,值被处理。对于整数,输入类型=\’text\’RadioField 单选按钮集输入类型=\’radio\’SelectField 下拉列表selectoption/option/selectSelectMultipleField 多选下拉列表select multipleoption /option/selectSubmitField 提交按钮输入类型=\’submit\’StringField 文本字段输入type=\’text\’HiddenField 隐藏文本字段输入类型=\’hidden\’PasswordField 密码文本字段输入类型=\’password\’TextAreaField 多行文本字段textarea/textarea

实例化字段类的常用参数

参数说明label 字段标签值。 render_kw 渲染后将在输入字段之前显示的文本。字典用于设置与HTML 输入标记的验证器相对应的属性。 包含一组验证器的列表,稍后将逐个调用这些验证器。提交表单检查表单数据中用于设置表单字段默认值的默认字符串或可调用项

在WTForm 中,验证器是一组用于验证字段数据的类,validators 关键字用于在实例化字段类时指定额外的验证器列表。验证器是从wtforms.valitators 模块导入的。常用的有:

验证器说明DataRequired(message=None) 验证数据是否有效Email(message=None) 验证电子邮件地址EqualTo(filedname, message=None) 验证两个字段值是否相同InputRequired(message=None) 验证数据是否有效LengthRange(min=-1,max=None,message=None) 验证输入值的长度是否在指定范围内NumberRange (min=None,max=None,message=None) 验证输入数字是否为在指定范围内Optional(strip_whitespace=True ) 允许输入值为空并跳过其他验证Regexp(regex,flags=0.message=None) 使用正则表达式输入验证值URL(require_tld=True,message=None ) Validate URLAnyOf(values,message=None,values_formatter=None) 检查输入值是否不包含在可选值列表中。 NoneOf(values, message=None, value_formatter=None) 检查输入值是否不包含在可选值列表中。可选值的列表。

验证器的第一个参数通常是错误消息。使用message关键字来传递参数。

name=StringField(\’您的姓名\’, validators=[DataRequired(message=u\’姓名不能为空!\’)])

当您使用Flask-WTF 定义表单时,您同样可以使用WTForms 提供的字段类和验证器,但您必须继承表单类。

Flask-WTF提供的FlaskForm类(继承Form类,进行一些设置,添加辅助方法)

从FlaskForm 导入flask_wtf

从wtforms StringField、PasswordField、BooleanField、SubmitField 导入

从wtforms.validators 导入DataRequired,长度

班级登录表单(FlaskForm):

用户名=StringField(\’用户名\’,validators=[DataRequired()])

密码=PasswordField(\’密码\’,validators=[DataRequired(),Length(8,128)])

记住=BooleanField(\’记住我\’)

提交=SubmitField(\’登录\’)

配置键WTF_CSRF_ENABLED用于设置是否启用CSRF保护。默认为True。

2.2、输出HTML代码

以使用WTForms创建的LoginForm为例,实例化表单类,并将实例属性转换为字符串或直接调用,获取表单字段对应的HTML代码。

表单=登录表单()

表单.用户名()

u\’input id=\’用户名\’ name=\’用户名\’ type=\’text\’ value=\’\’\’

表单.submit()

u\’input id=\’提交\’ name=\’提交\’ type=\’提交\’ value=\’提交\’\’

字段标签元素的HTML 代码以“form.fieldname.label”形式提供。

form.用户名.label()

u\’label for=\’用户名\’用户名/标签\’

form.submit.label()

u\’label for=\’submit\’提交/标签\’

默认情况下,WTForms 输出的字段HTML 代码仅包含id 和name 属性,其中属性值是表单类中对应的字段属性名称。如果您需要添加其他属性,您有两种选择:

2.2.1、使用render_kw属性

使用render_kw 设置用户名字段的placeholderHTML 属性。

用户名=StringField(\’用户名\’, render_kw={\’Placeholder\’:\’您的用户名\’})

调用该字段后的HTML 代码输出如下所示:

input type=\’text\’ id=\’用户名\’ name=\’用户名\’ placeholder=\’您的用户名\’

2.2.2、在调用字段时传入

调用字段属性时,您还可以通过添加括号以关键字参数的形式传递字段的附加HTML 属性。

form.username(style=\’width:200px;\’,class_=\’bar\’)

u\’input class=\’bar\’ id=\’用户名\’ name=\’用户名\’ style=\’width: 200px;\’ type=\’text\’\’

class是Python保留的关键字。这里我们使用class_来代替类。调用模板时可以直接使用类。

2.3、在模板中渲染表单

首先,将表单类的实例传递给模板。在视图函数中实例化表单类LoginForm,并在render_template()函数中使用关键字参数form将表单实例传递给模板。

@app.route(\’/基本\’)

默认基本():

表单=登录表单()

返回render_template(\’login.html\’,form=form)

使用模板渲染表单

表单方法=\’post\’

{{ form.csrf_token }}

{{ 表单.用户名.标签}}{{ 表单.用户名}}br

{{ 表单.密码.标签}}{{ 表单.密码}}br

{{ 表单.remember }}{{ 表单.remember.label }}br

{{ 表单.提交}}br

/形状

form.csrf_token 字段包含自动生成的CSRF 令牌值,必须在提交表单后手动呈现。

您可以使用render_kw 字典或在调用字段时传递参数来定义其他HTML 属性并以这种方式添加CSS 类,从而创建Bootstrap 样式的表单。

.

表单方法=\’post\’

{{ form.csrf_token }}

div class=\’形成组\’

{{ 表单.用户名.标签}}

{{ form.username(class=\’form-control\’) }}

/div

div class=\’形成组\’

{{ 表单.密码.标签}}

{{ form.password(class=\’form-control\’) }}

/div

div class=\’表单检查\’

{{ form.remember(class=\’form-check-input\’) }}

{{ 表单.记住.标签}}

/div

{{ form.submit(class=\’btn btn-primary\’) }}

/形状

.

三、处理表单数据

从数据获取到存储(不包括表单提交)的步骤如下。

解析请求,检索表单数据,对数据执行任何必要的转换(例如将复选框值转换为Python 布尔值),检查数据是否满足请求,同时检查CSRF 令牌是否匹配。如果验证失败,则会生成错误消息并显示在模板中。如果验证通过,数据将保存到数据库或进一步处理。

3.1、提交表单

在HTML 中,单击使用form 标记声明的表单中的“提交”类型的提交字段会创建一个提交表单的HTTP 请求,其中包括每个字段的数据。

该属性的默认值表示action当前的URL,即页面表单提交对应的URL、提交请求的目标URL、methododget、表单提交的HTTP请求方法。目前仅支持GET 和HTTP 请求。 POST 方法enctypeapplication/x-www-form-urlencoded 表单数据的编码类型。如果表单包含文件上传字段,则必须将其设置为multipart/form-data。或者,可以将其设置为纯文本类型text/。素色

form标签的action属性用于指定表单提交的目标URL。默认为当前URL,即渲染模板的路由所在的URL。

Flask 设置默认侦听HTTP 方法以路由到GET。设置监听POST方法

@app.route(\’/\’,methods=[\’GET\’,\’POST\’])

默认基本():

表单=登录表单()

返回render_template(\’login.html\’,form=form)

3.2、验证表单数据

3.2.1、客户端验证和服务器端验证

(1) 客户端认证

客户端验证是指在客户端(例如Web服务器)上验证用户输入值。例如,可以使用HTML5 的内置验证属性来实现基本的客户端验证(类型、请求、最小值、最大值、接受等)。例如:

输入类型=\’文本\’ 名称=\’用户名\’ 必填

# 添加了必需的标志。如果用户在没有输入任何内容的情况下按下提交按钮,则会显示内置浏览器错误消息。

与任何其他附加HTML 属性一样,您可以在定义表单或呈现表单时通过render_kw 传递这些属性。布尔属性(例如required)可以为空或任何ASCII 字符。

{{ form.用户名(必需=\’\’)}}

JavaScript 通常用于实现完整的验证机制,并且有各种JavaScript 表单验证库,例如jQuery Validation Plugin、Parsley js 和Bootstrap Validator,它们可以与Bootstrap 集成。

(2)服务端验证

服务器端验证是指用户将输入数据发送到服务器,并在服务器上验证数据。这是至关重要的。

3.2.2、WTForms验证机制

WTForms 验证表单字段的方式是在实例化表单类并调用表单实例上的validate() 方法时传递表单数据。这将调用字段被实例化时定义的验证器,并返回表示验证结果的布尔值。如果验证失败,错误消息将存储在与表单实例的错误属性对应的字典中。

3.2.3、在视图函数中验证表单

Basic_form 视图现在同时接受两种类型的请求:GET 和POST 请求。不同的请求有不同的处理方式。首先,实例化表单,如果是GET 请求,则渲染模板;如果是POST 请求,则调用validate() 方法检查表单数据。

请求的HTTP方法是通过request.method属性获取的。

来自烧瓶导入请求

@app.route(\’/basic\’,methods=[\’GET\’,\’POST\’])

默认基本():

表单=LoginForm() # GET+POST

如果request.method==\’POST\’ 且form.validate():

. # 处理POST 请求

返回render_template(\’forms/basic.html\’,form=form)

Flask-WTF 提供的validate_on_submit() 方法合并了上述操作,因此您可以将其简化为:

@app.route(\’/basic\’,methods=[\’GET\’,\’POST\’])

默认基本():

表单=LoginForm() # GET+POST

如果form.validate_on_submit:

. # 处理POST 请求

返回render_template(\’forms/basic.html\’,form=form)

# validate_on_submit() 方法验证表单数据

如果validate_on_submit() 返回True,则表示用户提交了表单并且表单通过了验证。您可以使用此if 语句检索表单数据。

@app.route(\’/basic\’,methods=[\’GET\’,\’POST\’])

默认基本():

表单=LoginForm() # GET+POST

如果form.validate_on_submit():

用户名=表单.用户名.数据

flash(\’欢迎回家,%s!\’ %用户名)

返回重定向(url_for(\’index\’))

返回render_template(\’basic.html\’,form=form)

表单类的data属性是一个匹配所有字段及其对应数据的字典,对应字段的数据通常以“form.field属性名.data”的形式直接检索。

在浏览器中,单击F5 刷新/重新加载时的默认行为是发送上一个请求。如果上一个请求是POST 请求,则会出现一个窗口,询问用户是否要再次提交表单。确保提交表单的POST 请求不是最后一个请求。因此,当您在处理表单后返回重定向响应时,浏览器会向重定向的目标URL 重新发送新的GET 请求。

3.3、在模板中渲染错误信息

如果form.validate_on_submit() 返回False,则表示验证未通过。 WTForms 将错误信息添加到表单类的错误属性(字典)中。通常通过字段名“form.fieldname.errors”获取对应字段的错误信息列表。

表单方法=\’post\’

{{ form.csrf_token }}

{{ 表单.用户名.标签}}br

{{ form.username() }}br

{form.username.errors 中消息的百分比}

小类=\’错误\'{{消息}}/smallbr

{ 结尾}

{{ form.password.label }}br

{{ 表单.密码}}br

{form.password.errors 中消息的百分比}

小类=\’错误\'{{消息}}/smallbr

{ 结束}

对于{{form.remember}}{{

m.remember.label }}<br>
{{ form.submit }}<br>
</form>

四、表单进阶实践

4.1、设置错误消息语言

WTForms内置了多种语言的错误消息,通过自定义表单基类实现

设置内置错误消息语言为中文

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from form import LoginForm
app = Flask(__name__)
app.config[\’WTF_I18N_ENABLED\’] = False # 这会让Flask-WTF使用WTForms内置的错误消息翻译。
class MybaseForm(FlaskForm):
class Meta:
locales = [\’zh\’]

class HelloForm(MybaseForm):
name = StringField(\’Name\’,validators=[DataRequired()])
submit = SubmitField()
在自定义基类中定义Meta类,并在locales列表中加入简体中文的地区字符串。在创建表单时,继承这个MybaseForm即可将错误消息语言设置为中文。另外也可在实例化表单类时通过meta关键字传入locales值:

form = Myform(meta=(\’locales\’: [\’en_US\’,\’en\’]))

4.2、使用宏渲染表单

在模板中渲染表单,有大量工作:

调用字段属性,获取<input>定义调用对应的label属性,获取<label>定义渲染错误消息
为避免代码重复,创建一个宏来渲染表单字段:

{% macro form_field(field) %}
{{ field.label }}<br>
{{ field(**kwargs) }}<br>
{% if field.errors %}
{% for error in errors %}
<small class=\”error\”>{{ error }}</small><br>
{% endfor %}
{% endif %}
{% endmacro %}
这个form_field()宏接收表单类实例的字段属性和附加的关键字参数作为输入,返回包含<label>标签、表单字段、错误消息列表的HTML表单字段代码。使用实例:

{% from \’macro.html\’ import form_field %}

<form method=\”post\”>
{{ form.csrf_token }}
{{ form_field(form.username) }}<br>
{{ form_field(form.password) }}<br>
</form>
上述调用form_field()宏逐个渲染表单中的字段,只要把每一个类的属性传入form_field()宏,即可完成渲染。

4.3、自定义验证器

4.3.1、行内验证器

可在表单类中定义方法来验证特定字段:针对特定字段的验证器:

from wtforms import SubmitField, IntegerField
from wtforms.validators import ValidationError
class FortyTwoForm(FlaskForm):
answer = IntegerField(\’The Number\’)
submit = SubmitField()
def validata_answer(form,field):
if field.data != 42:
raise ValidationError(\’Must be 42.\’)
在表单类中包含以\”validate_字段属性名\”形式命名的方法时,在验证字段数据时会同时调用这个方法来验证对应的字段。验证出错则抛出ValiddationError异常。仅用来验证特定的表单类字段,又称行内验证器。

4.3.2、全局验证器

若想要一个可重用的通用验证器,通过定义一个函数实现。简单示例:

from wtforms.validators import ValidationError
def is_42(form,field):
if field.data != 42:
raise ValidationError(\’Must be 42\’)
class FortyTwoForm(FlaskForm):
answer = IntegerField(\’The Number\’,validators=[is_42])
submit = SubmitField()
当使用函数定义全局的验证器时,我们需要在定义字段时在validators列表里传入这个验证器。因为在validators列表中传入的必须是可调用对象,所以这里传入函数对象,而不是函数调用。

在现实中,通常让验证器支持传入参数来对验证过程进行设置。至少支持message参数来设置自定义错误消息。此时验证函数应该实现成工厂函数,即返回一个可调用对象的函数。

from wtforms.validators import ValidationError
def is_42(message=None):
if message is None:
message = \’Must be 42.\’
def _is_42(form, field):
if field.data != 42:
raise ValidationError(message)
return _is_42
class FortyTwoForm(FlaskForm):
answer = IntegerField(\’The Number\’,validators=[is_42()])
submit = SubmitField()
在is_42()函数中,我们创建了另一个_is_42()函数,这个函数作为可调用对象返回。is_42()函数接收的message参数用来传入自定义错误消息,默认为None。

4.4、文件上传

渲染一个文件上传字段只需要将<input>标签的type属性设为file:<input type=\”file\”>

在服务器端,可以和普通数据一样获取上传文件数据并保存。不过需要考虑安全问题,文件上传漏洞也是比较流行的攻击方式。除了常规的CSRF防范,还需要注意:

验证文件类型验证文件大小过滤文件名

4.4.1、定义上传表单

在Python表单类中创建文件字段时,我们使用扩展Flask-WTF提供的FileField类

from flask_wtf.file import FileField,FileRequired,FileAllowed
class UploadForm(FlaskForm):
photo = FileField(\’Upload Image\’,validators=[FileRequired(),FileAllowed([\’jpg\’,\’jpeg\’,\’png\’,\’gif\’])])
submit = SubmitField()
和其他字段类似,也需要对文件上传字段进行验证。Flask-WTF在flask_wtf.file模块下提供了两个文件相关的验证器:

验证器说明FileRequired(message=None)验证是否包含文件对象FileAllowed(upload_set,message=None)用来验证文件类型,upload_set参数用来传入包含允许的文件后缀名列表
使用FileRequired对上传的文件类型进行限制。(如果用户上传HTML文件,而我们同时提供了视图函数获取上传后的文件,那么容易导致XSS攻击)

FileAllowed是在服务器端验证上传文件,使用HTML5中的accept属性也可以在客户端实现简单的类型过滤。

<input type=\”file\” id=\”profile_pic\” name=\”profile_pic\” accept=\”.jpg, .jpeg, .png, .gif\”>
通过设置Flask内置的配置变量MAX_CONTENT_LENGTH,我们限制请求报文的最大长度,单位为字节(byte):

# 限制最大长度为3M
app.config[\’MAX_CONTENT_LENGTH\’] = 3 * 1024 * 1024
当请求数据(上传大小)超过这个限制后,会返回413错误响应(Request Entiy Too Large)

4.4.2、渲染上传表单

在新创建的upload视图里,我们实例化表单类UploadForm,然后传入模板:

@app.route(\’/upload\’,methods=[\’GET\’,\’POST\’])
def upload():
form = UploadForm()

return render_template(\’upload.html\’,form=form)
在模板中渲染表单

<form method=\”post\” enctype=\”multipart/form-data\”>
{{ form.csrf_token }}
{{ form_field(form.photo) }}
{{ form.submit }}
</form>
当表单中包含文件上传字段时(即type属性为file的input标签),需要将表单的enctype属性设为“multipart/form-data”,这会告诉浏览器将上传数据发送到服务器,否则仅会把文件名作为表单数据提交。

4.4.3、处理上传文件

和普通的表单数据不同,当包含上传文件字段的表单提交后,上传的文件需要在请求对象的file属性(request.files)中获取。前面介绍过,这个属性是Werkzeug提供的ImmutableMultiDict字典对象,存储字段的name键值和文件对象的映射,比如:

ImmutableMultiDict([(\’photo\’,<FileStorage: u\’0f913b0fddcds.JPG\’ (\’image/jpeg\’)>)])
上传的文件会被Flask解析为Werkzeug中的FileStorage对象。当手动处理时,我们需要用文件上传字段的name属性值作为键获取对应的文件对象:

request.files.get(\’photo\’)
当使用Flask-WTF时,会自动帮我们获取对应的文件对象,这里我们仍使用表单类属性的data属性获取上传文件。

处理上传文件app.py:

@app.route(\’/upload\’,methods=[\’GET\’,\’POST\’])
def upload():
form = UploadForm()
if form.validate_on_submit():
f = form.photo.data
filename = random_filename(f.filename)
f.save(os.path.join(app.config[\’UPLOAD_PATH\’],filename))
flash(\’Upload success.\’)
session[\’filenames\’] = [filename]
return redirect(url_for(\’show_images\’))
return render_template(\’upload.html\’,form=form)
表单验证通过后,我们通过form.photo.data获取存储上传文件的FileStorage对象。接下来有三种方式处理文件名:

(1)使用原文件名

若能确定文件的来源安全,可直接使用原文件名,通过FileStorage对象的filename属性获取:

filename = f.filename
(2)使用过滤后的文件名

若支持用户上传文件,我们必须对文件名进行处理,因为攻击者可能会在文件名中加入恶意路径(比如加入上级目录的…)。我们可以使用Werkzeug提供的secure_filename()函数对文件名进行过滤,传递文件名作为参数,它会过滤掉所有危险字符,返回“安全的文件名”:

>>> form werkzeug import secure_filename
>>> secure_filename(\’avatar!@#//#\\\\%&$.jpg\’)
\’avatar.jpg\’
>>> secure_filename(\’avatar头像.jpg\’)
\’avatar.jpg\’
(3)统一文件名

secure_filename()函数非常方便,会过滤掉文件名中非ASCII字符。但如果文件名完全由非ASCII字符组成,那么会得到一个空文件名:

>>> secure_filename(\’头像.jpg\’)
\’jpg\’
更好的做法是使用统一的处理方式对所有上传的文件重新命名。随机文件名有很多种方式可以生成,下面是一个用Python内置的uuid模板生成随机文件名的random_filename()函数:

import uuid
def random_filename(filename):
ext = os.path.splitext(filename)[1]
new_filename = uuid.uuid4().hex + ext
return new_filename
# 这个函数接受原文件名作为参数,使用内置的uuid模块中的uuid4()方法生成新的文件名,并使用hex属性获取十六进制字符串,最后返回包含后缀的新文件名
在upload视图就调用了这个方法来获取随机文件名。

处理完文件名后就是将文件保存到文件系统了。我们在forms目录下创建了一个uploads文件夹,用于保存上传后的文件。指向这个文件夹的绝对路径存储在自定义配置变量UPLOAD_PATH中:

app.config[\’UPLOAD_PATH\’] = os.path.join(app.root_path,\’uploads\’)
为了保存文件需提前创建。对FileStorage对象调用save()方法即可保存,传入包含目标文件夹绝对路径和文件名在内的完整保存路径:

f.save(os.apth.join(app.config[\’UPLOAD_PATH\’],filename))
文件保存后,我们希望能够显示上传后的图片。为了让上传后的文件能够通过URL获取,我们创建一个视图函数来返回上传后的文件:

@app.route(\’/uploads/<path:filename>\’)
def get_file(filename):
return send_from_directory(app.config[\’UPLOAD_PATH\’],filename)
使用Flask提供的send_from_directory()函数来获取文件,传入文件的路径和文件名作为参数。

在upload视图保存文件后,使用flash()发送一个提示,将文件名保存到session中,最后重定向到show_images视图。show_images视图返回的upload.html模板将从session获取文件名,渲染出上传后的图片。

flash(\’Upload success.\’)
session[\’filenames\’] = [filename]
return redirect(url_for(\’show_images\’))

4.4.4、多文件上传

在客户端,通过在文件上传字段(type=file)加入multiple属性,就可以开启多选:

<input type=\”file\” id=\”file\” name=\”file\” multiple>
创建表单类时,可以直接使用WTForms提供的MultipleFileField字段实现,添加一个DataRequired验证器来确保包含文件:

from wtforms import MultipleFileField
class MultiUploadForm(FlaskForm):
photo = MultipleFileField(\’Upload Image\’, validators={DataRequired()})
submit = SubmitField()
表单提交时,在服务器端的程序中,对request.files属性调用getlist()方法并传入字段的name属性值会返回包含所有上传文件对象的列表。在multi_upload视图中,我们迭代这个列表,然后逐一对文件进行处理:

@app.route(\’/multi_upload\’,methods=[\’GET\’,\’POST\’])
def multi_upload():
form = MultiUploadForm()
if request.method == \’POST\’:
filenames = []
# 验证CSRF令牌
try:
validate_csrf(form.csrf_token.data)
except ValidationError:
flash(\’CSRF token error\’)
return redirect(url_for(\’multi_upload\’))
# 检查文件是否存在
if \’photo\’ not in request.files:
flash(\’This field is required.\’)
return redirect(url_for(\’multi_upload\’))
for f in request.files.getlist(\’photo\’):
# 检查文件类型
if f and allowd_file(f.filename):
filename = random_filename(f.filename)
f.save(os.path.join(
app.config[\’UPLOAD_PATH\’],filename
))
filenames.append(filename)
else:
flash(\’Invalid file type.\’)
return redirect(url_for(\’multi_upload\’))
flash(\’Upload success.\’)
session[\’filenames\’] = filenames
return redirect(url_for(\’show_images\’))
return render_template(\’upload.html\’,form=form)
当请求方法为POST时,我们对上传数据进行手动验证,主要包含以下几步:

手动调用flask_wtf.csrf.valitate_csrf验证CSRF令牌,传入表单中csrf_token隐藏字段的值。如果抛出wtforms.ValidationError异常则表明验证未通过。其中if \’photo\’ not in request.files用来确保字段中包含文件数据(相当于FileRequired验证器),如果用户没有选择文件就提交表单则request.files将为空。if f用来确保文件对象存在,这里也可以检查f是否是FileStorage实例allowed_file(f.filename)调用了allowed_file()函数,传入文件名。这个函数相当于FileAllowed验证器,用来验证文件类型,返回布尔值:
app.config[\’ALLOWED_EXTENSIONS\’] = [\’png\’,\’jpg\’,\’jpeg\’,\’gif\’]
def allowed_file(filename):
return \’.\’ in filename and \\
filename.rsplit(\’.\’,1)[1].lower() in app.config[\’ALLOWED_EXTENSIONS\’]
在上面几个验证语句里,如果没有通过验证,则使用flash()函数显示错误信息,然后重定向到multi_upload视图。

4.5、使用Flask-CKEditor集成富文本编辑器

富文本编辑器即WYSIWYG(What You See Is What You Get,所见即所得)编辑器,类似我们经常使用的文本编辑软件。它提供一系列按钮和下拉列表来为文本设置格式,编辑状态的文本样式即最终呈现出来的样式。在Web程序中,这种编辑器也称为HTML富文本编辑器,因为它使用HTML标签来为文本定义样式。

CKEditor是一个开源的富文本编辑器,它包含丰富的配置选项,而且有大量第三方插件支持。扩展Flask-CKEditor简化了在Flask程序中使用CKEditor的过程,首先安装:

pip install flask-ckeditor
然后实例化Flask-CKEditor提供的CKEditor类,传入程序实例

from flask_ckeditor import CKEditor
ckeditor = CKEditor

4.5.1、配置富文本编辑器

Flask-CKEditor提供了许多配置变量来对编辑器进行设置,常用的配置:

配置键默认值说明CKEDITOR_SERVE_LOCALFalse设为True会使用内置的本地资源CKEDITOR_PKG_TYPE\’standard\’CKEditor包类型,可选值为basic。standard和fullCKEDITOR_LANGUAGE\’\’界面语言,传入ISO 639格式的语言码CKEDITOR_HEIGHT\’\’编辑器高度CKEDITOR_WIDTH\’\’编辑器宽度
在示例程序中,为方便开发,使用内置的本地资源

app.config[\’CKEDITOR_SERVE_LOCAL\’] = True
配置变量CKEDITOR_LANGUAGE用来固定界面的显示语言(简体中文和繁体中文对应的配置分别为zh-cn和zh),如果不设置,默认自动匹配。

其他具体访问Flask-CKEditor文档的插件集成部分。

4.5.2、渲染富文本编辑器

富文本编辑器在HTML中通过文本区域字段表示,即<textarea></textarea>。Flask-CKEditor通过包装WTForms提供的TextAreaField字段类型实现一个CKEditorField字段类,我们使用它来构建富文本编辑框字段。

# 文章表单
from flask_wtf import FlaskForm
from flask_ckeditor import CKEditorField
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length
class RichTextForm(FlaskForm):
title = StringField(\’Title\’,validators=[DataRequired(),Length(1,50)])
body = CKEditorField(\’Body\’,validators=[DataRequired()])
submit = SubmitField(\’Publish\’)
文章正文字段(body)使用的CKEditorField字段类型从Flask-CKEditor导入。可像其他字段一样定义标签、验证器和默认值。同样使用data属性获取数据。

渲染模板上也一样:

{% extends \’base.html\’ %}
{% from \’macro.html\’ import form_field %}
{% block content %}
<h1>Integrate CKEditor with Flask-CKEdtior</h1>
<form method=\”post\”>
{{ form.csrf_token }}
{{ form_field(form.title) }}
{{ form_field(form.body) }}
{{ form.submit }}
</form>
{% endblock %}
{% block scripts %}
{{ super() }}
{{ ckeditor.load() }}
{% endblock %}
渲染CKEditor编辑器需要加载相应的JavaScript脚本。为方便开发,可以使用Flask-CKEditor在模板中提供的ckeditor.load()方法加载资源,它默认从CDN加载资源,将CKEDITOR_SERVE_LOCAL设为True会使用扩展内置的本地资源,内置的本地资源包含了几个常用的插件和语言包。ckeditor.load()方法支持通过pkg_type参数传入包类型,这会覆盖CKEDITOR_PKG_TYPE的值,额外的version参数可以设置从CDN加载的CKEditor版本。

作为替代,可访问CKEditor官网提供的构建工具构建自己的CKEditor包。

若使用配置变量设置了编辑器的高度、宽度和语言或是其他插件配置,需要使用ckeditor.config()方法加载配置,传入对应表单类属性名。这个方法需要在加载CKEditor资源后调用:

{{ ckeditor.config(name=\’body\’) }}

4.6、单个表单多个提交按钮

# 包含两个按钮的表单:
class NewPostForm(FlaskForm):
title = StringField(\’Title\’,validators=[DataRequired(),Length(1,50)])
body = TextAreaField(\’Body\’,validators=[DataRequired()])
save = SubmitField(\’Save\’) # 保存按钮
publish = SubmitField(\’Publish\’) # 发布按钮
机制:当表单数据通过POST请求提交时,Flask会把表单数据解析到request.form字典。如果表中有两个提交字段,那么只有被单击的提交字段才会出现在这个字典中。当我们对表单类实例或特定的字段属性调用data属性时,WTForms会对数据进行处理。对于提交字段的值,转换为布尔值;被单击的提交字段的值将是True,否则False。

# 判断被单击的提交按钮
@app.route(\’/two-submits\’,method=[\’GET\’,\’POST\’])
def two_submits():
form = NewPostForm()
if form.validate_on_submit():
if form.save.data:
# save it …
flash(\’You click the \”Save\” button.\’)
elif form.publish.data:
# publish it…
flash(\’You click the \”Publish\” button.\’)
return redirect(url_for(\’index\’))
return render_template(\’2submit.html\’,form=form)
返回主页的按钮与表单提交无关,直接在HTML中手动添加即可。

4.7、单个页面多个表单

当在同一个页面上添加多个表单时,我们要解决的一个问题就是在视图函数中判断当前被提交的是哪个表单。

4.7.1、单视图处理

第一步:为两个表单的提交字段设置不同的名称

class SigninForm(FlaskForm):
username = StringField(\’Username\’,validators=[DataRequired(),Length(1,20)])
password = PasswordField(\’Password\’,validators=[DataRequired(),Length(8,128)])
submit = SubmitField(\’Sign in.\’)
class RegisterForm(FlaskForm):
username = StringField(\’Username\’,validators=[DataRequired(),Length(1,20)])
email = StringField(\’Email\’,validators=[DataRequired(),Email(),Length(1,254)])
password = PasswordField(\’Password\’,validators=[DataRequired(),Length(8,128)])
submit2 = SubmitField(\’Register\’)
第二步:在视图函数中处理多个表单

@app.route(\’/multi-form\’,method=[\’GET\’,\’POST\’])
def multi_form():
signin_form = SigninForm()
register_form = RegisterForm()
if signin_form.submit.data and signin_form.validate():
username = signin_form.username.data
flash(\’%s, you just submit the Signin Form.\’ % username)
return redirect(url_for(\’index\’))
if register_form.submit2.data and register_form.validate():
username = register_form.username.data
flash(\’%s, you just submit the Register Form.\’ % username)
return redirect(url_for(\’index\’))
return render_template(\’2form.html\’,signin_form=signin_form,register_form=register_form)
以登录表单(SigninForm)的if判断为例,如果signin_form.submit1.data的值为True,那就说明用户提交了登录表单,这时我们手动调用signin_form.validate()对这个表单进行验证。

这两个表单类实例通过不同的变量名称传入模板,以便在模板中相应渲染对应的表单字段:

<form method=\”post\”>
{{ signin_form.csrf_token }}
{{ form_filed(signin_form.username) }}
{{ form_filed(signin_form.password) }}
{{ signin_form.submit1 }}
</form>
<h2>Register Form</h2>
<form method=\”post\”>
{{ register_form.csrf_token }}
{{ form_field(register_form.username) }}
{{ form_field(register_form.email) }}
{{ form_field(register_form.password) }}
</form>

4.7.2、多视图处理

除了通过提交按钮判断,更简洁的方法是通过分离表单的渲染和验证实现。这时表单的提交字段可以使用同一个名称,在视图函数中处理表单时也只需要使用我们熟悉的form.validate_on_submit()方法。

当处理多个表单时,我们可以把表单的渲染在单独的视图函数中处理:

@app.route(\’/multi-form-multi-view\’)
def multi_form_multi_view():
signin_form = SigninForm()
register_form = RegisterForm()
return render_template(\’2form2view.html\’,signin_form=signin_form,register_form=register_form)
这个视图只负责处理GET请求,实例化两个表单类并渲染模板。另外我们在为每一个表单单独创建一个视图函数来处理验证工作。处理表单提交请求的视图仅监听POST请求:

@app.route(\’/handle-signin\’,methods=[\’POST\’])
def handle_signin():
signin_form = SigninForm()
register_form = RegisterForm()
if signin_form.validate_on_submit():
username = signin_form.username.data
flash(\’%s,you just submit the Signin Form.\’ % username)
return redirect(url_for(\’index\’))
return render_template(\’2form2view.html\’,signin_form=signin_form,register_form=register_form)
@app.route(\’/handle-register\’,methods=[\’POST\’])
def handle_register():
signin_form = SigninForm()
register_form = RegisterForm()
if register_form.validate_on_submit():
username = register_form.username.data
flash(\’%s,you just submit the Register Form.\’% username)
return redirect(url_for(\’index\’))
return render_template(\’2form2view.html\’,signin_form=signin_form,register_form=register_form)
在HTML中,表单提交请求的模板URL通过action属性设置。为了让表单提交时将请求发送到对应的URL,我们需要设置action属性:


<h2>Login Form</h2>
<form method=\”post\” action=\”{{ url_for(\’handle_signin\’) }}\”>

</form>
<h2>Register Form</h2>
<form method=\”post\” action=\”{{ url_for(\’handle_register\’) }}\”>

</form>

这种方法有一个显著的缺点。如果验证未通过,要将错误消息的form.errors字典传入模板。在处理表单的视图中传入表单错误信息,意味着需要再次渲染模板,但如果视图函数中还涉及大量要传入模板的操作,那么这种方式会带来大量的重复。

对于这个问题,一般的解决方法是通过其他方式传递错误信息,然后统一重定向到渲染表单页面的视图。例如使用flash()函数迭代form.errors字典发送错误消息,然后重定向到用来渲染表单的multi_form_multi_view视图:

def flash_errors(form):
for field,errors in form.errors.items():
for error in errors:
flash(u\”Error in the %s field – %s\” % (
getattr(form,field).label.text,
error
))
若希望像往常一样在表单字段下渲染错误信息,可以直接将错误消息字典form.errors存储到session中,然后重定向到用来渲染表单的multi_form_multi_view视图。在模板中渲染表单字段错误时添加一个额外的判断,从session中获取并迭代错误消息。

致谢

在此,我要对所有为知识共享做出贡献的个人和机构表示最深切的感谢。同时也感谢每一位花时间阅读这篇文章的读者,如果文章中有任何错误,欢迎留言指正。 

学习永无止境,让我们共同进步!!

#以上关于Flask之表单的相关内容来源网络仅供参考,相关信息请以官方公告为准!

原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/92408.html

(0)
CSDN的头像CSDN
上一篇 2024年6月27日
下一篇 2024年6月27日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注