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修改一下)
相關文章
相關標籤/搜索