flask的WTForms

flaskwtf文档

https://flask-wtf.readthedocs.io/en/stable/#

wtform文档

https://wtforms.readthedocs.io/en/2.3.x/

Flask-WTF集成了WTForm功能,是带有csrf令牌的安全表单且具有全局csrf保护的功能、有文件上传(Flask-Uploads)及图形验证码功能。

安装,会自动安装WTForms:pip install Flask-WTF

定义form.py 添加

import re
from flask import session
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, EqualTo, ValidationError

# 定义表单项匹配到前端的表单中,继承FlaskForm
class UserForm(FlaskForm):
    # field类型 'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList', 'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField', 'SelectMultipleField', 'StringField', 'TimeField',
    # StringField表示前端的文本框 type=text name=name DataRequired表示必填 errors为元组
    # 限制格式 DataRequired EqualTo IPAddress Length NumberRange URL Email Regexp
    name = StringField(label='用户名', validators=[DataRequired(), Length(min=6, max=12, message='用户名长度必须在6~12位之间')])

    # 密码框
    password = PasswordField(label='密  码',validators=[DataRequired(), Length(min=6, max=12, message='密码长度必须在6~12位之间')])

    # 确认密码 EqualTo(name)
    confirm_pwd = PasswordField(label='确认密码', validators=[ DataRequired( ), Length(min=6, max=12, message='密码长度必须在6~12位之间'), EqualTo('password', '两次密码不一致') ])

    # 手机号 ctrl点击进去,点击下箭头显示其所有子类
    phone = StringField(label='手机号码', validators=[ DataRequired( ), Length(min=11, max=11, message='手机号码必须11位长度') ])

    # 头像上传 from flask_wtf.file import FileField, FileRequired, FileAllowed  FileAllowed允许上传的扩展名
    icon = FileField(label='用户头像', validators=[ FileRequired( ), FileAllowed([ 'jpg', 'png', 'gif' ], message='必须是图片文件格式') ])

    # 图形验证码 RecaptchaField 使用需要google 所以直接使用StringField
    recaptcha = StringField(label='验证码')

    # If the form defines a ``validate_<fieldname>`` method, it is appended as an extra validator for the field's ``validate``.
    # 自定义name的格式 data 就是提交过来的表单项 data.data以及 self.name.data 都为取值 2zxcgyh
    def validate_name(self, data):
        # self.name <input id="name" name="name" required type="text" value="2zxcgyh">
        if self.name.data[ 0 ].isdigit( ):
            # 抛出错误 加入元组
            raise ValidationError('用户名不能以数字开头')

    # 自定义phone的格式
    def validate_phone(self, data):
        phone = data.data
        # 正则匹配 ^1[35678]\d{9}$  开头1 第二位为3/5/6/7/8 后面9位数字
        if not re.search(r'^1[35678]\d{9}$', phone):
            raise ValidationError('手机号码格式错误')

    def validate_recaptcha(self, data):
        input_code = data.data
        code = session.get('valid')
        # 全改为小写
        if input_code.lower() != code.lower( ):
            raise ValidationError('验证码错误')

Field类型:

StringField PasswordField IntegerField DecimalField FloatField
BooleanField RadioField SelectField DatetimeField

所有的验证:

DataRequired EqualTo IPAddress Length NumberRange URL Email Regexp

在app.py中连接表单对象和html

import os
from io import BytesIO
from flask import Flask, render_template, make_response, session
from flask_bootstrap import Bootstrap
from werkzeug.utils import secure_filename
from form import UserForm
from flask_wtf.csrf import CSRFProtect
from util import generate_image

# 路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # 项目路径
STATIC_DIR = os.path.join(BASE_DIR, 'static') # 静态文件路径
UPLOAD_DIR = os.path.join(STATIC_DIR, 'upload') # 上传文件路径


app = Flask(__name__)
# 必须配置密钥 flaskform的csrf需要
app.config['SECRET_KEY'] = 'jfkdk73434kjfk3'
app.config['ENV'] = 'development'
# 验证码的公钥
app.config['RECAPTCHA_PUBLIC_KEY'] = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J'
# 验证码的私钥
app.config['RECAPTCHA_PRIVATE_KEY	'] = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
app.config['RECAPTCHA_PARAMETERS'] = {'hl': 'zh', 'render': 'explicit'}
# 验证码主题为黑色
app.config['RECAPTCHA_DATA_ATTRS'] = {'theme': 'dark'}
# 初始化bootstrap
bootstrap = Bootstrap(app=app)
csrf = CSRFProtect(app)

