1. 程式人生 > >Flask修煉-模板-02!

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 流程:

  1. 客戶端瀏覽並登入信任網站 A
  2. 驗證通過,在使用者處產生 A 的 cookie
  3. 使用者在沒有登出的情況下訪問攻擊網站 B
  4. B 會用一個誘惑性的按鈕來讓使用者點選,但是這個點選會要求訪問第三方網站 A ,發出一個請求
  5. 根據 B 的請求,瀏覽器要去訪問 A 網站,訪問 A 網站會預設帶上之前儲存的 cookie
  6. A 網站不知道這個請求是使用者發出的還是 B 攻擊網站發出的,但是瀏覽器過去的時候帶上了 cookie,所以 A 會根據使用者的許可權處理這個請求,B 攻擊網站的請求裡一般會有請求,這樣 B 就達到了模擬使用者操作進行攻擊的目的
防止 CSRF 攻擊
  1. 在客戶端向後端請求介面資料的時候,後端會往響應中的 cookie 中設定 csrf_token 的值
  2. 在 Form 表單中新增一個隱藏的的欄位,值也是 csrf_token
  3. 在使用者點選提交的時候,會帶上這兩個值向後臺發起請求
  4. 後端接受到請求,以會以下幾件事件:
    • 從 cookie中取出 csrf_token
    • 從 表單資料中取出來隱藏的 csrf_token 的值
    • 進行對比
  5. 如果比較之後兩值一樣,那麼代表是正常的請求,如果沒取到或者比較不一樣,代表不是正常的請求,不執行下一步操作