Flask教程4:Flask数据交互

文章目录

使用flask处理表单

传统的前端通用表单,需要前后端共同完成操作,前端需要使用form标签来定义表单,而后端则需要使用request.form来获取post请求中的表单数据:

python 复制代码
# 判断请求方式
if request.method == 'POST':
	# 获取表单中name为username的文本域提交的数据
	name = request.form.get('username')
	# 获取表单中name为password的文本域提交的数据
	password = request.form.get('password')
	return name+" "+password
  • 上述的方法既没有为表单提供保护措施,也不利于前后端分离的改进需求,固我们引入第三方扩展包:flask-wtfwtforms,来实现由后端单独完成的表单操作:
  • wtforms安装:pip install wtforms
  • flask-wtf安装:pip install Flask-WTFpip install flask-wtf
  • wtforms依照功能类别来说wtforms分别由以下几个类别:
    • Forms: 主要用于表单验证、字段定义、HTML生成,并把各种验证流程聚集在一起进行验证。
    • Fields: 包含各种类型的字段,主要负责渲染(生成HTML文本域)和数据转换。
    • Validator:主要用于验证用户输入的数据的合法性。比如Length验证器可以用于验证输入数据的长度。
    • Widgetshtml插件,允许使用者在字段中通过该字典自定义html小部件。
    • Meta:用于使用者自定义wtforms功能(配置),例如csrf功能开启。
    • Extensions:丰富的扩展库,可以与其他框架结合使用,例如django
  • Flask-WTF其实是对wtforms的简单集成,也能通过添加动态token令牌的方式,为所有Form表单提供免受CSRFCross-site request forgery------跨站请求伪造)攻击的技术支持

我们可以采用以下方法来启用CSRF保护:

  1. 定义配置文件,再将配置文件中的配置语句通过app.config.from_object(<配置对象>)或app.config.from_pyfile(<'配置文件名'>)导入到flask对象app中,这个配置对象可以是配置模块也可以是配置类:
python 复制代码
# config.py
CSRF_ENABLED = TRUE # 用于开启CSRF保护,但默认状态下都是开启的
SECRET_KEY = 'X1X2X3X4X5' # 用于生成动态令牌的秘钥
  • 其中SECRET_KEY用于建立加密令牌token,在我们编写程序时可以尽量定义的复杂一些;
python 复制代码
from flask import Flask
from flask_wtf.csrf import CSRFProtect # 导入CSRFProtect模块
import config # 导入配置文件

app = Flask(__name__)
# 导入配置模块中的配置
app.config.from_object(config)
# 为当前应用程序启用WTF_CSRF保护,并返回一个CSRFProtect对象
csrf = CSRFProtect(app)
  1. 直接通过键值对的方式新增配置,即app.config['<配置名称>']=值添加配置到flask对象app中:
python 复制代码
from flask import Flask
from flask_wtf.csrf import CSRFProtect # 导入CSRFProtect模块

app = Flask(__name__)
app.config['SECRET_KEY'] = 'ADJLAJDLA' # 用于生成动态令牌的秘钥
app.config['CSRF_ENABLED'] = True # 用于开启CSRF保护,但默认状态下都是开启的
# 为当前应用程序启用WTF_CSRF保护,并返回一个CSRFProtect对象
csrf = CSRFProtect(app)
  1. 除了使用上述方法来配置CSRF保护,我们还需要用到flask_wtfwtfroms来定义一个支持CSRF保护的后端表单,我们一般将其定义在一个类当中;
    该类需要继承基类:flask_wtf.FlaskFormflask_wtf.Form,二者完全相同,但Form即将被FlaskForm替换,推荐使用前者!
python 复制代码
from flask import Flask,render_template,request
from flask_wtf.csrf import CSRFProtect
# 导入表单基类FlaskForm
from flask_wtf import FlaskForm
# 导入FlaskForm父类的表单字段组件(字符串文本域,密码文本域,提交按钮)
from wtforms import StringField,PasswordField,SubmitField
# 导入FlaskForm父类的表单验证组件(数据不为空,数据是否相同,数据长度)
from wtforms.validators import DataRequired,EqualTo,Length

app = Flask(__name__)
# 配置加密匙,后端为了保护网站加入的验证机制
# 不加会报错:RuntimeError: A secret key is required to use CSRF.
app.config['SECRET_KEY'] = 'ADJLAJDLA'
# app.config['CSRF_ENABLED'] = True # 可以省略
csrf = CSRFProtect(app)

