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驗證函式
驗證函式 | 說明 |
---|---|
驗證電子郵件地址 | |
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>