@app.route('/', methods=["GET","POST"])
def hello_world():
    # 创建表单类对象
    uform = UserForm()
    if uform.validate_on_submit( ): # 主要通过其进行校验 校验提交方式以及form的验证
        name = uform.name.data
        password = uform.password.data
        phone = uform.phone.data
        print('name:',name,' password:',password,' phone:',phone)
        icon = uform.icon.data # FileStorage类型
        print(type(icon))
        # 构建安全文件名 存入
        filename = secure_filename(icon.filename)
        icon.save(os.path.join(UPLOAD_DIR, filename))
        return '提交成功!'
    return render_template('user.html', uform = uform)

@app.route('/image')
def get_image():
    im, code = generate_image(4)
    # 将image对象转成二进制
    buffer = BytesIO()
    # 将图片保存到buffer format=JPEG
    im.save(buffer, "JPEG")
    # getvalue:Retrieve the entire contents of the BytesIO object
    buf_bytes = buffer.getvalue()
    # 保存到session中
    session['valid'] = code
    # 构建response对象 存二进制
    response = make_response(buf_bytes)
    response.headers['Content-Type'] = 'image/jpg'
    return response

# form与bootstrap结合使用
@app.route('/user1',methods=['GET', 'POST'])
def boot_form_user():
    uform = UserForm()
    if uform.validate_on_submit( ):  # 主要通过其进行校验 校验提交方式以及form的验证
        name = uform.name.data
        password = uform.password.data
        phone = uform.phone.data
        print('name:', name, ' password:', password, ' phone:', phone)
        icon = uform.icon.data  # FileStorage类型
        print(type(icon))
        # 构建安全文件名 存入
        filename = secure_filename(icon.filename)
        icon.save(os.path.join(UPLOAD_DIR, filename))
        return '提交成功!'
    return render_template('user1.html',uform=uform)

if __name__ == '__main__':
    app.run(debug=True)