# 定义表单模型类,继承FlaskForm
class Register(FlaskForm):
    # 定义表单中的元素,类似于html的form中定义input标签下的内容
    # label 用于点击后跳转到某一个指定的field框
    # validators 用于接收一个验证操作列表
    # render_kw 用于给表单字段添加属性,各属性以键值对的形式设置
    user_name = StringField(label='用户名:',validators=[DataRequired(message=u'用户名不能为空'),Length(6,16,message='长度位于6~16之间')],render_kw={'placeholder':'输入用户名'})
    # message中存放判断为错误时要返回的信息,EqualTo中第一个参数是要比较的field组件
    password = PasswordField(label='密码:',validators=[DataRequired(message=u'密码不能为空'),EqualTo('password2',message=u'两次输入需相同'),Length(6,16,message='长度位于6~16之间')],render_kw={'placeholder':'输入密码'})
    password2 = PasswordField(label='再次输入密码:', validators=[DataRequired(message=u'密码不能为空'),Length(6,16,message='长度位于6~16之间')],render_kw={'placeholder':'再次输入密码'})
    submit = SubmitField(label='提交')

@app.route('/',methods=['GET','POST'])
def register():
    # 实例化表单对象
    form = Register()
    if request.method == 'GET':
    	# 表单对象发送至前端
        return render_template('register.html',form=form)
    elif request.method == 'POST':
        # form.validate_on_submit() 等价于:request.method=='post' and form.validate()
        # form.validate() 用于验证表单的每个字段(控件),都满足时返回值为True
        if form.validate_on_submit():
            username = form.user_name.data
            password = form.password.data
            password2 = form.password2.data
            return 'login success'
        else:
            # flask的form使用一个字典来储存各控件的errors列表
            # print(type(form.errors))
            # 输出密码字段导致validate_on_submit为false的错误原因(两种方式)
            print(form.errors['password'])
            print(form.password.errors)
            return render_template('register.html',form=form)

if __name__ == '__main__':
    app.run()
  • 前端中使用后端定义的表单,同样也需要使用jinja2模板引擎,在{``{ }}中调用我们传入的form对象,且表单开头需要使用form.csrf_tokenform.hidden_tag()语句添加动态令牌(用户不可见也不可编辑,用于验证)
html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Flask_WTF</title>
    <style type="text/css">
        .div1 {
            height:450px;
            width:400px;
            border:1px solid #8A8989;
            margin:0 auto;
            padding: 10px;
        }
        .input {
            display: block;
            width: 350px;
            height: 40px;
            margin: 10px auto;
            }
        .button{
            background: #2066C5;
            color: white;
            font-size: 18px;
            font-weight: bold;
            height: 50px;
            border-radius: 4px;
        }
</style>
</head>
<body>
<div class="div1">
    <form action="" method="post">
        <!-- 启用CSRF验证,将token令牌置于表单内 -->
        <!-- 不添加该字段,后端验证会一直为False -->
        {{ form.csrf_token }}
        {{ form.username.label }}
        <!-- 可以在变量后添加括号,并在括号内设置变量的属性 -->
        {{ form.username(class='input',id='name',size=16) }}
        <!-- 错误展示 -->
        {% for e in form.username.errors %}
            <span style="color: red">{{ e }}</span>
        {% endfor %}
        <br>
        {{ form.password.label }}
        {{ form.password(class='input',id='pwd',size=16) }}
        <!-- 错误展示 -->
        {% for e in form.password.errors %}
            <span style="color: red">{{ e }}</span>
        {% endfor %}
        <br>
        {{ form.password2.label }}
        {{ form.password2(class='input',id='pwd2',size=16) }}
        <!-- 错误展示 -->
        {% for e in form.password2.errors %}
            <span style="color: red">{{ e }}</span>
        {% endfor %}
        <br>
        {{ form.submit(class='button') }}
    </form>
</div>
</body>
</html>
flash闪现的使用

导入:from flask import flash

后端的使用:flash("message")message为消息内容;

前端通过遍历get_flashed_messages()获取flash消息内容;

示例代码(部分):

