1. 程式人生 > 實用技巧 >Flask_Jinja2模板(九)

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)是直接將目標模板檔案整個渲染出來。