Flask_8

自定义验证器

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表单字段代码。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章