html 复制代码
# --------------视图函数------------------
@app.route('/login/', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template("flash.html")
    else:
        username = request.form.get('username')
        password = request.form.get('password')
        # user = User.query.filter(User.username == username, User.password == password).first()
        user = User.query.filter(User.username == username).first()
        if user and user.check_password(password):
            session['user_id'] = user.id
            session['user_name'] = user.username
            session.permanent = True
            return redirect(url_for("index"))
        else:
            flash('用户名或密码不正确,请检查!')
            return render_template('flash.html')

# ---------------前端使用-----------------
<div class="warning">
    {% for message in get_flashed_messages() %}
        <div class="alert alert-warning alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
            <strong>Warning!</strong> {{ message }}
        </div>
    {% endfor %}
</div>
Flask实现文件上传

文件上传指的是客户端将文件上传(post请求发送)到服务器,服务器端进行保存的操作;

这一部分涉及到的库和拓展知识过多,将解释放在代码注释中,直接上代码再做简单的流程分析;

对于单文件的上传,主要用到flask_wtf.file库下的上传字段类:FileField,以及检验组件:FileRequiredFileAllowed

后端应用程序文件"flask_file.py":

python 复制代码
from flask import Flask,render_template,redirect,url_for
from flask import send_from_directory,session,flash
from flask_wtf import FlaskForm
from wtforms import SubmitField
from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_wtf.csrf import CSRFProtect


app = Flask(__name__)
app.config['SECRET_KEY'] = 'XASXA#@216.WDFAW'
csrf = CSRFProtect(app)

# 自定义表单类
class UploadForm(FlaskForm):
    # flask_WTF中提供的上传字段,label仍是字段的标签
    # validators接收的验证列表中:
    # FileRequired()用于验证是否包含文件对象,可以设置参数message
    # FileAllowed()用于验证文件的类型(后缀名),参数一为允许的文件后缀名的列表,参数二为可选的message
    photo = FileField(label='Upload Image', validators=[FileRequired(), FileAllowed(['jpg','jpeg','png','gif'])])
    # 采用了wtforms提供的提交字段,flask_WTF中似乎不包含该字段
    submit = SubmitField()

import os
# 验证文件大小,可以通过修改配置,设置请求报文的最大长度
# 接收到超过长度的文件,服务器会中断连接,并返回413错误响应
app.config['MAX_CONTENT_LENGTH'] = 1*1024*1024
# root_path获取当前程序文件所处的目录,使用path.join将其与uploads连接形成上传路径
# 将形成的路径写入到flask程序的配置当中,上传到服务器的文件都会保存在当前目录下的uploads目录(需要手动预先创建)中
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')

# 导入 通用唯一识别码 库
import uuid
# 自定义文件重命名方法
def random_filename(filename):
    # os.path.splitext将文件路径与扩展名(文件类型标识)分开
    # 这里ext获取了文件扩展名用于拼接生成新的文件名
    ext = os.path.splitext(filename)[1]
    # 将生成的随机uuid与扩展名结合,形成新的文件名
    new_filename = uuid.uuid4().hex + ext
    # 返回新的文件名
    return new_filename

# 文件展示
@app.route('/uploaded-images')
def show_images():
    # 响应显示文件的模板
    return render_template('upload_file/uploaded.html')

# 获取文件路径
@app.route('/uploads/<path:filename>')
def get_file(filename):
    # send_from_directory可以生成对应文件的下载链接
    # 参数一是所有文件的存储目录(即uploads目录),参数二是该目录下要下载的文件名
    return send_from_directory(app.config['UPLOAD_PATH'], filename)

# 主程序
@app.route('/upload', methods=['GET', 'POST'])
def upload():
    # 实例化我们自定义的表单类UploadForm
    form = UploadForm()
    if form.validate_on_submit():
        # 使用表单对象form.photo的data属性即可获取到上传的文件
        f = form.photo.data
        # 处理文件名,这里采用自定义的random_filename方法来实现
        filename =random_filename(f.filename)
        # 服务器端使用save方法来保存接收到的文件
        # 读取配置中上传文件夹的路径,与文件名拼接后形成完整存储路径
        f.save(os.path.join(app.config['UPLOAD_PATH'], filename))
        # 使用flash通知用户文件上传成功
        flash('Upload success.')
        # 保存文件名到session,采用列表是为了后续拓展为多文件上传
        session['filenames'] = [filename]
        # 上传成功后显示图片,重定向到对应视图函数
        return redirect(url_for('show_images'))
    # 响应上传文件的模板,并把表单对象作为参数传递
    return render_template('upload_file/upload.html', form = form)
Session的使用

session是基于cookie实现的,也就是说二者之间是存在紧密联系的

  • sessioncookie都是由服务器生成的,都是用来存储特定的值(键值对应);
  • session是存储在服务器的,而cookie是会返回给客户端的。
    返回形式:置于响应信息头 ------ set-cookie
  • 客户端(浏览器)在发送请求的时候,会自动将存活、可用的cookie封装在请求头中和请求一起发送。
  • 发送形式:置于请求信息头 ------ Cookie
  • cookiesession都是有其生命周期的;
  • cookie的生命周期,一般来说,cookie的生命周期受到两个因素的影响
  • cookie自身的存活时间,是服务器生成cookie时设定的;
  • 客户端是否保留了cookie。客户端是否保留cookie,只对客户端自身有影响,对其它封包工具是没有影响的。
  • session的生命周期,一般来说,session的生命周期也是受到两个因素的影响:
    服务器对于session对象的保存的最大时间的设置。
  • 客户端进程是否关闭。客户端进程关闭,只对客户端自身有影响,对其它封包工具是没有影响的。
  • cookiesession都是有其作用域的。
为什么session比cookie安全?
  • cooke是存储在客户端的,是(用户)可见的,是(用户)可以改变的;
  • session是存储在服务器端的,是(用户)不可见的,是(用户)不可改变的。

当客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个sessionID,用来标识该session对象;

sessionID是一次会话的key,如果客户端的一次请求没有携带sessionID,那么服务器端就会为这次会话创建一个session用于存储内容,每个session都有唯一的sessionID

设置session
  • 服务器端接收到请求会自动创建session,我们可以通过session['name']='value'方法来对session内的值进行设置;
  • namekey,是我们设置的变量名称;value则是变量的值;
python 复制代码
from flask import Flask,session

# 设置session
@app.route('/')
def set_session():
    # 将 username=zhangsan 存储在session中
    # session的存储与获取都是字典方式
    session['username'] = 'zhangsan'
    return 'Session设置成功!'
获取session的值

在设置了session值后,我们有两种方法来获取我们设置的值,类似于字典的值的获取,推荐使用第二种:

result = session['name'] → 如果内容不存在会报错;

result = session.get('name') → 如果内容不存在会返回None

删除session的值或清空session所有内容

也是与字典中的操作类似:

①删除单条session值,可以采用session.pop('name')方法;

②清空整个session内容,则采用session.clear()方法;

python 复制代码
# 清除session
@app.route('/d')
def del_session():
    # 删除session中username的这条记录
    session.pop('username')
    # 清空session
    # session.clear()
    return 'Session被删除!'
  • 最终得到一个完整的服务器端session操作过程,代码如下:
python 复制代码
from flask import Flask,session
from datetime import timedelta

app = Flask(__name__)

import os
# 使用os库下的urandom随机生成秘钥
app.config['SECRET_KEY'] = os.urandom(24)
# 配置session有效期为7天
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)

