Flask插件wtforms、Flask文件上传和Echarts柱状图

1、wtforms

类比Django的Form组件
Form组件的主要应用是帮助咱们自动生成HTML代码和作一些表单数据的验证css

flask的wtforms用法跟Form组件大同小异
参考文章:http://www.javashuo.com/article/p-daydujxg-g.htmlhtml

下载安装
pip install wtforms前端

 

一、wtforms使用介绍

1. wtforms支持的字段和验证函数
原文:https://blog.csdn.net/wuqing942274053/article/details/72510920html5

WTForms支持的HTML标准字段正则表达式

字段类型 说明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为datetime.date格式
DateTimeField 文本字段,值为datetime.datetime格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为True和False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文件上传字段
SubmitField 表单提交按钮
FormField 把表单做为字段嵌入另外一个表单
FieldList 一组指定类型的字段

WTForms验证函数数据库

验证函数 说明
Email 验证电子邮件地址
EqualTo 比较两个字段的值,经常使用于要求输入两次密码进行确认的状况
IPAddress 验证IPv4网络地址
Length 验证输入字符串的长度
NumberRange 验证输入的值在数字范围内
Optional 无输入值时跳过其余验证函数
Required 确保字段中有数据
Regexp 使用正则表达式验证输入值
URL 验证URL
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选列表中

2. wtforms类的属性和方法
属性:
data
  包含每一个字段的数据的字典npm


errors
  包含每一个字段的错误列表的DECT。若是没有验证表单,或者没有错误,则为空。flask

 

meta
  这是一个包含各类配置选项以及自定义表单行为的能力的对象。有关可使用类元选项自定义的内容的更多信息,请参见类元文档。bootstrap


方法:
validate():经过在每一个字段上调用Value来验证表单,将任何额外的Form.Value_<field name>验证器传递给字段验证器。
populate_obj(obj):使用表单字段中的数据填充传递的obj的属性。
__iter__():按建立顺序迭表明单字段。
__contains__(name):若是指定的字段是此表单的成员,则返回True。后端


例如:

form_obj = wtform(request.form)
if form_obj.validate():
    # 包含每一个字段的数据的字典
    print(form_obj.data)
    # 这是一个包含各类配置选项以及自定义表单行为的能力的对象
    print(form_obj.meta)
    return "注册成功"
# 包含每一个字段的错误列表的DECT。若是没有验证表单,或者没有错误,则为空。
print(form_obj.errors)

 

字段的基类:

label 字段的标签
validators 验证器 -验证的序列时要调用验证被调用
default 若是未提供表单或对象输入,则分配给字段的默认值
widget 若是提供,则覆盖用于呈现字段的窗口小部件
render_kw 设置字段的额外参数,提供一个字典
   
filters 按进程在输入数据上运行的一系列过滤器。
description 字段的描述,一般用于帮助文本
id 用于字段的id。表单设置了合理的默认值,您不须要手动设置。
   
_form 包含此字段的表单。在施工期间,它由表格自己传递。你永远不该该本身传递这个值。
_name 此字段的名称,由封闭表单在构造期间传递。你永远不该该本身传递这个值
_prefix 前缀为此字段的表单名称的前缀,在构造期间由封闭表单传递。
_translations 提供消息翻译的翻译对象。一般在施工期间经过封闭的形式经过。
_meta 若是提供,这是表单中的'meta'实例。你一般不会本身经过

 

二、基本的使用

1. MyForms.py 定义Form表单的类

from wtforms import Form, widgets
from wtforms.fields import simple, core, html5

# 使用wtforms的类必需要继承它的Form类
# simple:普通字段   core:核心字段   html5:H5新增的字段


class RegisterForm(Form):
    username = simple.StringField(
        label='用户名',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        # widget插件,能够把这个字段设置成其余type类型
        widget=widgets.TextArea()
    )
    pwd = simple.PasswordField(
        label='密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'}
    )
    re_pwd = simple.PasswordField(
        label='确认密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},

    )

 

 

2. 视图

from flask import Blueprint, render_template
from FlaskPlug.utils.MyForms import RegisterForm


