1. 程式人生 > >Flask外掛wtforms、Flask檔案上傳和Echarts柱狀圖

Flask外掛wtforms、Flask檔案上傳和Echarts柱狀圖

一、wtforms
類比Django的Form元件
Form元件的主要應用是幫助我們自動生成HTML程式碼和做一些表單資料的驗證

flask的wtforms用法跟Form元件大同小異
參考文章:https://www.cnblogs.com/Zzbj/p/9966753.html

下載安裝
pip install wtforms

 

1、wtforms使用介紹

1. wtforms支援的欄位和驗證函式
原文:https://blog.csdn.net/wuqing942274053/article/details/72510920

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
包含每個欄位的資料的字典
errors
包含每個欄位的錯誤列表的DECT。如果沒有驗證表單,或者沒有錯誤,則為空。

meta
這是一個包含各種配置選項以及自定義表單行為的能力的物件。有關可以使用類元選項自定義的內容的更多資訊,請參見類元文件。


方法:
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(dict) - 如果提供,則提供一個字典,該字典提供將在呈現時提供給視窗小部件的預設關鍵字。

filters - 按程序在輸入資料上執行的一系列過濾器。
description - 欄位的描述,通常用於幫助文字。
id - 用於欄位的id。表單設定了合理的預設值,您不需要手動設定。

_form - 包含此欄位的表單。在施工期間,它由表格本身傳遞。你永遠不應該自己傳遞這個值。
_name - 此欄位的名稱,由封閉表單在構造期間傳遞。你永遠不應該自己傳遞這個值。
_prefix - 字首為此欄位的表單名稱的字首,在構造期間由封閉表單傳遞。
_translations - 提供訊息翻譯的翻譯物件。通常在施工期間通過封閉的形式通過。有關訊息轉換的資訊,請參閱 I18n文件。
_meta - 如果提供,這是表單中的'meta'例項。你通常不會自己通過。

 

2、基本的使用
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/[email protected]/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、驗證
3-1、基本驗證
步驟
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/[email protected]/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-2、自定義校驗規則
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-3、利用鉤子函式進行校驗
鉤子函式: validate_欄位名,接收兩個引數(form, field),後端呼叫validate時觸發

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


class TestForm(Form):
username = simple.StringField(
label='使用者名稱',
)

def validate_username(form, field):
if len(field.data) < 2:
raise ValidationError('使用者名稱至少兩位字元')

 


4、拓展欄位core/html5
# MyForms.py
# 使用wtforms的類必須要繼承它的Form類
from wtforms import Form, widgets, validators
# simple:普通欄位 core:核心欄位 html5:H5新增的欄位
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, '羽毛球'))

 

二、基於Flask的檔案上傳Demo
1. upload.py

"""
檔案上傳完後,進行程式碼的統計
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)


2. HTML程式碼
<!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>

 

三、柱狀圖
參考文件Echarts:http://echarts.baidu.com/

1、使用Echarts步驟:
1. 下載它需要的依賴包
2. 點選某個Demo-->Download
3. 參考著Demo去實現你的需求


2、注意
從後端傳資料到前端的時候,因為展示的柱狀圖需要後端的資料,
而如果把資料直接在JS中使用,會出現一些問題,
因此,我們可以使用一個標籤,給這個標籤設定屬性從而獲取從後端傳過來的資料,
然後在JS中獲取這個標籤的屬性值,就可以拿到後端的資料了

 

3、Demo
1. Demo.py
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)


2. HTML程式碼(根據官方Demo修改一下)
<!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>