# 设置session
@app.route('/')
def set_session():
    # 将 username=zhangsan 存储在session中
    # session的存储与获取都是字典方式
    session['username'] = 'zhangsan'
    # 将有效期设为启用
    session.permanent = True
    return 'Session设置成功!'

# 获取session
@app.route('/g')
def get_session():
    # 通过字典方式获取session中的username值
    username = session.get('username')
    return username or 'Session为空!'

# 清除session
@app.route('/d')
def del_session():
    # 删除session中username的这条记录
    session.pop('username')
    # 清空session
    # session.clear()
    return 'Session被删除!'

if __name__ == '__main__':
    app.run(debug=True)
相关推荐
颜淡慕潇6 分钟前
【K8S系列】kubectl describe pod显示ImagePullBackOff,如何进一步排查?
后端·云原生·容器·kubernetes
数据小爬虫@29 分钟前
利用Python爬虫获取淘宝店铺详情
开发语言·爬虫·python
Clarify1 小时前
docker部署go游戏服务器(进阶版)
后端
IT书架1 小时前
golang面试题
开发语言·后端·golang
编程修仙2 小时前
Collections工具类
linux·windows·python
芝麻团坚果2 小时前
对subprocess启动的子进程使用VSCode python debugger
linux·ide·python·subprocess·vscode debugger
机器之心2 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
EterNity_TiMe_2 小时前
【论文复现】神经网络的公式推导与代码实现
人工智能·python·深度学习·神经网络·数据分析·特征分析
Stara05112 小时前
Git推送+拉去+uwsgi+Nginx服务器部署项目
git·python·mysql·nginx·gitee·github·uwsgi
hence..3 小时前
Vscode写markdown快速插入python代码
ide·vscode·python