userBlue = Blueprint("userBlue", __name__)


@userBlue.route('/register')
def register():
    # 实例化form
    form_obj = RegisterForm()
    return render_template('register.html', form_obj=form_obj)

 


3. HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <h2 style="margin-top: 50px;margin-bottom: 30px;text-align: center">欢迎注册</h2>
            <form action="" method="POST" novalidate class="form-horizontal">
                {% for field in form_obj %}
                <div class="form-group">
                    {{ field.label }}
                    {{ field }}
                </div>
                {% endfor %}
                <button type="submit"  class="btn btn-success">提交</button>
            </form>
        </div>
    </div>
</div>

</body>
</html>

 

三、验证

3-一、基本验证

步骤
  1. 在Form类中增长验证信息
  2. 在视图中作数据的校验 而且页面展现错误信息


1. MyForms.py 定义Form表单的类

from wtforms import Form, widgets, validators
from wtforms.fields import simple, core, html5


class RegisterForm(Form):
    username = simple.StringField(
        label='用户名',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        # 能够定义多个校验规则
        validators=[
            # DataRequired字段必填
            validators.DataRequired(message='用户名不能为空'),
            # length字段的长度限制
            # message:用户填写错误时的错误信息
            validators.length(min=2, max=8, message='长度必须在2-8之间')
        ],
        # widget=widgets.TextArea()
    )
    pwd = simple.PasswordField(
        label='密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间')
        ],
    )
    re_pwd = simple.PasswordField(
        label='确认密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间'),
            # EqualTo:校验两个字段的值是否相等
            validators.EqualTo('pwd', message='两次密码不一致')
        ],
    )
    phone = simple.StringField(
        label='手机号码',
        validators=[
            validators.Regexp(regex="^1[3-9][0-9]{9}$",message='手机格式不正确')
        ]

    )

 

 

2. 视图

from flask import Blueprint, render_template, request
from FlaskPlug.utils.MyForms import RegisterForm
from FlaskPlug.models import User


userBlue = Blueprint("userBlue", __name__)


@userBlue.route('/register', methods=['GET', 'POST'])
def register():
    # 实例化form
    form_obj = RegisterForm()
    if request.method == "POST":
        # 把用户提交上来的数据放入Form表单中实例化
        form_obj = RegisterForm(request.form)
        # validate方法会去校验用户提交上来的数据
        if form_obj.validate():
            # 验证经过能够写入数据库,这里演示,不写入
            # 验证经过的数据都保存在data这个大字典里面
            # username = form_obj.data.get('username')
            # password = form_obj.data.get('pwd')
            # user_obj = User(username=username, password=password)
            # db.session.add(user_obj)
            # db.session.commit()
            # db.session.close()
            return "注册成功"
    return render_template('register.html', form_obj=form_obj)

 


3. HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <h2 style="margin-top: 50px;margin-bottom: 30px;text-align: center">欢迎注册</h2>
            <form action="" method="POST" novalidate class="form-horizontal">
                {% for field in form_obj %}
                <div class="form-group">
                    {{ field.label }}
                    {{ field }} <span style="color: red">{{ field.errors[0] }}</span>
                </div>
                {% endfor %}
                <button type="submit"  class="btn btn-success">提交</button>
            </form>
        </div>
    </div>
</div>

</body>
</html>

 

3-二、自定义校验规则
from wtforms import Form, validators, ValidationError
from wtforms.fields import simple


def check_username(form, field):
    if len(field.data) < 2:
        raise ValidationError('错了,嘿嘿')


class TestForm(Form):
    username = simple.StringField(
        label='用户名',
        validators=[check_username, ]
    )

 

3-三、利用钩子函数进行校验
局部钩子函数: validate_字段名,接收两个参数(form, field),后端调用validate()校验函数的时候触发
form: wtforms类的实例
field: 字段对象,能够经过field.data获取前端传过来的该字段的数据,不是字典

全局钩子函数:validate, 接收self参数,self.data表明前端表单传过来的全部数据,是一个字典


