内容概述:
過濾器,
自定義過濾器,
控制代碼塊,
模闆代碼複用,
特有變量和函數,
Flask-WTF 表單,
CSRF
過濾器
過濾器的本質就是函數。有時候我們不僅僅隻是需要輸出變量的值,我們還需要修改變量的顯示,甚至格式化、運算等等,而在模闆中是不能直接調用 Python 中的某些方法,那麼這就用到了過濾器
過濾器的使用方式為:變量名 | 過濾器。 {{ 變量名 | 過濾器 }}
如果沒有任何參數傳給過濾器,則可以把括号省略掉
在 jinja2 中,過濾器是支援鍊式調用的
常見内建過濾器
字元串操作
- **safe:**禁用轉義
<p>{{ '<em>hello</em>' | safe }}</p>
- **capitalize:**把變量值的首字母轉成大寫,其餘字母轉小寫
<p>{{ 'hello' | capitalize }}</p>
- **lower:**把值轉成小寫
<p>{{ 'HELLO' | lower }}</p>
- **upper:**把值轉成大寫
<p>{{ 'hello' | upper }}</p>
- **title:**把值中的每個單詞的首字母都轉成大寫
<p>{{ 'hello' | title }}</p>
- **reverse:**字元串反轉
<p>{{ 'olleh' | reverse }}</p>
- **format:**格式化輸出
<p>{{ '%s is %d' | format('name',17) }}</p>
- **striptags:**渲染之前把值中所有的HTML标簽都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>
- truncate: 字元串截斷
<p>{{ 'hello every one' | truncate(9)}}</p>
清單操作
- **first:**取第一個元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
- **last:**取最後一個元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
- **length:**擷取清單長度
<p>{{ [1,2,3,4,5,6] | length }}</p>
- **sum:**清單求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
- **sort:**清單排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
自定義過濾器
過濾器的本質是函數。當模闆内置的過濾器不能滿足需求,可以自定義過濾器。自定義過濾器有兩種實作方式:重要:自定義的過濾器名稱如果和内置的過濾器重名,會覆寫内置的過濾器
- 一種是通過Flask應用對象的 add_template_filter 方法
- 通過裝飾器來實作自定義過濾器
# 自定義過濾器
# 方式1: 裝飾器的形式
# @app.template_filter('lireverse')
def do_lireverse(li):
temp = list(li)
temp.reverse()
return temp
# 方式2: 直接添加過濾器
app.add_template_filter(do_lireverse, 'lireverse')
控制代碼塊
控制代碼塊主要包含兩個:
- if/else if /else / endif
- for / endfor
{% for item in my_list if item.id != 5 %}
{% if loop.index == 1 %}
<li style="background-color: orange">{{ item.value }}</li>
{% elif loop.index == 2 %}
<li style="background-color: green">{{ item.value }}</li>
{% elif loop.index == 3 %}
<li style="background-color: gray">{{ item.value }}</li>
{% else %}
<li style="background-color: red">{{ item.value }}</li>
{% endif %}
{% endfor %}
模闆代碼複用
- 宏(Macro)、繼承(Block)、包含(include)均能實作代碼的複用。
- **繼承(Block)**的本質是代碼替換,一般用來實作多個頁面中重複不變的區域。
- **宏(Macro)**的功能類似函數,可以傳入參數,需要定義、調用。
- **包含(include)**是直接将目标模闆檔案整個渲染出來。
宏
對宏(macro)的了解:
- 把它看作 Jinja2 中的一個函數,它會傳回一個模闆或者 HTML 字元串
- 為了避免反複地編寫同樣的模闆代碼,出現代碼備援,可以把他們寫成函數以進行重用
- 需要在多處重複使用的模闆代碼片段可以寫入單獨的檔案,再包含在所有模闆中,以避免重複
- 定義宏
{% macro input(name,value='',type='text') %} <input type="{{type}}" name="{{name}}" value="{{value}}" class="form-control"> {% endmacro %}
- 調用宏
{{ input('name' value='zs')}}
- 這會輸出
<input type="text" name="name" value="zs" class="form-control">
- 把宏單獨抽取出來,封裝成html檔案,其它模闆中導入使用,檔案名可以自定義macro.html
{% macro function(type='text', name='', value='') %} <input type="{{type}}" name="{{name}}" value="{{value}}" class="form-control"> {% endmacro %}
- 在其它模闆檔案中先導入,再調用
{% import 'macro.html' as func %} {% func.function() %}
模闆繼承
模闆繼承是為了重用模闆中的公共内容。一般 Web 開發中,繼承主要使用在網站的頂部菜單、底部。這些内容可以定義在父模闆中,子模闆直接繼承,而不需要重複書寫。
- 标簽定義的内容
{% block top %} {% endblock %}
- 相當于在父模闆中挖個坑,當子模闆繼承父模闆時,可以進行填充。
- 子模闆使用 extends 指令聲明這個模闆繼承自哪個模闆
- 父模闆中定義的塊在子模闆中被重新定義,在子模闆中調用父模闆的内容可以使用super()
在子模闆中使用 extends
指令聲明這個模闆繼承自哪
模闆繼承使用時注意點:
- 不支援多繼承
- 為了便于閱讀,在子模闆中使用extends時,盡量寫在模闆的第一行。
- 不能在一個模闆檔案中定義多個相同名字的block标簽。
- 當在頁面中使用多個block标簽時,建議給結束标簽起個名字,當多個block嵌套時,閱讀性更好。
{% extends 'base.html' %}
{% block contentBlock %}
{{ super() }}
我是子類的内容<br/>
{% endblock %}
包含
Jinja2模闆中,除了宏和繼承,還支援一種代碼重用的功能,叫包含(Include)。它的功能是将另一個模闆整個加載到目前模闆中,并直接渲染。
包含在使用時,如果包含的模闆檔案不存在時,程式會抛出TemplateNotFound異常,可以加上
關鍵字。如果包含的模闆檔案不存在,會忽略這條include語句。
ignore missing
{% include 'includeaaa.html' ignore missing %}<br/>
{% include 'include.html' %}<br/>
特有變量和函數
config: 可以從模闆中直接通路 Flask 目前的 config 對象
**request:**就是 flask 中代表目前請求的 request 對象
session: 為 flask 的 session 對象
g 變量: 在視圖函數設定 g 變量的 name 屬性的值,然後在模闆中直接可以取出
url_for():url_for會根據傳入的路由器函數名,傳回該路由對應的URL,在模闆中始終使用url_for()就可以安全的修改路由綁定的URL,則不比擔心模闆中渲染出錯的連結;如果我們定義的路由URL是帶有參數的,則可以把它們作為關鍵字參數傳入url_for(),Flask會把他們填充進最終生成的URL中
get_flashed_messages():這個函數會傳回之前在flask中通過flask()傳入的消息的清單,flash函數的作用很簡單,可以把由Python字元串表示的消息加入一個消息隊列中,再使用get_flashed_message()函數取出它們并消費掉:
config: {{ config.DEBUG }}<br/> {# 可以從模闆中直接通路Flask目前的config對象 #}
<hr/>
請求上下文中兩個變量:<br/>
目前路由: {{ request.url }}<br/>
session 取值: {{ session.name }}<br/>
<hr/>
應用上下文中 1 個變量<br/>
g 變量: {{ g.name }}<br/>
<hr/>
兩個可以直接使用的函數:<br/>
<a href="{{ url_for('index') }}" target="_blank" rel="external nofollow" >回到首頁</a><br/>
<hr/>
擷取閃現消息:<br/>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
Flask-WTF 表單
Web 表單是 Web 應用程式的基本功能。
它是HTML頁面中負責資料采集的部件。表單有三個部分組成:表單标簽、表單域、表單按鈕。表單允許使用者輸入資料,負責HTML頁面資料采集,通過表單将使用者輸入的資料送出給伺服器。
在Flask中,為了處理web表單,我們可以使用 Flask-WTF 擴充,它封裝了 WTForms,并且它有驗證表單資料的功能
WTForms 支援的 HTML 标準字段
字段對象 | 說明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密碼文本字段 |
HiddenField | 隐藏檔案字段 |
DateField | 文本字段,值為 datetime.date 文本格式 |
DateTimeField | 文本字段,值為 datetime.datetime 文本格式 |
IntegerField | 文本字段,值為整數 |
DecimalField | 文本字段,值為decimal.Decimal |
FloatField | 文本字段,值為浮點數 |
BooleanField | 複選框,值為True 和 False |
RadioField | 一組單選框 |
SelectField | 下拉清單 |
SelectMutipleField | 下拉清單,可選擇多個值 |
FileField | 檔案上傳字段 |
SubmitField | 表單送出按鈕 |
FormField | 把表單作為字段嵌入另一個表單 |
FieldList | 一組指定類型的字段 |
WTForms 常用驗證函數
驗證函數 | 說明 |
---|---|
DataRequired | 確定字段中有資料 |
EqualTo | 比較兩個字段的值,常用于比較兩次密碼輸入 |
Length | 驗證輸入的字元串長度 |
NumberRange | 驗證輸入的值在數字範圍内 |
URL | 驗證URL |
AnyOf | 驗證輸入值在可選清單中 |
NoneOf | 驗證輸入值不在可選清單中 |
使用 Flask-WTF 需要配置參數 SECRET_KEY。
CSRF_ENABLED是為了CSRF(跨站請求僞造)保護。 SECRET_KEY用來生成加密令牌,當CSRF激活的時候,該設定會根據設定的密匙生成加密令牌。
伺服器代碼
from flask import Flask, render_template, flash, request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import InputRequired, EqualTo, DataRequired
app = Flask(__name__)
# 關閉 csrf 驗證
app.config['WTF_CSRF_ENABLED'] = True
app.secret_key = 'asdfasdf'
# 自定義系統資料庫單
class RegisterForm(FlaskForm):
username = StringField('使用者名:', validators=[InputRequired('請輸入使用者名')], render_kw={'placeholder': '我是占位文字'})
password = PasswordField('密碼:', validators=[InputRequired('請輸入密碼')])
password2 = PasswordField('确認密碼:', validators=[InputRequired('請輸入确認密碼'), EqualTo('password', '兩次密碼要一緻')], )
submit = SubmitField('注冊:')
@app.route('/')
def index():
return 'Hello World!'
@app.route('/register_wtf', methods=['GET', 'POST'])
def register_wtf():
register_form = RegisterForm()
# 使用 wtf 表單幫我們做驗證
if register_form.validate_on_submit():
# 執行注冊邏輯
# 取到表單中送出上來的三個參數
username = request.form.get("username")
password = request.form.get("password")
password2 = request.form.get("password2")
# 取值方式 2
# username = register_form.username.data
# 假裝做注冊操作
print(username, password, password2)
return "success"
else:
if request.method == 'POST':
flash('參數錯誤')
return render_template('temp5_wtf.html', form=register_form)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == "POST":
# 取到表單中送出上來的三個參數
username = request.form.get("username")
password = request.form.get("password")
password2 = request.form.get("password2")
if not all([username, password, password2]):
# 向前端界面彈出一條提示(閃現消息)
flash("參數不足")
elif password != password2:
flash("兩次密碼不一緻")
else:
# 假裝做注冊操作
print(username, password, password2)
return "success"
return render_template('temp5_wtf.html')
if __name__ == '__main__':
app.run(debug=True)
模闆代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
<label>使用者名:</label><input type="text" name="username" placeholder="請輸入使用者名"><br/>
<label>密碼:</label><input type="password" name="password" placeholder="請輸入密碼"><br/>
<label>确認密碼:</label><input type="password" name="password2" placeholder="請輸入确認密碼"><br/>
<input type="submit" value="注冊">
</form>
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
<hr/><br/>
以下是使用 Flask_wtf 實作 <br/><br/><br/><br/>
<form method="post">
{{ form.csrf_token() }}
{{ form.username.label }}{{ form.username }}<br/>
{{ form.password.label }}{{ form.password }}<br/>
{{ form.password2.label }}{{ form.password2 }}<br/>
{{ form.submit }}<br/>
</form>
</body>
</html>
CSRF
全拼為
CSRF
,譯為跨站請求僞造。
Cross Site Request Forgery
CSRF
指攻擊者盜用了你的身份,以你的名義發送惡意請求。
造成的問題:個人隐私洩露以及财産安全。
流程:
CSRF
- 用戶端浏覽并登入信任網站 A
- 驗證通過,在使用者處産生 A 的 cookie
- 使用者在沒有登出的情況下通路攻擊網站 B
- B 會用一個誘惑性的按鈕來讓使用者點選,但是這個點選會要求通路第三方網站 A ,發出一個請求
- 根據 B 的請求,浏覽器要去通路 A 網站,通路 A 網站會預設帶上之前儲存的 cookie
- A 網站不知道這個請求是使用者發出的還是 B 攻擊網站發出的,但是浏覽器過去的時候帶上了 cookie,是以 A 會根據使用者的權限處理這個請求,B 攻擊網站的請求裡一般會有請求,這樣 B 就達到了模拟使用者操作進行攻擊的目的
防止 CSRF 攻擊
- 在用戶端向後端請求界面資料的時候,後端會往響應中的 cookie 中設定 csrf_token 的值
- 在 Form 表單中添加一個隐藏的的字段,值也是 csrf_token
- 在使用者點選送出的時候,會帶上這兩個值向背景發起請求
- 後端接受到請求,以會以下幾件事件:
- 從 cookie中取出 csrf_token
- 從 表單資料中取出來隐藏的 csrf_token 的值
- 進行對比
- 如果比較之後兩值一樣,那麼代表是正常的請求,如果沒取到或者比較不一樣,代表不是正常的請求,不執行下一步操作