Flask知识点串烧(四)--- 表单

1. WTForms和Flask-WTF

  • WTForms是一个用Python编写的表单库,它使得表单的定义、验证(服务器端)和处理变得非常轻松;扩展Flask-WTF集成了WTForms,将表单数据解析、CSRF保护、文件上传等功能与Flask集成。Flask-WTF默认为每个表单启用CSRF保护,它会为我们自动生成和验证CSRF令牌。默认情况下,Flask-WTF使用程序秘钥来对CSRF令牌进行签名,所以我们需要为程序设置秘钥。
  • 一个表单由若干个输入字段组成,这些字段分别用表单类的类属性来表示。每个字段属性通过实例化WTForms提供的字段类表示,字段属性的名称将作为对应HTML<input>元素的name属性及id属性值。字段属性名称大小写敏感,不能以下划线和validate开头。
  • WTForms字段类:
    • BooleanField
    • DateField
    • DateTimeField
    • FileField
    • FloatField
    • IntegerField
    • RadioField
    • SelectField
    • StringField
    • HiddenField
    • PasswordField
    • TextAreaField
  • 实例化表单类常用参数:
    • label 字段标签<label>的值,也就是渲染后显示在输入字段前的文字;
    • render_kw 一个字典,用来设置对应HTML<input>标签的属性,比如传入{'placeholder':'Alex'},则<input>标签的placeholder属性设为'Alex';
    • validators 一个列表,包含一系列验证器,会在表单提交后被逐一调用,验证表单数据;
    • default 字符串或可调用对象,用来为表单字段设置默认值。
  • 验证器从wtforms.validators模块导入,常用的验证器如下:
    • DataRequired(message=None)
    • Email(message=None)
    • EqualTo(fieldname, message=None)
    • InputRequired(message=None)
    • Length(min=-1, max=-1, message=None)
    • NumberRange(min=None, max=None, message=None)
    • Optional(strip_whitespace=True) 允许输入值为空,并跳过其它验证;
    • Regexp(regex, flags=0, message=None) 
    • URL(required_tld=True, message=None)
    • AnyOf(values, message=None, values_formatter=None)
    • NoneOf(values, message=None, values_formatter=None)
  • 在实例化验证器时,message参数用来传入自定义错误消息,默认使用内置的英文错误消息;
  • validators参数接收一个可调用对象组成的列表,内置的验证器都实现了__call__()方法,所以需要在验证器后添加括号;
  • 使用Flask-WTF定义表单时,表单类要继承flask-wtf.FlaskForm类。FlaskForm类继承自Form。
  • Flask-WTF会在实例化表单类时添加一个CSRF令牌值的隐藏字段,字段名为csrf_token;
  • <label>的HTML代码可以通过"form.字段名.label"的形式获取;
  • 默认情况下WTForms输出的字段HTML代码只会包含id和name属性,属性值均为表单类中对应的类属性名称。若要添加额外的属性,有如下2种方式:
    • 使用render_kw属性:StringField('User Name', render_kw={'place_holder':'name'})
    • 在调用字段时传入:form.username(style='width:200px;', class_='bar') class是python的保留关键字,这里我们使用class_代替class属性;在模板中调用时可以直接使用class
    • 在模板中渲染表单,需要把表单类实体传入模板,之后渲染各个字段的标签和字段本身,还需要调用form.csrf_token属性渲染CSRF令牌。在提交表单后会自动验证,为了确保表单通过验证,必须在表单中手动渲染这个字段。
    • 使用render_kw字典或是在调用字段时传入参数来定义字段的额外HTML属性,通过这种方式添加CSS类,我们可以编写一个Bootstrap风格的表单。

2.提交表单

  • HTML表单中控制提交行为的属性:
    • action 表单提交时发送请求的目标URL,默认为当前页面对应的URL;
    • method 提交表单的HTTP方法,目前仅支持使用GET和POST方法;
    • enctype 表单数据的编码类型,默认为:application/x-www-form-urlencoded;当表单中包含文件上传字段时,需要设为:multipart/form-data,还可以设为纯文本类型text/plain;
  • GET方法仅适用于长度不超过3000个字符,且不包含敏感信息的表单。因为这种方式会将表单数据暴露在URL中,容易被攻击者截获,出于安全考虑,我们一般使用POST方法提交表单。使用POST方法时,按照默认的编码类型,表单数据将被存储在请求主体中;