from wtforms import Form, validators, ValidationError
from wtforms.fields import simple


class TestForm(Form):
    username = simple.StringField(
        label='用户名',
    )
    password = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间')
        ]
    )

    # 局部钩子函数
    def validate_username(form, field):
        print(field.data)  # 张三
        if len(field.data) < 2:
            raise ValidationError('用户名至少两位字符')

    # 全局钩子函数
    def validate(self):
        print(self.data)  # {'username': '张三', 'password': '12345678'}
        for key, value in self.data.items():
            print(value)

 

四、拓展字段core/html5

from wtforms import Form, widgets, validators
from wtforms.fields import simple, core, html5


class RegisterForm(Form):
    username = simple.StringField(
        label='用户名',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        # 能够定义多个校验规则
        validators=[
            # DataRequired字段必填
            validators.DataRequired(message='用户名不能为空'),
            # length字段的长度限制
            # message:用户填写错误时的错误信息
            validators.length(min=2, max=8, message='长度必须在2-8之间')
        ],
        # widget=widgets.TextArea()
    )
    pwd = simple.PasswordField(
        label='密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间')
        ],
    )
    re_pwd = simple.PasswordField(
        label='确认密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间'),
            # EqualTo:校验两个字段的值是否相等
            validators.EqualTo('pwd', message='两次密码不一致')
        ],
    )
    phone = simple.StringField(
        label='手机号码',
        validators=[
            validators.Regexp(regex="^1[3-9][0-9]{9}$",message='手机格式不正确')
        ]

    )
    # H5新增的标签email
    email = html5.EmailField(
        label='邮箱',
        validators=[
            validators.DataRequired(message='邮箱不能为空.'),
        ],
        widget=widgets.TextInput(input_type='email'),
    )
    # 核心字段core,单选框
    gender = core.RadioField(
        label='性别',
        choices=((1, ''), (2, '')),
        # 前端传过来的数据是字符串类型,coerce能够把穿过来的数据转换类型
        # 由于数据库存的1是int类型,前端选择"男",传过来的是字符串1
        coerce=int,
        default=1
    )
    # 单选下拉菜单
    city = core.SelectField(
        label='城市',
        choices=(('sz', '深圳'), ('gz', '广州'), )
    )
    # 多选下拉菜单
    hobby = core.SelectMultipleField(
        label='爱好',
        choices=(
            (1, '美女'),
            (2, 'xiong'),
        ),
    )
    favor = core.SelectMultipleField(
        label='喜爱',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        # 把多选下拉菜单设置成列表
        widget=widgets.ListWidget(prefix_label=False),
        option_widget=widgets.CheckboxInput(),
        coerce=int,
        default=[1, 2]
    )

    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        # 从数据库获取数据 作到实时更新
        # self.favor.choices = ORM操做
        # 这里演示一下更改
        self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))

 

五、实现csrf_token

1.Form类

from flask import request
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5


# 自定义CSRF类,重写CSRF的三个方法
class MyCSRF(CSRF):
    """
    Generate a CSRF token based on the user's IP. I am probably not very
    secure, so don't use me.
    """

    def setup_form(self, form):
        # 获取class Meta里设置的一些值
        self.csrf_context = form.meta.csrf_context()
        self.csrf_secret = form.meta.csrf_secret
        # 调用父类的setup_form方法
        return super(MyCSRF, self).setup_form(form)

    def generate_csrf_token(self, csrf_token):
        # 使用md5加密,生成惟一token
        gid = self.csrf_secret + self.csrf_context
        token = md5(gid.encode('utf-8')).hexdigest()
        return token

    def validate_csrf_token(self, form, field):
        # 校验token
        print(field.data, field.current_token)
        if field.data != field.current_token:
            raise ValueError('Invalid CSRF')


