天天看點

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') }}" 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

流程:
  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. 如果比較之後兩值一樣,那麼代表是正常的請求,如果沒取到或者比較不一樣,代表不是正常的請求,不執行下一步操作

繼續閱讀