一章节我们通过在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小时内与您取得联系。