3.验证表单数据

  • 客户端验证和服务器端验证:
    • 客户端验证可以增强用户体验,降低服务器负载。可以使用H5提供的属性或使用JS实现更完善的验证机制。
    • 无论是否使用客户端验证,服务器端验证都是必不可少的,因为用户可以通过各种方式绕过客户端验证,比如在客户端设置禁用JS。
  • WTForms验证机制:
    • WTForms验证表单字段的方式是在实例化表单类时传入表单数据,然后对表单实例调用validate()方法,逐个对字段调用字段实例化时定义的验证器,返回验证结果的布尔值;
    • 使用Flask-WTF时,表单类继承的FlaskForm基类默认会从request.form获取表单数据,所以不需要手动传入;
    • POST方法提交表单使用request.form属性获取数据字典;GET方法提交的表单则使用request.args获取数据字典;
  • 在视图函数中验证表单:
    • Flask-wtf提供的validate_on_submit()方法 == request.method == 'POST' and form.validate(),这会逐个字段(包含CSRF令牌字段)调用附加的验证器进行验证;
    • 验证返回True就可以继续获取表单数据(通过form.字段名属性名.data);
    • 最后通过有PRG技术重定向到一个GET请求,来防止重复提交表单;
  • 在模板中渲染错误消息
    • 对验证未通过的字段,WTForms会把错误消息添加到表单类的errors属性中,如:form.username.errors返回username字段的错误消息列表;
    • 在模板中使用for循环迭代错误消息列表;

设置错误消息语言(不重要,有更好的方式为Flask应用程序添加国际化和本地化支持)

  • step1:app.config['WTF_I18N_ENABLED'] = False Flask-WTF将会使用WTForms内置的错误消息翻译;
  • step2:在自定义基类中定义Meta类,并在locals列表中加入简体中文的地区字符zh;
  • step3:集成这个MyBaseForm即可将错误消息设为中文。

使用宏渲染表单

  • 在渲染模板时我们有大量的工作要做:
    • 调用对应的label属性,获取<label>定义
    • 调用字段属性获取<input>定义
    • 渲染错误消息
  • 为了避免重复上述代码,可以定义一个宏来渲染表单字段;这个宏接收表单类实例的字段属性和附加的关键字参数作为输入,返回包含<label>标签、表单字段、错误消息列表的HTML表单字段代码;
  • 甚至可以编写一个渲染Bootstrap风格表单的宏,不过,这类复杂的工作可以交给扩展来帮我们完成;

自定义验证器

  • 验证器是指在定义表单字段时传入validators参数列表的可调用对象。
  • 行内验证器:当表单类中包含以”validate_字段属性名“形式命名的方法时,在验证字段数据时会同时调用这个方法来验证对应字段;验证方法接收2个参数,依次为form和filed,前者为表单实例,后者为字段对象,可以通过field.data取出字段数据,这2个参数将在验证表单时被传入。验证出错时抛出从wtforms.validators模块导入的ValidationError异常,传入错误消息作为参数。由于这种方法仅用来验证特定的表单类字段,所以又称为行内验证器。
  • 全局验证器:如果需要一个可重用的通用验证器,可以定义一个函数实现。该函数和表单类中定义的验证方法完全相同,def is_42(form, field);因为validators列表中传入的验证器必须是可调用对象,所以validators=[is_42]传入的是函数对象;
  • 工厂函数形式的全局验证器(即返回一个可调用对象的函数):在验证器需要传入参数对验证过程进行设置时,如自定义message参数,这时验证函数应该实现成工厂函数,validators列表中传入对工厂函数的调用。
  • 在更复杂的场景下,我们可以用实现了__call__()方法的类(可调用类)来编写验证器。

文件上传

       HTML中文件上传字段为<input type="file">;在服务器端,我们必须考虑安全问题,文件上传问题也是比较流行的攻击方式。除了常规的CSRF防范,我们还需要关注下面问题:

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

定义上传表单

  • 扩展Flask-WTF提供的FileField类,集成自WTForms提供的上传字段FileField,添加了Flask集成;
  • flask-wtf.file模块下提供了2个文件相关的验证器:
    • FileRequired(message=None) 验证是否包含文件对象;
    • FileAllowed(upload_set, message=None) 用来验证文件类型,upload_set参数用来传入包含允许的文件后缀名列表;
  • Flask-WTF提供的FileAllowed在服务端验证文件类型,H5的accept属性也可以在客户端实现简单验证;accept=".jpg, .jpeg, ..."
  • 设置Flask内置的配置变量MAX_CONTENT_LENGTH可以限制报文的最大长度,单位为字节(byte)。app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024------将最大长度限制为3M,当请求数据(上传文件大小)超过这个限制后,会返回413错误响应;

