美文网首页Flaskflask
Flask系列:表单

Flask系列:表单

作者: 超net | 来源:发表于2016-02-11 09:33 被阅读4313次

    这个系列是学习《Flask Web开发:基于Python的Web应用开发实战》的部分笔记

    网站需要能提供一个表格,让用户提供信息进行注册、写点东西

    处理POST请求中提交的表单数据

    • 用 flask 的请求对象,request.form,但功能很初级,需要做很多重复、额外的操作
    • 一个名为 Flask-WTF 的扩展,将 WTForms 集成到 flask 程序,可以帮助完成很多事情

    CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击

    恶意网站在受害者不知情的情况下,伪造请求,以受害者名义(利用用户浏览器中的 cookie )发送给受害者已登录的受攻击站点,在自身没有授权的情况下执行用户的某些权限的操作。

    IBM CSRF
    wiki CSRF
    wiki cookie

    为了进行防御,需要在响应的表单(不在 cookie 中)中添加一个攻击者无法伪造的信息,即随机产生的token,然后在提交表单的请求中送回,进行比对验证

    在程序的配置中设置一个密钥,启用 CSRF 保护

    CSPR_ENABLED = True # 启用 CSPR (跨站请求伪造) 保护,在表单中使用,隐藏属性
    SECRET_KEY = 'this-is-safe-and-you-never-guess-it' # 建立一个用于加密的密钥,验证表单
    

    使用:

    如果是使用 Flask-WTF 自动化表单的一些操作,会自动用密钥生成一个随机的加密令牌,放入响应中,并要求在用户提交的请求中送回,通过对比是否一致,判断表单的真伪

    如果是手动写,添加

        {{ form.hidden_tag() }}
    

    表单类

    表单的创建,可以通过继承从 Flask-WTF 导入的Form父类实现

    from flask.ext.wtf import Form # 表单类,从第三方扩展的命名空间 导入
    

    表单类中需要定义 属性/字段,值是字段类型类,就是将要在 HTML 中显示的表单各个字段,其实就是对 HTML 表单各种标签的包装

    from wtforms import StringField, BooleanField, SubmitField, PasswordField, TextAreaField, SelectField # 字段类型类,字符串、布尔值、提交、密码、文本区域、选择框
    

    字段类型类(说明文本,验证器列表)

    验证器列表,检查用户填写表单时输入的内容是否符合我们的期望,有多个验证器时,需要同时通过验证

    from wtforms.validators import DataRequired, Required, Length, Email  , Regexp, EqualTo # 验证器,直接从 wtforms.validators 导入
    
    # 普通用户的资料编辑表单
    class EditProfileForm(Form):
        name = StringField('Real name', validators=[Length(0, 64)]) # 因为是可选,允许长度为0
        location = StringField('Location', validators=[Length(0, 64)])
        about_me = TextAreaField('About me') # 文本区域,可以多行,可以拉动
        submit = SubmitField('Submit')
    

    可以在 表单类 中,通过定义validate_开头的类方法,自定义验证器,用ValidationError定义报错的提示信息。自定义的验证器会和在用户提交表单时自动被调用

    from wtforms import ValidationError
    
    
    # 管理员的资料编辑表单
    class EditProfileAdminForm(Form):
        # 检查提交的昵称
        # 如果字段值没有变,跳过验证
        # 如果新的与旧的不同,但与其他用户的昵称冲突,报错
        # 如果有变化,且与其他用户不冲突,验证通过
        def validate_username(self, field):
            if field.data != self.user.username and User.query.filter_by(username=field.data).first():
                raise ValidationError('Username already in use.')
    

    渲染

    将表单渲染成 HTML

    如果是 WTF()

    {% import "bootstrap/wtf.html" as wtf %}
    
    {{ wtf.quick_form(form) }}  # 渲染时,将 form 作为参数传递给模板
    

    如果是手动写

    <form method="POST"> # 表单提交方式为 POST
        {{ form.hidden_tag() }}
        {{ form.name.label }} {{ form.name() }}
        {{ form.submit() }}
    </form>
    

    在视图中的处理

    导入定义的表单类

    from .forms import  EditProfileForm
    

    在匹配 URL 和 HTTP 的请求方式时,需要添加 POST 方法,默认只处理 GET 请求

    其实 HTTP 中 GET方式 和 POST方式 都可以提交表单中填写的数据,区别是,GET方式会将数据以查询字符串的形式放到 URL 中提交,POST方式 会将数据保存在 HTTP 主体中提交

    # 个人主页编辑页面
    @main.route('/edit_profile', methods=['GET', 'POST'])
    

    实例化表单类

        form = EditProfileForm()
    

    查看提交的数据是否能被所有验证器验证通过,如果通过,通过form.字段名.data获取指定字段的内容,并保存到数据库,否则,设置表单字段为当前值(如果是修改或编辑页面),或直接返回空表单(注册、登陆 页面)

        if form.validate_on_submit():
            current_user.name = form.name.data
            current_user.location = form.location.data
            current_user.about_me = form.about_me.data
            db.session.add(current_user)
            db.session.commit()
            flash('Your profile has been updated')
            return redirect(url_for('.user', username=current_user.username)) # 提交后,转到个人主页,显示编辑结果
        # 如果是 GET,或 验证器不通过,显示目前的资料内容
        form.name.data = current_user.name 
        form.location.data = current_user.location
        form.about_me.data = current_user.about_me
        return render_template('edit_profile.html', form=form)
    

    登陆页面的例子:

    from .forms import LoginForm
    
    # 登录页面,填写表单、认证
    @auth.route('/login', methods = ['GET', 'POST']) # 接收 url 为 `/login`, HTTP 方式为 'GET' 和 'POST' 的请求
    def login():
        form = LoginForm() # 创建实例,表示表单
        if form.validate_on_submit(): # 如果 通过 post 提交的表单,数据通过了所有验证器的检查
            user = User.query.filter_by(email=form.email.data).first() 
            if user is not None and user.verify_password(form.password.data): 
                login_user(user, form.remember_me.data) 
                return redirect(request.args.get('next') or  url_for('main.index'))
            flash('Invalid username or password.')
    
        return render_template('auth/login.html', form=form)
    

    刷新

    如果提交表单后,点击浏览器刷新按钮,会要求在浏览器再次提交表单前进行确认

    这是因为,刷新页面时,浏览器会重新发送最后一次发送过的请求

    为了避免用户遇到这种情况,需要避免让 POST 请求作为浏览器最后发出的一个请求

    POST/重定向/GET 模式

    对于用户的 POST 请求,如果验证通过,使用重定向作为响应,使得浏览器向 响应中的重定向URL 发送 GET 请求,这样就可以正常刷新了

    返回重定向响应的方法

    return redirect(url_for('auth.login')) 使用redirect()函数,将目标 URL 作为参数

    # 注册页面
    @auth.route('/register', methods = ['GET', 'POST']) # 初次 get 请求获取空白表单,然后 post 请求提交填写后的表单
    def register():
        form = RegistrationForm()
        if form.validate_on_submit():
    
            return redirect(url_for('auth.login')) # 重定向到登陆页面, 让用户登陆。浏览器向 login url 发送 get 请求。
        return render_template('auth/register.html', form=form)
    

    通用密钥

    SECRET_KEY 不仅可以用于 CSRF 还可以用于加密 cookie

    cookie 是网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据

    默认情况下,用户会话保存在客户端 cookie 中,使用设置的 SECRET_KEY 进行加密签名。如果篡改了 cookie 中的内容,签名就会失效,会话也会随之 失效。

    在下一个返回的响应中显示当前处理结果的消息

    请求完成后,有时需要让用户知道状态发生了变化。

    通过函数flash()get_flashed_messages(),可以将当前请求处理的结果,在下一个返回的响应中显示

    两步:

    • 在 view 中,用函数flash()定义、收集消息
    # 退出,跳转到主页
    @auth.route('/logout')
    @login_required # 保护路由,只允许已登陆用户访问。Flask-Login 提供的装饰器,将用户重定向到 登陆页面
    def logout():
        logout_user() # 删除并重设用户会话
        flash('You have been logged out.')
        return redirect(url_for('main.index'))
    

    当前处理的响应是重定向URL,然后在浏览器向 重定向的URL 请求的响应中显示

    需要注意,可以多次调用 flash() 收集多条消息,形成队列,但所有消息都只能显示一次

    • 在模板中调用函数get_flashed_messages()显示所有收集的消息

    因为可能队列中有多条消息,所以需要用 for 循环获取

        {% for message in get_flashed_messages() %} 
             {{ message }}
        {% endfor %}
    

    相关文章

      网友评论

        本文标题:Flask系列:表单

        本文链接:https://www.haomeiwen.com/subject/azqukttx.html