class RegisterForm(Form):
    username = simple.StringField(
        label='用户名',
        render_kw={'class': 'form-control'},
        widget=widgets.TextInput(),
        validators=[
            validators.DataRequired(message='用户名不能为空'),
            validators.length(min=2, max=8, message='长度必须在2-8之间')
        ]
    )
    password = simple.PasswordField(
        label='密码',
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间')
        ]
    )
    re_pwd = simple.PasswordField(
        label='确认密码',
        # 给这个字段添加样式
        render_kw={'class': 'form-control'},
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.length(min=8, max=16, message='长度必须在8-16之间'),
            validators.EqualTo('password', message='两次密码不一致')
        ]
    )
    phone = simple.StringField(
        label='手机号码',
        validators=[
            validators.Regexp(regex="^1[3-9][0-9]{9}$", message='手机格式不正确')
        ]

    )

    class Meta:
        # -- CSRF
        # 是否自动生成CSRF标签
        csrf = True
        # 生成CSRF标签name
        csrf_field_name = 'csrf_token'

        # 自动生成标签的值,加密用的csrf_secret
        csrf_secret = '一库'
        # 自动生成标签的值,加密用的csrf_context
        csrf_context = lambda x: request.url
        # 生成和比较csrf标签
        csrf_class = MyCSRF

        # -- i18n
        # 是否支持本地化
        # locales = False
        locales = ('zh', 'en')
        # 是否对本地化进行缓存
        cache_translations = True
        # 保存本地化缓存信息的字段
        translations_cache = {}

 

2.models

class User(db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), nullable=False)
    password = db.Column(db.String(32), nullable=False)
    phone = db.Column(db.String(32))

 

3.视图

from flask import request, views, session, render_template
from flask_demo.home.models.home_model import *
from flask_demo import db
from flask_demo.home.forms.home_forms import RegisterForm


class RegisterView(views.MethodView):
    def get(self):
        form_obj = RegisterForm()
        return render_template('register.html', form_obj=form_obj)

    def post(self):
        form_obj = RegisterForm(request.form)
        if form_obj.validate():
            user_dict = {}
            user_dict['username'] = form_obj.data.get('username')
            user_dict['password'] = form_obj.data.get('password')
            user_dict['phone'] = form_obj.data.get('phone')
            user_obj = User(**user_dict)
            db.session.add(user_obj)
            db.session.commit()
            db.session.close()
            return "注册成功"
        return render_template('register.html', form_obj=form_obj)

 

4.HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <h2 style="margin-top: 50px;margin-bottom: 30px;text-align: center">欢迎注册</h2>
            <form action="" method="POST" novalidate class="form-horizontal"> {{ form_obj.csrf_token }} <div class="form-group">
                    {{ form_obj.username.label }}
                    {{ form_obj.username }}
                    <span style="color: red">{{ form_obj.username.errors[0] }}</span>
                </div>
                <div class="form-group">
                    {{ form_obj.password.label }}
                    {{ form_obj.password }}
                    <span style="color: red">{{ form_obj.password.errors[0] }}</span>
                </div>
                <div class="form-group">
                    {{ form_obj.re_pwd.label }}
                    {{ form_obj.re_pwd }}
                    <span style="color: red">{{ form_obj.re_pwd.errors[0] }}</span>
                </div>
                <div class="form-group">
                    {{ form_obj.phone.label }}
                    {{ form_obj.phone }}
                    <span style="color: red">{{ form_obj.phone.errors[0] }}</span>
                </div>
                <button type="submit" class="btn btn-success">提交</button>
            </form>
        </div>
    </div>
</div>
</body>
</html>

 

2、基于Flask的文件上传Demo

"""
文件上传完后,进行代码的统计
app.config.root_path: 项目的根路径
os.walk:
    遍历你给的路径下的全部文件(会递归遍历)
    每次循环的根文件夹的路径,文件夹的名字组成的列表,和文件组成的列表
    dirpath, dirnames, filenames
zipfile: 压缩解压文件的模块
shutil: 也是压缩解压文件的模块,还能移动啥的
"""

from flask import Blueprint, request, render_template
from flask import current_app as app
import shutil
from uploadCode.models import CodeRecord
from uploadCode import db
import os
import time


uploadBlue = Blueprint('uploadBlue', __name__)