在templates中建立网页user.html,form标签和提交按钮需要自己写

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户页面</title>
    {# jquery cdn加速 #}
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <style>
        p span {
            font-size: 14px;
            color: red;
        }
    </style>
</head>
<body>

<form action="{{ url_for('hello_world') }}" method="post" enctype="multipart/form-data">
    {# 全局使用csrf保护 #}
    {# uform.name.errors.0 表示取出元组里的第一个 #}
    {{ uform.csrf_token }}
    <p>{{ uform.name.label }}:{{ uform.name }}
        <span>{% if uform.name.errors %}{{ uform.name.errors.0 }} {% endif %}</span>
    </p>
    <p>{{ uform.password.label }}:{{ uform.password }}
        <span>{% if  uform.password.errors %}{{ uform.password.errors.0 }}{% endif %}</span>
    </p>
    <p>{{ uform.confirm_pwd.label }}:{{ uform.confirm_pwd }}
        <span>{% if  uform.confirm_pwd.errors %}{{ uform.confirm_pwd.errors.0 }}{% endif %}</span>
    </p>
    <p>{{ uform.phone.label }}:{{ uform.phone }}
        <span>{% if  uform.phone.errors %}{{ uform.phone.errors.0 }}{% endif %}</span>
    </p>
    {# 头像上传 添加 enctype="multipart/form-data" #}
    <p>{{ uform.icon.label }}:{{ uform.icon }}
        <span>{% if  uform.icon.errors %}{{ uform.icon.errors.0 }}{% endif %}</span>
    </p>
    {# 图形验证码 #}
    <p>{{ uform.recaptcha.label }}:{{ uform.recaptcha }}
        <span>
        {% if uform.recaptcha.errors %}{{ uform.recaptcha.errors.0 }}{% endif %}
        </span>
    </p>
    <p> <img src="{{ url_for('get_image') }}" id="img"></p>
    <p><input type="submit" value="提交"></p>
</form>
    <script>
        // 单击图片重新加载图片 ran= 向服务器表示两次请求不同 否则服务器会返回与之前相同的结果
        $('#img').click(function () {
            $(this).attr('src', "{{ url_for('get_image') }}?ran=" + Math.random());
        })
    </script>
</body>
</html>

CSRF中文名称
跨站请求伪造,缩写为:CSRF/XSRF。
可以这么理解CSRF攻击:
攻击者盗用了受害者的身份,以受害者的名义发送恶意请求。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
登录受信任网站A,并在本地生成Cookie。在不登出A的情况下,访问危险网站B。此时B就可以携带在A处产生的cookie访问A
CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。
{{ uform.csrf_token }}表示表单随机产生一个值,每次请求都不同。浏览器验证cookie和随机值,都正确才通过。

文件上传
上传使用的是FileField,如果需要指定上传文件的类型需要使用:FileAllowed

icon = FileField(label='用户头像', validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif'], message='必须是图片文件格式')])

必须在form上面添加:enctype=“multipart/form-data”
保存文件

icon = uform.icon.data   -----》icon是FileStorage类型
filename = secure_filename(icon.filename)
icon.save(os.path.join(UPLOAD_DIR, filename))

图形验证码

pip install pillow

util.py 生成图形验证码

import random
# pillow模块
from PIL import Image, ImageFont, ImageDraw, ImageFilter

# 随机颜色
def get_random_color():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

# 生成一张图片
def generate_image(length):
    s = 'qwerQWERTYUIOtyuiopas123456dfghjklPASDFGHJKLZXCVBNMxcvbnm7890'
    size = (130, 35)
    # 创建画布背景 mode, size, color
    im = Image.new('RGB', size, color=get_random_color())
    # im.show()
    # 创建字体 font, size=1, index, encoding, layout_engine windows C:\Windows\Fonts
    font = ImageFont.truetype('font/GILLUBCD.TTF', size=25)
    # 创建ImageDraw对象
    draw = ImageDraw.Draw(im)
    # 绘制验证码
    code = ''
    for i in range(length):
        # 选择其中一个字母
        c = random.choice(s)
        # 拼接
        code += c
        # 位置 内容 颜色 字体
        draw.text((9+random.randint(4,7)+25*i, 1),
                  text=c,
                  fill=get_random_color(),
                  font=font)
    # 绘制干扰线
    for i in range(5):
        x1 = random.randint(0, 130)
        y1 = random.randint(0, 50 / 2)
        x2 = random.randint(0, 130)
        y2 = random.randint(50 / 2, 50)
        # xy, fill
        draw.line(((x1, y1), (x2, y2)), fill=get_random_color())

    # im.show( )
    # 加滤镜 增强EDGE_ENHANCE 模糊BLUR
    im = im.filter(ImageFilter.EDGE_ENHANCE)
    # im.show( )
    return im, code

if __name__ == '__main__':
    generate_image(4)

bootstrap与wtfrom同时使用

{% extends 'bootstrap/base.html' %}
{# 为了和form匹配使用,需要导入 #}
{% import 'bootstrap/wtf.html'  as wtf %}
{% block styles %}
    {{ super() }}
{% endblock %}

{% block content %}
    <form action="{{ url_for('hello_world') }}" method="post" enctype="multipart/form-data">
        {# 产生CSRF token #}
        {{ uform.hidden_tag() }}
        {{ wtf.form_errors(uform, hiddens="only") }}
        {{ wtf.form_field(uform.name,form_type="basic", horizontal_columns=('lg', 2, 10)) }}
        {{ wtf.form_field(uform.password) }}
        {{ wtf.form_field(uform.confirm_pwd) }}
        {{ wtf.form_field(uform.phone) }}
        {{ wtf.form_field(uform.icon) }}
        {{ wtf.form_field(uform.recaptcha) }}
        <p> <img src="{{ url_for('get_image') }}" id="img"></p>
        <p><input type="submit" value="提交"></p>
    </form>
{% endblock %}

{% block scripts %}
    {{ super() }}
    <script>
        // 单击图片重新加载图片 ran= 向服务器表示两次请求不同 否则服务器会返回与之前相同的结果
        $('#img').click(function () {
            $(this).attr('src', "{{ url_for('get_image') }}?ran=" + Math.random());
        })
    </script>
{% endblock %}

项目上传https://github.com/zxy1013/flask_form

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值