1.表單介紹
1.1.表單知識回顧
常見的表單元素:
- 表單標籤<form>
- action:表單提交的URL地址
- method:表單請求的方式(GET/POSt)
- enctype:請求內容的形式,如:application/x-www-form-urlencoded、multipart/form-data
- 單行文字框/多行文字框
- textarea:多行文字
- 單行文字(type的不同值),常見的有:text(單行文字)、password(密碼)、email(郵箱)、url(URL)、number(數字)、color(顏色)、日期時間等(date、month、week等等)
- 選擇(單選、多選、下拉選擇)
- 單選: <input type="radio">
- 多選: <input type="checkbox">
- 下拉框選擇: <select><option></option></select>
- 隱藏表單域: <input type="hidden">
- 表單按鈕: <input type="button"> <button></button>
- 檔案上傳框: <input type="file">
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>表單知識點回顧</title> 6 </head> 7 <body> 8 <form action="/index" method="post" enctype="multipart/form-data"> 9 <ul> 10 <li> 11 使用者地址: 12 <textarea name="" id="" cols="30" rows="10" placeholder="請輸入地址"></textarea> 13 </li> 14 <li> 15 使用者名稱: 16 <input type="text" placeholder="請輸入使用者名稱"> 17 </li> 18 <li> 19 密碼: 20 <input type="password" placeholder="請輸入密碼"> 21 </li> 22 <li> 23 使用者的年齡: 24 <input type="number"> 25 </li> 26 <li> 27 性別: 28 <label><input type="radio" value="男" name="sex">男</label> 29 <label><input type="radio" value="女" name="sex">女</label> 30 </li> 31 <li> 32 愛好 33 <input id="id-paly-ball" type="checkbox" value="打球"> 34 <label for="id-paly-ball">打球</label> 35 <input id="id-paly" type="checkbox" value="玩耍"> 36 <label for="id-paly">玩耍</label> 37 </li> 38 </ul> 39 </form> 40 </body> 41 </html>
在檢視中獲取表單值:
- get請求: request.args.get('name',None)
- post請求: request.form.get('name',None)
思考:HTML表單在Flask中如何快速使用?
1.2.wtf表單介紹
透過在Flask中寫python程式碼,可以直接生成HTML表單,透過wtf實現。
flask-wtf提供的3個組要功能:
- 整合wtforms
- CSRF保護,flask-wtf能保護所有表單免受跨站請求偽造(CSRF)的攻擊
- 與Flask-Uploads一起支援檔案上傳
安裝
- pip安裝: pip install Flask-WTF
- 原始碼安裝: python setup.py install
下載好之後如何使用呢?需不需要一些配置呢?它的配置很簡單,它配置的目的就是用來做CSRF保護。只需要在Flask app上加上 WTF_CSRF_SECRET_KEY = 'a random string' 就行了,這個key可以隨便給一個字串,沒有要求,只是一個隨機的串就行了。
1 from flask import Flask, render_template,flash 2 from flask_sqlalchemy import SQLAlchemy 3 4 app = Flask(__name__) 5 # 配置資料庫的連線引數 6 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@127.0.0.1/test_flask' 7 app.config['SECRET_KEY'] = 'abc' #訊息閃現保護的key,注意當訊息閃現的SECRET_KEY配置了,WTF_CSRF_SECRET_KEY也可以不用配置,但是你配置了也沒有影響,這個知識點了解一下 8 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc' #WTF_CSRF_SECRET_KEY配置
第一個表單模型
1 from flask_wtf import FlaskForm #匯入flask_wtf的FlaskForm類 2 from wtforms import StringField #StringField表示的是一個文字的輸入框 3 4 5 class LoginForm(FlaskForm): #繼承FlaskForm類 6 """ 登入表單的實現 """ 7 username = StringField(label='使用者名稱')
1.3.表單常用欄位型別及渲染
表單常用欄位型別
- 文字/字串
- StringField :字串輸入
- PasswordField :密碼輸入
- TextAreaField :長文字輸入
- HiddenField :隱藏表單域
- 數值(整數,小數)
- FloatField :浮點數輸入
- IntegerField :整數輸入
- DecimalField :小數輸入(更準確)
- 選擇
- RadioFied :radio單選
- SelectField :下拉單選
- SelectMultipleField :下拉多選
- BooleanField :勾選(核取方塊)
- 日期/時間
- DateField :日期選擇
- DateTimeField :日期時間選擇
- 檔案/檔案上傳
- FileField :檔案單選
- MultipleFileField :檔案多選
- 其他
- SubmitField :提交按鈕
- FieldList :自定義的表單選擇列表(如:選擇使用者物件)
- FormField :自定義多個欄位構成的選項
表單欄位的常用核心引數
- lable :lable標籤(如:輸入框錢的文字描述)
- default :表單的預設值
- validators :表單驗證規則
- widget :定製介面顯示方式(如:文字框、選擇框)
- description :幫助文字
表單渲染
使用模板語法渲染表單內容:
- 表單輸入區域: {{form.username}}
- 表單label: {{form.username.label}}
例項程式碼:
forms.py:python編寫登入表單頁面匯入FlaskForm,wtfforms實現
1 from flask_wtf import FlaskForm 2 from wtforms import StringField, PasswordField, SubmitField 3 4 5 class LoginForm(FlaskForm): 6 """ 登入表單的實現 """ 7 username = StringField(label='使用者名稱', default='admin') 8 password = PasswordField(label='密碼') 9 submit = SubmitField('登入')
app.py:匯入forms檔案,將python編寫好的HTML頁面展示類傳遞給html檔案
1 from flask import Flask, render_template 2 3 from forms import LoginForm 4 5 app = Flask(__name__) 6 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc' 7 app.config['SECRET_KEY'] = 'abc' 8 9 10 @app.route('/form', methods=['GET', 'POST']) 11 def page_form(): 12 """ form 表單練習 """ 13 form = LoginForm() 14 return render_template('page_form.html', form=form)
page_form.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Flask Form表單練習</title> 6 </head> 7 <body> 8 <h3>歡迎登入</h3> 9 <form action="" method="post"> 10 <p> 11 {{ form.username.label }} 12 {{ form.username }} 13 </p> 14 <p> 15 {{ form.password.label }} 16 {{ form.password }} 17 </p> 18 <p> 19 {{ form.submit }} 20 </p> 21 22 </form> 23 </body> 24 </html>
1.4.透過表單儲存資料
保單儲存資料步驟:
- 第一步:檢測表單是否已經透過驗證
- form.validate_on_submit()
- 第二步:獲取表單中傳遞過來的值
- form.field_name.data
- 第三步:業務邏輯程式碼編寫(結合ORM)
表單儲存資料的時候會觸發CSRF表單保護:
- 預設模板是開啟CSRF保護
- 關閉單個表單CSRF保護
- form = LoginForm(csrf_enabled=False)
- 全域性關閉(不推薦)
- 在類上面加上:WTF_CSRF_ENABLED=False
如果不關閉CSRF保護,如何處理:
同步請求CSRF保護,在模板中新增csrf_token,透過CSRF機制的驗證:
- 方式一:{{ form.csrf_token }}
- 方式二:<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
例項程式碼:
新增使用者登錄檔單,對註冊的內容進行入庫,註冊成功後返回index主頁面。
app.py
1 from flask import Flask, render_template, flash, redirect, url_for 2 from flask_sqlalchemy import SQLAlchemy 3 4 from forms import RegisterForm 5 6 app = Flask(__name__) 7 # 配置資料庫的連線引數 8 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@********/test_flask' 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc' 10 app.config['SECRET_KEY'] = 'abc' 11 db = SQLAlchemy(app) 12 13 14 class User(db.Model): 15 __tablename__ = 'weibo_user' 16 id = db.Column(db.Integer, primary_key=True) 17 username = db.Column(db.String(64), nullable=False) 18 password = db.Column(db.String(256), nullable=False) 19 birth_date = db.Column(db.Date, nullable=True) 20 age = db.Column(db.Integer, default=0) 21 22 @app.route('/') 23 def index(): 24 """ 首頁 """ 25 return render_template('index.html') 26 27 @app.route('/user/register', methods=['GET', 'POST']) 28 def page_register(): 29 """ 新使用者註冊 """ 30 # csrf_enabled為False表示不做csrf校驗 31 # form = RegisterForm(csrf_enabled=False) 32 form = RegisterForm() 33 # 使用者在提交表單的時候,會觸發validate_on_submit 34 if form.validate_on_submit(): 35 # 表單驗證透過,接下來處理業務邏輯 36 # 1. 獲取表單資料 37 username = form.username.data 38 password = form.password.data 39 birth_date = form.birth_date.data 40 age = form.age.data 41 # 2. 構建使用者物件 42 user = User( 43 username=username, 44 password=password, 45 birth_date=birth_date, 46 age=age 47 ) 48 # 3. 提交到資料庫 49 db.session.add(user) 50 db.session.commit() 51 print('新增成功') 52 # 4. 跳轉到登入頁面 53 return redirect(url_for('index')) 54 else: 55 # 列印錯誤資訊 56 print(form.errors) 57 return render_template('page_register.html', form=form)
forms.py
1 from flask_wtf import FlaskForm 2 from wtforms import StringField, PasswordField, SubmitField, DateField, IntegerField 3 4 5 class RegisterForm(FlaskForm): 6 """ 使用者登錄檔單 """ 7 8 # def __init__(self, csrf_enabled, *args, **kwargs): 9 # super().__init__(csrf_enabled=csrf_enabled, *args, **kwargs) 10 11 username = StringField(label='使用者名稱', default='') 12 password = PasswordField(label='密碼') 13 birth_date = DateField(label='生日') 14 age = IntegerField(label='年齡') 15 submit = SubmitField('註冊')
page_register.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>使用者註冊</title> 6 </head> 7 <body> 8 <h3>使用者註冊</h3> 9 {# todo 註釋內容:使用宏來把表單進一步完善 #} 10 <form action="{{ url_for('page_register') }}" method="post"> 11 {{ form.csrf_token }} 12 <p> 13 {{ form.username.label }} 14 {{ form.username }} 15 </p> 16 <p> 17 {{ form.password.label }} 18 {{ form.password }} 19 </p> 20 <p> 21 {{ form.birth_date.label }} 22 {{ form.birth_date }} 23 </p> 24 <p> 25 {{ form.age.label }} 26 {{ form.age }} 27 </p> 28 <p> 29 {{ form.submit }} 30 </p> 31 32 </form> 33 </body> 34 </html>
index.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>使用者首頁</title> 6 </head> 7 <body> 8 註冊成功 9 </body> 10 </html>
2.表單驗證與圖片上傳
2.1.表單驗證
思考:以手機註冊為例,不驗證表單會怎麼樣?
- 使用者輸入的可能不是手機號
- 使用者輸入的可能不是他的手機號
- 不斷的提交表單
思考:表單驗證為了什麼?
- 更好的使用者體驗
- 更少的安全隱患
- 永遠不要相信使用者的輸入
內建的表單驗證器
- DataRequired/InputRequired :必填驗證
- Email/URL/UUID :電子郵箱/URL/UUID格式驗證
- Length(min=-1,max=-1,message=None) :長度範圍驗證
- EqualTo(fieldname,message=None) :重複驗證,用於密碼的二次輸入驗證和上一次是否一致
自定義表單驗證
- 場景一:只有本表單使用,在表單類裡面建立一個方法,方法名為:validatge_需要驗證的變數,在方法中編寫驗證邏輯
- 場景二:多個表單中使用,如:驗證手機號碼,登入/註冊/修改使用者資訊頁面均會用到。在表單類外面宣告一個驗證方法,傳入form物件。這個方法命名就沒有要求,這個見示例程式碼。
?圖為場景一的示例,場景二跟這個差不多;兩者的區別主要在於一個放到類裡面,一個放到類外面;備註:方法命名的規則暫時這麼理解,我學的好像是這樣的,我是這麼理解的,有知道所以然的可以教下我,謝謝!
例項:
場景一(當前表單使用):
1 import re 2 3 from flask_wtf import FlaskForm 4 from wtforms import StringField, PasswordField, SubmitField, DateField, IntegerField 5 from wtforms.validators import DataRequired,ValidationError 6 7 class RegisterForm(FlaskForm): 8 """ 使用者登錄檔單 """ 9 10 username = StringField(label='使用者名稱', default='') 11 password = PasswordField(label='密碼',validators=[DataRequired("請輸入密碼")]) #增加必填驗證 12 birth_date = DateField(label='生日') 13 age = IntegerField(label='年齡') 14 submit = SubmitField('註冊') 15 16 def validate_username(self,field): #注意這個方法必須是validate_***** 17 """ 驗證使用者名稱 """ 18 # 自定義:強制驗證使用者名稱為手機號 19 username = field.data 20 pattern = r'^1[0-9]{10}$' #自定義正則匹配規則 21 if not re.search(pattern,username): 22 raise ValidationError('請輸入手機號碼') 23 return field
我們看到報錯提示資訊是在控制檯展示的,那麼我們怎麼讓提示資訊展示給使用者?
forms.py檔案裡面自定義了表單驗證規則,驗證失敗丟擲form的錯誤
app.py檔案無法提交時,將列印form檔案丟擲的異常.。異常內容為 {'username': ['請輸入手機號碼']} 是個items結構。
前端頁面,獲取forms的username的報錯資訊,進行展示
場景二(多個表單使用):
?圖froms.py:用python編寫登入的前端頁面、表單自定義的驗證、表單欄位使用自定義的驗證
疑問:在類外面自定義的驗證方法,為什麼要傳form,不傳行不行;不穿form就會報錯,說應該傳兩個引數,只傳了一個,那這是為什麼呢?
看了系統自帶驗證規則的原始碼,發現都是兩個形參form和field,就暫時這麼理解吧:因為原始碼有,跟他保持一致,form具體幹什麼使的後面研究了再說吧!
?圖app.py啟動檔案:將登入頁面forms.py交給html檔案page_form進行渲染展示,同時進行form表單的驗證
?圖:page_form.html+展實效果
2.2.圖片上傳
圖片上傳主要有兩種方式實現
- 方式一:不使用wtf實現
- 方式二:使用wtf的FileField並新增型別驗證
圖片上傳:不使用wtf
- 1)設定<form>的enctype, enctype="multipart/form-data"
- 2)在檢視函式中獲取檔案物件 request.files
- 3)儲存檔案 f.save(file_path)
檔名稱格式化: werkzeug.utils.secure_filename
示例:
上傳不規則的檔名,secure_filename進行格式化
app.py
1 import os 2 3 from flask import Flask, render_template, redirect, url_for, request 4 from werkzeug.utils import secure_filename 5 6 from forms import UserAvatarForm 7 8 app = Flask(__name__) 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc' 10 app.config['SECRET_KEY'] = 'abc' 11 # 自定義的配置擴充套件,表示檔案上傳的路徑 12 app.config['UPLOAD_PATH'] = os.path.join(os.path.dirname(__file__), 'medias') 13 14 @app.route('/img/upload', methods=['GET', 'POST']) 15 def img_upload(): 16 """ 不使用wtf實現的檔案上傳 """ 17 if request.method == 'POST': 18 # 獲取檔案列表 19 files = request.files 20 file1 = files.get('file1', None) 21 if file1: 22 # 儲存檔案 23 f_name = secure_filename(file1.filename) 24 print('filename:', f_name) 25 file_name = os.path.join(app.config['UPLOAD_PATH'], f_name) 26 file1.save(file_name) 27 print('儲存成功') 28 return redirect(url_for('img_upload')) 29 return render_template('img_upload.html')
img_upload.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>檔案上傳</title> 6 </head> 7 <body> 8 <form action="/img/upload" method="post" enctype="multipart/form-data"> 9 <p> 10 <input type="file" name="file1"> 11 <input type="file" name="file2"> 12 </p> 13 <p> 14 <input type="submit" value="開始上傳"> 15 </p> 16 </form> 17 </body> 18 </html>
圖片上傳:驗證
- 檔案必須上傳: FileRequired
- 檔案型別驗證: FileAllowed
app.py
1 import os 2 3 from flask import Flask, render_template, redirect 4 from werkzeug.utils import secure_filename 5 6 from forms import UserAvatarForm 7 8 app = Flask(__name__) 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc' 10 app.config['SECRET_KEY'] = 'abc' 11 # 自定義的配置擴充套件,表示檔案上傳的路徑 12 app.config['UPLOAD_PATH'] = os.path.join(os.path.dirname(__file__), 'medias') 13 14 15 @app.route('/avatar/upload', methods=['GET', 'POST']) 16 def avatar_upload(): 17 """ 頭像上傳 """ 18 form = UserAvatarForm() 19 if form.validate_on_submit(): 20 # 獲取圖片物件 21 img = form.avatar.data 22 f_name = secure_filename(img.filename) 23 file_name = os.path.join(app.config['UPLOAD_PATH'], f_name) 24 img.save(file_name) 25 print('儲存成功') 26 return redirect('/') 27 else: 28 print(form.errors) 29 return render_template('avatar_upload.html', form=form)
forms.py
1 from flask_wtf import FlaskForm 2 from flask_wtf.file import FileRequired, FileAllowed 3 from wtforms import FileField 4 5 6 class UserAvatarForm(FlaskForm): 7 """ 使用者頭像上傳 """ 8 avatar = FileField(label='上傳頭像', validators=[ 9 FileRequired('請選擇頭像檔案'), 10 FileAllowed(['png'], '僅支援PNG圖片上傳') 11 ])
avatar_upload.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>WTF 檔案上傳</title> 6 </head> 7 <body> 8 <form action="/avatar/upload" method="post" enctype="multipart/form-data"> 9 {{ form.csrf_token }} 10 <p> 11 {{ form.avatar.label }} 12 {{ form.avatar }} 13 </p> 14 <p> 15 <input type="submit" value="開始上傳"> 16 </p> 17 </form> 18 </body>
使用擴充:Flask-Uploads
- 常用檔案型別驗證
- 指定檔案上傳的目錄