Flask_Jinja2模板(九)
在前面的示例中,檢視函式的主要作用是生成請求的響應,這是最簡單的請求。實際上,檢視函式有兩個作用:處理業務邏輯和返回響應內容。在大型應用中,把業務邏輯和表現內容放在一起,會增加程式碼的複雜度和維護成本。本節學到的模板,它的作用即是承擔檢視函式的另一個作用,即返回響應內容。 模板其實是一個包含響應文字的檔案,其中用佔位符(變數)表示動態部分,告訴模板引擎其具體值需要從使用的資料中獲取。使用真實值替換變數,再返回最終得到的字串,這個過程稱為“渲染”。Flask使用Jinja2這個模板引擎來渲染模板。Jinja2能識別所有型別的變數,包括{}。 Jinja2模板引擎,Flask提供的render_template函式封裝了該模板引擎,render_template函式的第一個引數是要渲染的模板的檔名,後面的引數可以是鍵值對,也可以是**kwargs,均能將變數對應的真實值傳遞給模板。。
我們先來認識下模板的基本語法:
{% if user %} {{ user }} {% else %} hello! <ul> {% for index in indexs %} <li> {{ index }} </li> {% endfor %} </ul>
一、變數
Jinja2 模版中的變數程式碼塊可以是任意 Python 型別或者物件,只要它能夠被 Python 的 str() 方法轉換為一個字串就可以。
檢視程式碼
from flask import Flask, render_template app= Flask(__name__) @app.route("/") def index(): """ "index.html"表示要渲染的模板 兩種傳遞引數值的方式: 1、鍵值對 2、不定長欄位引數**kwargs """ data = { "name2": "李四", "age2": 19 } my_dict = { "name": "王五", "age": 21 } my_list = [1, 2, 3, 4, 5, 6, 7] list_index= 1 return render_template("index.html", # 鍵值對 name1="張三", age1=18, # 不定長引數 **data, # 引數值除了可以是string和int型別外,還可以是dict,list等型別,只要這個型別能被 str() 方法轉換為一個字串就可以。 # dict型別 mydict=my_dict, # list型別 mylist=my_list, index=list_index ) if __name__ == '__main__': app.run()
模板程式碼
模板放在templates目錄下,名字為index.html。使用 {{ 引數名 }} 的方式進行插值操作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- string 和 int取值方式 --> <div>{{ name1 }}:{{ age1 }}</div> <div>{{ name2 }}:{{ age2 }}</div> <!-- dict取值方式 --> <div>{{ mydict }}</div> <div>{{ mydict["name"]}}:{{ mydict.age }}</div> <!-- list取值方式 --> <div>{{ mylist }}</div> <div>{{ mylist[0] }}</div> <div>{{ mylist[index] }}</div> </body> </html>
渲染效果
二、過濾器
過濾器的本質就是函式。有時候我們不僅僅只是需要輸出變數的值,我們還需要修改變數的顯示,甚至格式化、運算等等,而在模板中是不能直接呼叫 Python 中的某些方法,那麼這就用到了過濾器。
過濾器的使用方式為:變數名 | 過濾器。 過濾器名寫在變數名後面,中間用 | 分隔。如:{{variable | capitalize}},這個過濾器的作用:把變數variable的值的首字母轉換為大寫,其他字母轉換為小寫。
字串過濾器
safe:禁用轉義html標籤 <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>
列表過濾器
irst:取第一個元素 <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>
語句塊過濾(不常用)
{% filter upper %}
this is a Flask Jinja2 introduction
{% endfilter %}
自定義過濾器
過濾器的本質是函式。當模板內建的過濾器不能滿足需求,可以自定義過濾器。
自定義過濾器有兩種實現方式:
- 使用Flask應用物件的add_template_filter方法
- 使用Flask應用物件的template_filter裝飾器
注意,自定義的過濾器名稱如果和內建的過濾器重名,會覆蓋內建的過濾器。
實現方式一:通過呼叫應用程式例項的add_template_filter方法實現自定義過濾器。該方法第一個引數是函式名,第二個引數是自定義的過濾器名稱。
# 間隔擷取列表 def filter_double_sort(ls): return ls[::2] # 第一個引數為過濾器函式, # 第二個引數為模板中使用的過濾器名字) app.add_template_filter(filter_double_sort, 'double_2')
實現方式二:使用Flask應用物件的template_filter裝飾器實現自定義過濾器。裝飾器傳入的引數是自定義的過濾器名稱。
# 裝飾器中的引數為模板中使用的過濾器名字 @app.template_filter('db2') def filter_double_sort(ls): return ls[::2]
使用自定義過濾器
<p>add_template_filter函式:{{ mylist | double_2 }}</p> <p>template_filter裝飾器:{{ mylist | db2}}</p>
三、Flask-WTF表單擴充套件
在介紹Flask-WTF表單擴充套件前,我們先不使用Flask-WTF簡單實現一個登錄檔單:
模板檔案如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- form標籤不使用action屬性時,傳送的請求被當前檢視函式接收 --> <form method='post'> <div><input type="text" name="username" placeholder='使用者名稱'></div> <div><input type="password" name="password" placeholder='密碼'></div> <div><input type="password" name="password2" placeholder='確認密碼'></div> <input type="submit"> </form> </body> </html>
檢視函式如下:
from flask import Flask, render_template, request, session, redirect, url_for app = Flask(__name__) app.config["SECRET_KEY"] = "asd" @app.route("/login/") def login(): return F"{session.get('user')} 註冊成功" @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") session["user"] = username return redirect(url_for("login")) else: return render_template("register.html") if __name__ == '__main__': app.run()
測試效果:
上圖中,即使密碼與確認密碼不一致,但仍能註冊成功,這是因為我們沒有在檢視中驗證表單的資料,為了保證引數的合規性,我們需要對每個使用if來進行校驗,這是很繁瑣的。
而Flask-WTF擴充套件不僅可以幫助我們在檢視中驗證表單的資料,還可以使用該擴充套件進行CSRF驗證,幫助我們快速定義表單模板。
使用Flask-WTF表單擴充套件,需要自行安裝,安裝命令如下:
pip install flask-wtf
並且還需要配置引數SECRET_KEY,當CSRF(跨站請求偽造)保護啟用的時候,CSRF_ENABLED設定會根據設定的密匙生成加密令牌。
接下來,我們使用Flask-WTF實現表單。
模板檔案如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> <!-- 設定csrf_token --> {{ form.csrf_token }} {{ form.user_name.label }} <p>{{form.user_name}}</p> {% for msg in form.user_name.errors %} <p>{{msg}}</p> {% endfor %} {{ form.password.label }} <p>{{form.password}}</p> {% for msg in form.password.errors %} <p>{{msg}}</p> {% endfor %} {{ form.password2.label }} <p>{{form.password2}}</p> {% for msg in form.password2.errors %} <p>{{msg}}</p> {% endfor %} {{form.submit}} </form> </body> </html>
檢視函式如下:
from flask import Flask,render_template, redirect,url_for,session,request,flash # 匯入wtf擴充套件的表單類 from flask_wtf import FlaskForm # 匯入自定義表單需要的欄位 from wtforms import SubmitField, StringField, PasswordField # 匯入wtf擴充套件提供的表單驗證器的驗證函式 from wtforms.validators import DataRequired, EqualTo app = Flask(__name__) app.config['SECRET_KEY'] = 'asd' @app.route("/login/") def login(): return F"{session.get('user')} 註冊成功" # 定義登錄檔單類,該類繼承FlaskForm class RegisterForm(FlaskForm): # 欄位型別: # StringField 對應 type="text" # PasswordField 對應 type="password" # SubmitField 對應 type="submit" # 欄位引數說明: # label:欄位的名稱 # validators:驗證器 # DataRequired(message):驗證資料不能為空。message為校驗不通過的提示 # EqualTo(fieldname, message):驗證與指定欄位fieldname的值相等,message為校驗不通過的提示。 user_name = StringField(label=u"使用者名稱", validators=[DataRequired(u"使用者名稱不能為空")]) password = PasswordField(label=u"密碼", validators=[DataRequired(u"密碼不能為空")]) password2 = PasswordField(label=u"確認密碼", validators=[DataRequired(u"確認密碼不能為空"), EqualTo("password", u"兩次密碼不一致")]) submit = SubmitField(label=u"提交") # 定義根路由檢視函式,生成表單物件,獲取表單資料,進行表單資料驗證 @app.route('/register/', methods=['GET', 'POST']) def register(): # 建立表單物件, 如果是post請求,前端傳送了資料,flask會把資料在構造form物件的時候,存放到物件中 form = RegisterForm() # 判斷form中的資料是否合理 # 如果form中的資料完全滿足所有的驗證器,則返回True,否則返回False if form.validate_on_submit(): # 表示驗證合格 # 提取資料 uname = form.user_name.data pwd = form.password.data pwd2 = form.password2.data print(uname, pwd, pwd2) session["user"] = uname return redirect(url_for("login")) # 如果校驗為False,則返回模板資料 return render_template("register.html", form=form) if __name__ == '__main__': app.run(debug=True)
測試效果:
WTForms支援的HTML標準欄位
欄位物件 | 說明 |
---|---|
StringField | 文字欄位 |
TextAreaField | 多行文字欄位 |
PasswordField | 密碼文字欄位 |
HiddenField | 隱藏文字欄位 |
DateField | 文字欄位,值為datetime.date格式 |
DateTimeField | 文字欄位,值為datetime.datetime格式 |
IntegerField | 文字欄位,值為整數 |
DecimalField | 文字欄位,值為decimal.Decimal |
FloatField | 文字欄位,值為浮點數 |
BooleanField | 複選框,值為True和False |
RadioField | 一組單選框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可選擇多個值 |
FileField | 文字上傳欄位 |
SubmitField | 表單提交按鈕 |
FormField | 把表單作為欄位嵌入另一個表單 |
FieldList | 一組指定型別的欄位 |
WTForms常用驗證函式
驗證函式 | 說明 |
---|---|
DataRequired | 確保欄位中有資料 |
EqualTo | 比較兩個欄位的值,常用於比較兩次密碼輸入 |
Length | 驗證輸入的字串長度 |
NumberRange | 驗證輸入的值在數字範圍內 |
URL | 驗證URL |
AnyOf | 驗證輸入值在可選列表中 |
NoneOf | 驗證輸入值不在可選列表中 |
四、控制語句
模板中的if控制語句
@app.route('/user') def user(): user = 'flsk' return render_template('user.html',user=user)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% if user %} <h1> hello {{user}} </h1> {% else %} <h1> welcome to flask </h1> {% endif %} </body> </html>
模板中的for迴圈語句
@app.route('/loop') def loop(): fruit = ['apple','orange','pear','grape'] return render_template('loop.html',fruit=fruit)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for index in fruit %} <li>{{ index }}</li> {% endfor %} </ul> </body> </html>
五、巨集、繼承、包含
5.1 巨集
類似於python中的函式,巨集的作用就是在模板中重複利用程式碼,避免程式碼冗餘。
Jinja2支援巨集,還可以匯入巨集,需要在多處重複使用的模板程式碼片段可以寫入單獨的檔案,再包含在所有模板中,以避免重複。
定義巨集
{% macro input() %} <input type="text" name="username" value="" size="30"/> {% endmacro %}
呼叫巨集
{{ input() }}
定義帶引數的巨集
{% macro input(name,value='',type='text',size=20) %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}" size="{{ size }}"/> {% endmacro %}
呼叫巨集,並傳遞引數
{{ input(value='name',type='password',size=40)}}
把巨集單獨抽取出來,封裝成html檔案,其它模板中匯入使用
檔名可以自定義,比如:定義macro.html,檔案內程式碼如下
{% macro function() %} <input type="text" name="username" placeholde="Username"> <input type="password" name="password" placeholde="Password"> <input type="submit"> {% endmacro %}
在其它模板檔案中先匯入,再呼叫
{% import 'macro.html' as func %}
{% func.function() %}
5.2 繼承
模板繼承是為了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部選單、底部。這些內容可以定義在父模板中,子模板直接繼承,而不需要重複書寫。
{% block top %}``{% endblock %}
標籤定義的內容,相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。
子模板使用extends指令宣告這個模板繼承自哪?父模板中定義的塊在子模板中被重新定義,在子模板中呼叫父模板的內容可以使用super()。
父模板:base.html
{% block top %}
頂部選單
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
子模板:
{% extends 'base.html' %}
{% block content %}
需要填充的內容
{% endblock content %}
模板繼承使用時注意點:
- 不支援多繼承。
- 為了便於閱讀,在子模板中使用extends時,儘量寫在模板的第一行。
- 不能在一個模板檔案中定義多個相同名字的block標籤。
- 當在頁面中使用多個block標籤時,建議給結束標籤起個名字,當多個block巢狀時,閱讀性更好。
5.3 包含(Include)
Jinja2模板中,除了巨集和繼承,還支援一種程式碼重用的功能,叫包含(Include)。它的功能是將另一個模板整個載入到當前模板中,並直接渲染。
示例:include的使用
{\% include 'hello.html' %}
包含在使用時,如果包含的模板檔案不存在時,程式會丟擲TemplateNotFound異常,可以加上ignore missing關鍵字。如果包含的模板檔案不存在,會忽略這條include語句。
示例:include的使用加上關鍵字ignore missing
{\% include 'hello.html' ignore missing %}
5.4 巨集、繼承、包含
- 巨集(Macro)、繼承(Block)、包含(include)均能實現程式碼的複用。
- 繼承(Block)的本質是程式碼替換,一般用來實現多個頁面中重複不變的區域。
- 巨集(Macro)的功能類似函式,可以傳入引數,需要定義、呼叫。
- 包含(include)是直接將目標模板檔案整個渲染出來。