一章節我們通過在html中直接編寫表單的方式進行數據傳遞,并且在視圖中對前端傳遞的數據進行了簡單的認證,但是如果把驗證數據的代碼與邏輯混合在一起,將使得視圖的代碼不夠清晰,并且難以維護,稍加疏忽就會產生驗證漏洞,如果細心的同學其實可以發現,在之前的登錄注冊中我們一直沒有對空表單進行驗證,當然這是我故意為之,但如果在生產環境,這將是一個災難的開始,所以,在編程中無論是前端還是后端都要求要對數據進行驗證,作為后端,更要保持一種永遠不相信前端傳遞數據的態度去做數據校驗。
本章節我們將使用Flask官方推薦的Flask-WTF擴展來重構我們的登錄注冊表單!
Flask-WTF是Flask 和 WTForms 的簡單集成,包括 CSRF、文件上傳和 reCAPTCHA。
pip install Flask-WTF
在app/auth/目錄下新建一個forms.py的文件,所有的表單驗證代碼都放到這個文件當中!
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, ValidationError, EqualTo
from werkzeug.security import check_password_hash
from .models import User
class LoginForm(FlaskForm):
# 登錄表單
def qs_username(username):
# 對該字段進行在傳遞之前處理
u=f'{username}123456'
print(u)
return username
username=StringField('username', validators=[
DataRequired(message="不能為空"),
Length(max=32, message="不符合字數要求!")
], filters=(qs_username,))
password=PasswordField('password', validators=[
DataRequired(message="不能為空"),
Length(max=32, message="不符合字數要求!")
])
def validate_username(form, field):
user=User.query.filter_by(username=field.data).first()
if user is None:
error='該用戶不存在!'
raise ValidationError(error)
elif not check_password_hash(user.password, form.password.data):
raise ValidationError('密碼不正確')
代碼詳解:
class LoginForm(FlaskForm): 創建了一個登錄表單類,繼承了FlaskForm類
StringField, PasswordField
這些都是wtforms內置的字段,負責呈現和數據轉換。
官方文檔:https://wtforms.readthedocs.io/en/3.0.x/fields/
他繼承自Filed的基類,其中有一些比較重要的參數我們大概在這里理解一下!
第一個字符串其實是該類的label參數,字段的標簽,也就是轉換到html中的label!
validators傳入對該字段的一些驗證器,在提交數據之前對數據進行驗證!
filters這個參數比較特殊,官方文檔并沒有對其詳細說明,只說是篩選器,其實怎么說就是在額外的方法中對該字段的值提前處理過濾,元組中的每個值都是一個回調函數,函數不需要傳入括號,但這個回調函數默認有一個參數,這個參數就是本身該字段的值,所以在定義該函數時就必須傳入一個參數!例如:我們定義username之前定義的這個方法!
def qs_username(username):
# 對該字段進行在傳遞之前處理
u=f'{username}123456'
print(u)
return username
備注:必須返回處理后的這個參數,否則會觸發DataRequired驗證器,后端獲取不到該表單的值!
官方文檔: https://wtforms.readthedocs.io/en/3.0.x/validators/#custom-validators
在之前的視圖函數中我們對用戶名和密碼都做了校驗,現在我們需要把驗證的代碼全部移動到表單類中,代碼如下:
def validate_username(form, field):
user=User.query.filter_by(username=field.data).first()
if user is None:
error='該用戶不存在!'
raise ValidationError(error)
elif not check_password_hash(user.password, form.password.data):
raise ValidationError('密碼不正確')
這個函數的寫法是固定的validate_{filed},validate_后邊的filed是指你需要驗證的某個字段名,比如我們這個驗證,他主要就是對username字段進行驗證,這個函數中參數的filed就是這個字段,通過field.data就可以獲取到usernam的值。form參數則指代的是整個表單,可以用form.{filed}.data的方式獲取表單類中某個具體字段的值!
當了解了登錄表單后,我們完全就可以參照登錄表單去實現注冊表單,代碼如下:
路徑:app/auth/forms.py
class RegisterForm(FlaskForm):
# 注冊表單
username=StringField('username', validators=[
DataRequired(message="不能為空"),
Length(min=2, max=32, message="超過限制字數!")
])
password=PasswordField('password', validators=[
DataRequired(message="不能為空"),
Length(min=2, max=32, message="超過限制字數!"),
EqualTo('password1', message='兩次密碼輸入不一致!')
])
password1=PasswordField('password1')
def validate_username(form, field):
user=User.query.filter_by(username=field.data).first()
if user is not None:
error='該用戶名稱已存在!'
raise ValidationError(error)
這里唯一需要注意的是兩次密碼是否輸入一致,我們用了一個內置的驗證器EqualTo,使用方式可完全參照代碼,他會自動校驗password和password1輸入的值是否一致!
from ..forms import LoginForm, RegisterForm
@bp.route('/login', methods=['GET', 'POST'])
def login():
# 登錄視圖
# form=LoginForm(meta={'csrf': False}) # 禁用csrf
form=LoginForm()
if form.validate_on_submit():
user=auth.User.query.filter_by(username=form.username.data).first()
session.clear()
session['user_id']=user.id
return redirect(url_for('index'))
return render_template('login.html', form=form)
@bp.route('/register', methods=['GET', 'POST'])
def register():
# 注冊視圖
form=RegisterForm()
if form.validate_on_submit():
user=auth.User(username=form.username.data, password=generate_password_hash(form.password.data))
db.session.add(user)
db.session.commit()
session.clear()
session['user_id']=user.id
return redirect(url_for('index'))
return render_template('register.html', form=form)
1、首先從forms.py中引入了我們定義的登錄(LoginForm)和注冊(RegisterForm)表單類!
2、form=RegisterForm() 實例化表單類
3、if form.validate_on_submit(): 驗證前端傳遞的數據是否有效,并且會自動判斷是POST請求還是GET請求!
4、 數據驗證通過則進入之后的邏輯,未驗證通過則返回我們在表單類中傳入的驗證提示!
我們以調用username字段的驗證提示為例,在模板中加入這段代碼即可獲得錯誤提示!
<!-- 表單驗證 -->
{% if form.username.errors %}
<b-message type="is-danger">
<ul class="errors">
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</b-message>
{% endif %}
路徑:app/auth/templates/login.html 以登陸表單為例,代碼如下:
{% block auth_form %}
<form action="" method="post" style="margin-top: 40%;" class="box">
<div class=" has-text-centered mb-3">
<p class=" subtitle">登錄</p>
<h1 class="title">FlaskBlog</h1>
</div>
{{ form.csrf_token }}
<!-- 消息閃現 -->
{% with messages=get_flashed_messages() %}
<b-message type="is-danger">
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</b-message>
{% endwith %}
<!-- 表單驗證 -->
{% if form.username.errors %}
<b-message type="is-danger">
<ul class="errors">
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</b-message>
{% endif %}
<div class="field">
<p class="control has-icons-left has-icons-right">
{{ form.username(class='input', placeholder='Username') }}
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
</span>
<span class="icon is-small is-right">
<i class="fas fa-check"></i>
</span>
</p>
</div>
<div class="field">
<p class="control has-icons-left">
{{ form.password(class='input', placeholder='Password') }}
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field">
<p class="control">
<input class="button is-success is-fullwidth" type="submit" value="Login">
</p>
</div>
</form>
{% endblock auth_form %}
{{ form.csrf_token }} 隱式的創建一個csrftoken的表單
{{ form.username(class='input', placeholder='Username') }} 這樣就可以直接獲得一個表單html并自動渲染,向該表單增加書香的方式就是像代碼中這樣傳入參數和值即可,當然也可以提前在表單類中定義!
剩下的注冊表單,就當是給大家留作的一個作業,大家自行去參照登錄表單完善重構一下,加油哦!我相信你可以!
到這里我們的表單驗證就大概了解了,之后的章節就是基本的增刪改查以及表單驗證,都是基于我們這些章節學習的知識點,所以之后的章節就不會過多的去講解每行代碼的意思,重心放在邏輯的展示上,如果基礎較差的同學,到這里,可以去反復的把前邊所有章節的內容去練習,寫代碼其實就是寫的多了就會了,也就理解了,練習 練習 再練習!
者:俊欣
來源:關于數據分析與可視化
今天小編帶領大家用Python自制一個自動生成探索性數據分析報告這樣的一個工具,大家只需要在瀏覽器中輸入url便可以輕松的訪問,如下所示
首先我們導入所要用到的模塊,設置網頁的標題、工具欄以及logo的導入,代碼如下
from st_aggrid import AgGrid
import streamlit as st
import pandas as pd
import pandas_profiling
from streamlit_pandas_profiling import st_profile_report
from pandas_profiling import ProfileReport
from PIL import Image
st.set_page_config(layout='wide') #Choose wide mode as the default setting
#Add a logo (optional) in the sidebar
logo=Image.open(r'wechat_logo.jpg')
st.sidebar.image(logo, width=120)
#Add the expander to provide some information about the app
with st.sidebar.expander("關于這個項目"):
st.write("""
該項目是將streamlit和pandas_profiling相結合,在您上傳數據集之后自動生成相關的數據分析報告,當然該項目提供了兩種模式 全量分析還是部分少量分析,這里推薦用部分少量分析,因為計算量更少,所需要的時間更短,效率更高
""")
#Add an app title. Use css to style the title
st.markdown(""" <style> .font {
font-size:30px ; font-family: 'Cooper Black'; color: #FF9633;}
</style> """, unsafe_allow_html=True)
st.markdown('<p class="font">請上傳您的數據集,該應用會自動生成相關的數據分析報告</p>', unsafe_allow_html=True)
output
緊接的是我們需要上傳csv文件,代碼如下
uploaded_file=st.file_uploader("請上傳您的csv文件: ", type=['csv'])
我們可以選擇針對數據集當中所有的特征進行一個統計分析,或者只是針對部分的變量來一個數據分析,代碼如下
if uploaded_file is not None:
df=pd.read_csv(uploaded_file)
option1=st.sidebar.radio(
'您希望您的數據分析報告中包含哪些變量呢',
('所有變量', '部分變量'))
if option1=='所有變量':
df=df
elif option1=='部分變量':
var_list=list(df.columns)
要是用戶勾選的是部分變量,只是針對部分變量來進行一個分析的話,就會彈出來一個多選框來供用戶選擇,代碼如下
var_list=list(df.columns)
option3=st.sidebar.multiselect(
'篩選出您希望在數據分析報告中包含的變量',
var_list)
df=df[option3]
用戶可以挑選到底是“簡單分析”或者是“完整分析”,要是勾選的是“完整分析”的話,會跳出相應的提示,提示“完整分析”由于涉及到更加復雜的計算操作,耗時更加地長,要是遇到大型的數據集,還會有計算失敗的情況出現
option2=st.sidebar.selectbox(
'篩選模式,完整分析還是簡單分析',
('簡單分析', '完整分析'))
if option2=='完整分析':
mode='complete'
st.sidebar.warning(
'完整分析由于涉及到更加復雜的計算操作,耗時更加地長,要是遇到大型的數據集,還會有計算失敗的情況出現,這里推薦使用簡單分析')
elif option2=='簡單分析':
mode='minimal'
grid_response=AgGrid(
df,
editable=True,
height=300,
width='100%',
)
updated=grid_response['data']
df1=pd.DataFrame(updated)
當用戶點擊“生成報告”的時候就會自動生成一份完整的數據分析報告了,代碼如下
if st.button('生成報告'):
if mode=='complete':
profile=ProfileReport(df,
title="User uploaded table",
progress_bar=True,
dataset={
"簡介": '歡迎關注公眾號:關于數據分析與可視化',
"作者": '俊欣',
"時間": '2022.05'
})
st_profile_report(profile)
elif mode=='minimal':
profile=ProfileReport(df1,
minimal=True,
title="User uploaded table",
progress_bar=True,
dataset={
"簡介": '歡迎關注公眾號:關于數據分析與可視化',
"作者": '俊欣',
"時間": '2022.05'
})
st_profile_report(profile)
最后出來的結果如下,這里再來顯示一遍
取網頁圖片的基本流程是:
1 使用urllib.request模板請求返回網頁文本;
2 從網頁文本中使用正則表達式篩選出img src地址(返回一個全部src的列表);
3 圖片文件逐一檢索或復制;
代碼:
運行效果:
附代碼1:
import re
import urllib.request
import os
#1 抓取網頁
#url='http://www.kgc.cn/list'
url='http://www.ttpaihang.com/vote/rank.php?voteid=1410&page=2'
req=urllib.request.urlopen(url)
buf=req.read()
req.close()
#2 獲取圖片地址
i=url.find("/",9) # 本句及下面三句截取url的前半截
url2=url
if i > 0 :
....url2=url[:i]
#buf=buf.decode('UTF-8')
buf=buf.decode('gb2312')
#listurl=re.findall(r'http:.[^"]+\.jpg',buf)
listurl=re.findall(r'img src=.[^"]+\.jpg',buf)
for i in range(len(listurl)):.... # 把字符img src="去掉
....listurl[i]=listurl[i].replace('img src="',"")
....if not re.match("http",listurl[i]):
........listurl[i]=url2 + listurl[i]
....print(listurl[i])
#3 抓取圖片并保存到本地
i=0
fpath="D:\pic2\"
if not os.path.isdir(fpath):
....os.mkdir(fpath)
for url in listurl:
....f=open(fpath + str(i)+'.jpg','wb')
....req=urllib.request.urlopen(url)
....buf=req.read()
....f.write(buf)
....f.close()
....i+=1
........
附代碼2(寫成函數的形式)
import re .... .... .... .... # 正則表達式
import urllib.request .... .... # 從服務器請求返回資源
import os .... .... .... .... # 文件和目錄操作
import socket .... .... .... .... # 套接字操作
#socket.setdefaulttimeout(20)....................# 設置socket層的超時時間為20秒
def gethtml(url): #1 抓取網頁html內容
....with urllib.request.urlopen(url) as req:
........buf=req.read()
........return buf
def getImg(buf,codec,fpath): #2 從html篩選圖片地址到list
....i=url.find("/",9)............................ # 本句及下面三句截取url的前半截
....url2=url
....if i > 0 :
........url2=url[:i]
....buf=buf.decode(codec)
....
....reg=r'img src="(.+?\.jpg)"'....#正則表達式,得到圖片地址
....#listurl=re.findall(r'http:.[^"]+\.jpg',buf)
....#listurl=re.findall(r'img src=.[^"]+\.jpg',buf)
....listurl=re.findall(reg,buf)
....print("準備下載圖片數量:",len(listurl))
....for i in range(len(listurl)):................
........#listurl[i]=listurl[i].replace('img src="',"") # 把字符img src="去掉
........if not re.match("http",listurl[i]):
............listurl[i]=url2 + listurl[i]
........print(listurl[i])
............#3 抓取圖片并保存到本地
....i=0
....
....if not os.path.isdir(fpath):
........os.mkdir(fpath)
....'''
....for imgurl in listurl:
........urllib.request.urlretrieve(imgurl,fpath + str(i)+'.jpg')
........i+=1
....'''#下面的操作方式要快一點
....for imgurl in listurl:
........f=open(fpath + str(i)+'.jpg','wb') # 新建空白圖片文件
........req=urllib.request.urlopen(imgurl) # 獲取網頁圖片文件
........buf=req.read().... .... .... # 讀取網站上圖片文件內容
........f.write(buf).... .... .... # 將網站上圖片內容寫入新建的圖片文件
........f.close()
........i+=1
# 四處內容需要確認:1 網頁url; .... ....2 網頁編碼UTF-8或gb2312;
#................ 3 圖片擴展名jpg或png(兩處); 4 保存的文件夾
#url='http://www.kgc.cn/list'
url='http://www.ttpaihang.com/vote/rank.php?voteid=1410&page=3'
buf=gethtml(url)
#codec='UTF-8'
codec='gb2312'
fpath="D:\pic4\"
print(getImg(buf,codec,fpath))
-End-
*請認真填寫需求信息,我們會在24小時內與您取得聯系。