渲染上传表单

       当表单中包含上传文件的字段时,需要将表单的enctype属性设置为"multipart/form-data",这会告诉浏览器将上传数据发送到服务器,否则会仅把文件名作为表单数据提交;

处理上传文件

  • 当包含上传文件的表单提交后,上传的文件需要在请求对象的files属性中获取:request.files.get('字段名'),获取的类型为FileStorage;
  • 当使用Flask-WTF时,它会自动帮我们获取对应的文件对象,然后使用表单类属性的data获取:form.字段名.data获取存储上传文件的FileStorage对象;
  • 接下来我们需要处理文件名,通常有3种方式:
    • 使用原文件名:在确保文件的来源安全时,可以直接使用原文件名:filename = f.filename
    • 使用过滤后的文件名:因为攻击者可能在文件名中加入恶意路径,就有可能导致服务器上的系统文件被覆盖或篡改,还有可能执行恶意脚本。Werkzeug提供的secure_filename()可以过滤掉文件名中的所有非ASCII字符,返回“安全的文件名”。若文件名完全有非ASCII组成,将得到一个空文件名。
    • 同一重命名:使用Python内置的uuid(通用唯一标识符)模块生成随机文件名:uuid.uuid4().hex;
  • 对FileStorsge对象调用save方法,传入包含绝对路径的文件名即可保存文件;
  • 通过URL获取上传后的文件:Flask提供的send_from_directory(路径,文件名)获取文件。

多文件上传

  • Flask-WTF当前版本0.14.2中并未添加对多文件上传的渲染和验证支持,因此需要我们在视图函数中手动获取文件并进行验证;
  • 客户端:<input type="file" is="file" name="file" multiple>
  • 创建表单类时使用WTForms提供的MultipleFileField字段实现;
  • 提交表单时在服务器端的程序中,request.files.getlist('字段名')获取文件对象列表;
  • 需手动调用flask_wtf.csrf.validate_csrf验证CSRF令牌,传入表单中csrf_token隐藏字段的值。验证失败时会抛出wtforms.ValidationError异常;
  • 如果用户没有选择文件就提交表单则request.files为空;

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

  • 配置富文本编辑器
    • CKEditor是一个开源的富文本编辑器,它包含丰富的配置选项,而且有大量第三方插件支持;
    • pipenv install flask-ckeditor
    • Flask-CKEditor提供了许多配置变量来对编辑器进行设置,如编辑器的高度、宽度、语言等;配置键盘以CKEDITOR_开头;
  • 渲染富文本编辑器
    • Flask-CKEditor通过包装WTForms提供的TextAreaField字段类型实现了一个CKEditorField字段类,我们使用它来构建富文本编辑框字段;
    • 在模板中引入SKEditor相关的js文件;(更多内容参考Flask-CKEditor官方文档)

单个表单多个提交按钮

  • 在某些情况下,一个表单具有多个提交按钮,比如创建文章的表单中有“发布”和“保存草稿”等不同的按钮。
  • 当表单数据通过POST请求提交时,Flask会把表单数据解析到request.form字典。如果表单中有2个提交字段,那么只有被点击的提交字段才会出现在request.form字典中;
  • 当我们对表单实例或字段属性调用data属性时,WTForms会对数据做进一步处理:对于提交字段的值,它会将其转换为布尔值,被单击的提交字段的值为True,未被单击的提交字段的值为False。

单个页面多个表单

  • 有时单个页面包含多个表单,比如在程序的主页上同时添加登录和注册表单;
  • 单视图处理:
    • 为2个表单的提交字段设置不同的名称;
    • 在视图中分别实例化2个表单,根据提交字段的值来区分被提交的表单;(被提交的submit值为True)
    • 并且手动调用from.validate()对表单进行验证;
  • 多视图处理:分离表单的渲染和验证
    • 一个视图只负责处理GET请求,实例化表单类并渲染模板;
    • 再为每一个表单单独创建一个视图函数来处理验证工作。
    • 在HTML中,表单提交的目标URL通过action属性设置。为了让表单提交时将请求发送到对应的URL,我们需要设置action属性。
发布了132 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geroge_lmx/article/details/104219394