# zip包上传
@uploadBlue.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == "GET":
        return render_template("upload.html", error="")
    # 先获取前端传过来的文件
    file = request.files.get("zip_file")
    # 判断是不是zip包
    zip_file_type = file.filename.rsplit(".", 1)
    if zip_file_type[-1] != "zip":
        return render_template("upload.html", error="文件必须是zip包")
    # 解压路径
    upload_path = os.path.join(app.config.root_path, "files", zip_file_type[0]+str(time.time()))
    print(upload_path)
    # 解压前端传过来的文件file到upload_path这个路径
    shutil._unpack_zipfile(file, upload_path)
    # 遍历保存的文件夹获得全部.py文件
    file_list = []
    for (dirpath, dirnames, filenames) in os.walk(upload_path):
        for filename in filenames:
            file_type = filename.rsplit(".", 1)
            if file_type[-1] != "py":
                continue
            file_path = os.path.join(dirpath, filename)
            file_list.append(file_path)
    # 打开每一个文件读取行数
    sum_num = 0
    for path in file_list:
        with open(path, mode="rb") as f:
            for line in f:
                if line.strip().startswith(b"#"):
                    continue
                sum_num += 1
    # 获得总行数去保存数据库
    return str(sum_num)
1. upload.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
    请上传你的代码: <input type="file" name="zip_file">
    <button type="submit">提交</button>
    {{error}}
</form>

</body>
</html>
2. HTML代码

 

3、柱状图

参考文档Echarts:http://echarts.baidu.com/

一、使用Echarts步骤

1. 下载它须要的依赖包
2. 点击某个Demo-->Download
3. 参考着Demo去实现你的需求

 

二、注意

从后端传数据到前端的时候,由于展现的柱状图须要后端的数据,
而若是把数据直接在JS中使用,会出现一些问题,
所以,咱们可使用一个标签,给这个标签设置属性从而获取从后端传过来的数据,
而后在JS中获取这个标签的属性值,就能够拿到后端的数据了,
可是从属性获取的数据已经被转化成字符串了,咱们这时可使用eval()方法,
把数据类型从新转换回来。

 

三、Demo

from flask import Blueprint, request, render_template
from uploadCode.models import CodeRecord
from uploadCode import db


# 柱状图
@uploadBlue.route("/")
def index():
    # 展现用户提交代码柱状图
    queryset = db.session.query(CodeRecord).all()
    date_list = []
    num_list = []
    for obj in queryset:
        date_list.append(str(obj.upload_date))
        num_list.append(obj.code_nums)
    return render_template("index.html", date_list=date_list, num_list=num_list)
1. Demo.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/echarts.common.min.js"></script>
</head>
<body>
    <div id="container" style="height: 400px"></div>
    <!--用一个标签获取从后端传过来的数据-->
    <div id="info" date_list="{{date_list}}" num_list="{{num_list}}"></div>
    <script>
        var dom = document.getElementById("container");
        var myChart = echarts.init(dom);
        var app = {};
        let infoEle = document.getElementById("info");
        let date_list = infoEle.getAttribute("date_list");
        let num_list = infoEle.getAttribute("num_list");
        option = null;
        app.title = '坐标轴刻度与标签对齐';

        option = {
            color: ['#3398DB'],
            tooltip : {
                trigger: 'axis',
                axisPointer : {            // 坐标轴指示器,坐标轴触发有效
                    type : 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
                }
            },
            grid: {
                left: '3%',
                right: '4%',
                bottom: '3%',
                containLabel: true
            },
            xAxis : [
                {
                    type : 'category',
                    data : eval(date_list),
                    axisTick: {
                        alignWithLabel: true
                    }
                }
            ],
            yAxis : [
                {
                    type : 'value'
                }
            ],
            series : [
                {
                    name:'直接访问',
                    type:'bar',
                    barWidth: '60%',
                    data: eval(num_list)  // 从新计算一下这个字符串,转成本来的数据类型
                }
            ]
        };
        ;
        if (option && typeof option === "object") {
            myChart.setOption(option, true);
        }
    </script>
</body>
</html>
2. HTML代码(根据官方Demo修改一下)
相关文章
相关标签/搜索