Python全棧工程師之從網頁搭建入門到Flask全棧專案實戰(6)

2022-12-15 18:01:19

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物件。這個方法命名就沒有要求,這個見範例程式碼。