Flask修煉-模板-02!
內容概述:
過濾器,
自定義過濾器,
控制程式碼塊,
模板程式碼複用,
特有變數和函式,
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異常,可以加上
ignore missing
關鍵字。如果包含的模板檔案不存在,會忽略這條include語句。
{% 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') }}">回到首頁</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 的值
- 進行對比
- 如果比較之後兩值一樣,那麼代表是正常的請求,如果沒取到或者比較不一樣,代表不是正常的請求,不執行下一步操作