自定义验证器
WTForms中的验证器指的是在定义字段时传入validatiors参数列表的可调用对象。
行内验证器
以下代码中定义的validate_answer方法对answer进行验证,如果标签内填入的不是66,抛出异常‘Must be 66!’。
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired()]) submit = SubmitField('提交') def validate_answer(self, field): if field.data != 66: raise ValidationError('Must be 66!')
当表单类中包含“validate_字段属性名”这样的方法时,在验证字段时会同时调用这个方法来验证对应字段,这也是为什么表单字段不能以validate开头的原因。验证方法接收两个参数,就是这段代码中的self和field,前者为表单实例,后者为字段对象,我们可以通过field.data来接收字段数据。ValidationError()用来抛出异常,需要从wtforms.validators模块中导入。
演示流程如下:
编写视函数图
@app.route('/hang_nei_yan_zheng_qi', methods=['POST', 'GET']) def hang_nei_yan_zheng_qi(): form = AnotherForm() if form.validate_on_submit(): ##验证成功返回3333333... return '33333333333' return render_template('hang_nei_yan_zheng_qi.html', form=form)
构造表单类
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired()]) submit = SubmitField('提交') def validate_answer(self, field): if field.data != 66: raise ValidationError('Must be 66!')
编写模板 hang_nei_yan_zheng_qi.html
<form method="post"> {{ form.csrf_token }} {{ form.answer.label }}{{ form.answer }}<br> {% for message in form.answer.errors %} ##显示抛出的异常 {{ message }}<br> {% endfor %} {{ form.submit }}<br> </form>
全局验证器
通过定义函数来实现一个全局通用的验证器。如果这是一个不需要传入参数的验证器,要实现它非常简单。以上面行内验证器为例,只需要把验证器
def is_66(form, field): if field.data != 66: raise ValidationError('Must be 66!')
写在外部,然后在需要的时候在validators里传入验证器对象即可。
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired(), is_66]) submit = SubmitField('提交')
如果需要一个可以接收参数的验证器,需要把验证函数实现为一个工厂函数。
def is_66_or_sth_else(number=66): def _is_66(form, field): if field.data != number: raise ValidationError('Must be %d!'% number) return _is_66
修改成只有77才能通过验证
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired(), is_66_or_sth_else(77)]) submit = SubmitField('提交')
文件上传
在HTML中,渲染一个文件上传字段只需要将<input>标签的type属性设置为file。在服务器端,可以和普通数据一样上传文件数据并保存,此外出于安全考虑或者其他原因,我们还需要注意验证文件类型、验证文件大小、过滤文件名。
定义上传表单
在python表单类中创建文件上传字段时,我们使用扩展Flask-WTF提供的FileField类,它继承WTForms提供的上下文字段FlieField,添加了对Flask的集成。下面是一个包含文件上下文字段的表单
class UploadForm(FlaskForm): photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif'])]) submit = SubmitField()
这里用到了2个验证器
FileRequired() 验证是否包含文件对象
FileAllowed() 验证文件类型
如果想限制上传文件大小,需要修改Flask内置变量MAX_CONTENT_LENGTH。例如修改最大上传为3M
app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024
当上传超过限制,就会返回413错误响应。
渲染上传表单
实例化表单类
@app.route('/upload', methods=['POST', 'GET']) 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",这会告诉浏览器将上传数据发送到服务器,否则仅会把文件名作为表单数据提交。
处理上传文件
当包含文件上传字段的表单提交后,上传的文件需要在请求对象的files属性(request.files)中获取。手动处理时,要使用文件上传字段的name属性值作为键获取对应文件对象,例如
request.files.get('photo')
而Flask-WTF会自动帮我们获取对应文件对象
@app.route('/upload', methods=['POST', 'GET']) 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对象,接下来处理文件名。
通常有3种处理文件名的方法
1.使用原文件名
filename = f.name
2.使用过滤后的文件名
为了防止攻击者在文件名中添加恶意路径,我们用secure_filename()函数来过滤文件名,这里就不演示拉。
3.统一重命名
secure_filename()函数很方便,它会过滤掉所有非ASCII字符,如果文件名全由非ASCII码构成,那么就会返回一个空文件名。为了避免这种状况,最好的办法就是统一对所有上传的文件重命名。随机文件名有很多办法实现,下列函数使用Python内置uuid模块来生成随机文件名
def random_filename(filename): ext = os.path.splitext(filename)[1] new_filename = uuid.uuid4().hex + ext return new_filename
这个函数接收原文件名作为参数,使用uuid模块中的uuid4()方法生成新的文件名,并使用hex属性获取16进制字符串最后返回包含后缀的新文件名。
处理完文件名后,就该将文件保存到文件系统中了。我在目录下创建了一个upload文件夹用来保存上传的文件。指向这个文件夹的绝对路径存储在自定义配置变量PULOAD_PATH中
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
app.root_path存储了程序实例所在脚本的绝对路径。
为了让上传后的文件能够通过URL获取,我们创建一个视图来返回上传后的图片
@app.route('/uploads/<path:filename>') def get_file(filename): return send_from_directory(app.config['UPLOAD_PATH'],filename)
在upload视图中保存文件后,使用flash()来提示保存成功,将文件名保存到session中,最后重定向到show_images视图。show_images视图返回的upload.html模板中将从session中获取文件名,渲染出上传的图片。
flash('Upload success!') session['filenames'] = [filename] return redirect(url_for('show_images'))
upload.html如下
<head> {% from 'macros.html' import form_field %} </head> <form method="post" enctype="multipart/form-data"> {{ form.csrf_token }} {{ form_field(form.photo) }} {{ form.submit }} </form>
form_field是一个表单渲染宏,接收表单实例的字段属性和附加的关键字参数,返回包含<label>标签、表单字段、错误消息列表的HTML表单字段代码。