Flask入門很輕鬆(三)—— 模板

Sunzz發表於2019-06-04

Jinja2模板引擎

轉載請在文章開頭附上原文連結地址:https://www.cnblogs.com/Sunzz/p/10959471.html

Flask內建的模板語言,它的設計思想來源於 Django 的模板引擎,並擴充套件了其語法和一系列強大的功能。

渲染模版函式

  • Flask提供的 render_template 函式封裝了該模板引擎
  • render_template 函式的第一個引數是模板的檔名,後面的引數都是鍵值對,表示模板中變數對應的真實值。

模板基本使用

  1. 在檢視函式中設定渲染模板
from flask import Flask, render_template
@app.route('/')
def index():
    return render_template('index.html')
  1. 在專案下建立 templates 資料夾,用於存放所有的模板檔案,並在目錄下建立一個模板html檔案 index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我的模板html內容
</body>
</html>

{{}} 來表示變數名,這種 {{}} 語法叫做變數程式碼塊

檢視程式碼:

@app.route("/")
def index():
    title = "網頁標題"
    return render_template("index.html",title=title)

模板程式碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
    <h1>{{title}}</h1>
</body>
</html>

Jinja2 模版中的變數程式碼塊可以是任意 Python 型別或者物件,只要它能夠被 Python 的 str() 方法轉換為一個字串就可以,比如,可以通過下面的方式顯示一個字典或者列表中的某個元素:

{{your_dict['key']}}
{{your_list[0]}}

用 {%%} 定義的控制程式碼塊,可以實現一些語言層次的功能,比如迴圈或者if語句

{% if user %}
    {{ user }}
{% else %}
    hello!
<ul>
    {% for index in indexs %}
    <li> {{ index }} </li>
    {% endfor %}
</ul>

使用 {# #} 進行註釋,註釋的內容不會在html中被渲染出來

{# {{ name }} #}

模板中特有的變數和函式

你可以在自己的模板中訪問一些 Flask 預設內建的函式和物件

config

你可以從模板中直接訪問Flask當前的config物件:

{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db

request

就是flask中代表當前請求的request物件:

{{request.url}}
http://127.0.0.1

session

為Flask的session物件

{{session.new}}
True

g變數

在檢視函式中設定g變數的 name 屬性的值,然後在模板中直接可以取出

{{ g.name }}

url_for()

url_for會根據傳入的路由器函式名,返回該路由對應的URL,在模板中始終使用url_for()就可以安全的修改路由繫結的URL,則不比擔心模板中渲染出錯的連結:

{{url_for('home')}}

如果我們定義的路由URL是帶有引數的,則可以把它們作為關鍵字引數傳入url_for(),Flask會把他們填充進最終生成的URL中:

{{ url_for('post', post_id=1)}}
/post/1

流程控制

主要包含兩個:

- if/else if /else / endif
- for / endfor

if語句

Jinja2 語法中的if語句跟 Python 中的 if 語句相似,後面的布林值或返回布林值的表示式將決定程式碼中的哪個流程會被執行:

{%if user.is_logged_in() %}
    <a href='/logout'>Logout</a>
{% else %}
    <a href='/login'>Login</a>
{% endif %}

過濾器可以被用在 if 語句中:

{% if comments | length > 0 %}
    There are {{ comments | length }} comments
{% else %}
    There are no comments
{% endif %}

迴圈語句

  • 我們可以在 Jinja2 中使用迴圈來迭代任何列表或者生成器函式
{% for post in posts %}
    <div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text | safe }}</p>
    </div>
{% endfor %}
  • 迴圈和if語句可以組合使用,以模擬 Python 迴圈中的 continue 功能,下面這個迴圈將只會渲染post.text不為None的那些post:
{% for post in posts if post.text %}
    <div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text | safe }}</p>
    </div>
{% endfor %}
  • 在一個 for 迴圈塊中你可以訪問這些特殊的變數:
變數 描述
loop.index 當前迴圈迭代的次數(從 1 開始)
loop.index0 當前迴圈迭代的次數(從 0 開始)
loop.revindex 到迴圈結束需要迭代的次數(從 1 開始)
loop.revindex0 到迴圈結束需要迭代的次數(從 0 開始)
loop.first 如果是第一次迭代,為 True 。
loop.last 如果是最後一次迭代,為 True 。
loop.length 序列中的專案數。
loop.cycle 在一串序列間期取值的輔助函式。見下面示例程式。
  • 在迴圈內部,你可以使用一個叫做loop的特殊變數來獲得關於for迴圈的一些資訊
    • 比如:要是我們想知道當前被迭代的元素序號,並模擬Python中的enumerate函式做的事情,則可以使用loop變數的index屬性,例如:
{% for post in posts%}
{{loop.index}}, {{post.title}}
{% endfor %}
  • 會輸出這樣的結果
1, Post title
2, Second Post
  • cycle函式會在每次迴圈的時候,返回其引數中的下一個元素,可以拿上面的例子來說明:
{% for post in posts%}
{{loop.cycle('odd','even')}} {{post.title}}
{% endfor %}
  • 會輸出這樣的結果:
odd Post Title
even Second Post

過濾器

過濾器的本質就是函式。有時候我們不僅僅只是需要輸出變數的值,我們還需要修改變數的顯示,甚至格式化、運算等等,而在模板中是不能直接呼叫 Python 中的某些方法,那麼這就用到了過濾器。

使用方式:

  • 過濾器的使用方式為:變數名 | 過濾器。
{{variable | filter_name(*args)}}
  • 如果沒有任何引數傳給過濾器,則可以把括號省略掉
{{variable | filter_name }}
  • 如:``,這個過濾器的作用:把變數variable 的值的首字母轉換為大寫,其他字母轉換為小寫

在 jinja2 中,過濾器是可以支援鏈式呼叫的,示例如下:

{{ "hello world" | reverse | upper }}

常見的內建過濾器

字串操作

  • 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>

語句塊過濾

{% filter upper %}
    #一大堆文字#
{% endfilter %}

自定義過濾器

過濾器的本質是函式。當模板內建的過濾器不能滿足需求,可以自定義過濾器。自定義過濾器有兩種實現方式:

  • 一種是通過Flask應用物件的 add_template_filter 方法
  • 通過裝飾器來實現自定義過濾器

重要:自定義的過濾器名稱如果和內建的過濾器重名,會覆蓋內建的過濾器。

需求:新增列表反轉的過濾器

方式一

通過呼叫應用程式例項的 add_template_filter 方法實現自定義過濾器。該方法第一個引數是函式名,第二個引數是自定義的過濾器名稱:

def do_listreverse(li):
    # 通過原列表建立一個新列表
    temp_li = list(li)
    # 將新列表進行返轉
    temp_li.reverse()
    return temp_li

app.add_template_filter(do_listreverse,'lireverse')

方式二

用裝飾器來實現自定義過濾器。裝飾器傳入的引數是自定義的過濾器名稱。

@app.template_filter('lireverse')
def do_listreverse(li):
    # 通過原列表建立一個新列表
    temp_li = list(li)
    # 將新列表進行返轉
    temp_li.reverse()
    return temp_li
  • 在 html 中使用該自定義過濾器
<br/> my_array 原內容:{{ my_array }}
<br/> my_array 反轉:{{ my_array | lireverse }}
  • 執行結果
my_array 原內容:[3, 4, 2, 1, 7, 9] 
my_array 反轉:[9, 7, 1, 2, 4, 3]

模板繼承

在模板中,可能會遇到以下情況:

  • 多個模板具有完全相同的頂部和底部內容
  • 多個模板中具有相同的模板程式碼內容,但是內容中部分值不一樣
  • 多個模板中具有完全相同的 html 程式碼塊內容

像遇到這種情況,可以使用 JinJa2 模板中的 繼承 來進行實現

模板繼承是為了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部選單、底部。這些內容可以定義在父模板中,子模板直接繼承,而不需要重複書寫。

  • 標籤定義的內容
{% block top %} {% endblock %}
  • 相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。
  • 子模板使用 extends 指令宣告這個模板繼承自哪個模板
  • 父模板中定義的塊在子模板中被重新定義,在子模板中呼叫父模板的內容可以使用super()

父模板程式碼:

base.html

{% block top %}
  頂部選單
{% endblock top %}

{% block content %}
{% endblock content %}

{% block bottom %}
  底部
{% endblock bottom %}

子模板程式碼:

  • extends指令宣告這個模板繼承自哪
{% extends 'base.html' %}
{% block content %}
 需要填充的內容
{% endblock content %}

模板繼承使用時注意點:

  1. 不支援多繼承
  2. 為了便於閱讀,在子模板中使用extends時,儘量寫在模板的第一行。
  3. 不能在一個模板檔案中定義多個相同名字的block標籤。
  4. 當在頁面中使用多個block標籤時,建議給結束標籤起個名字,當多個block巢狀時,閱讀性更好。

程式碼

config.py

class Config(object):
    DEBUG = True
    SECRET_KEY = "abcccddgadsag"

main.py

from flask import Flask
from config import Config
from flask import render_template

app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)

from flask import session
@app.route("/set_session")
def set_session():
    session["username"] = 'request資料'
    return "ok"



from flask import g
@app.route("/")
def index():
    title = "網頁標題"
    dict1 = {"id":1,"username":"xiaoming","money":20.5}
    love  = ['睡覺','吹牛','敲程式碼']
    g.list1 = ['睡覺','吹牛','敲程式碼']
    g.book_list = [
        {"id":1,"name":"浪潮之巔","price":88.5},
        {"id":12,"name":"數學之美","price":68.5},
        {"id":12,"name":"數學之美","price":68.5},
        {"id":12,"name":"數學之美","price":68.5},
        {"id":13,"name":"矽谷之謎","price":108.533333},
        {"id":14,"name":"五年高考三年模擬","price":78.5},
    ]

    g.title2 = '<script>alert("大標題")</script>'
    g.question = '如果x=10,x<y,y>z,求z的取值範圍?'

    return render_template("index.html",title=title,dict1=dict1,love=love)

"""自定義過濾器"""

def rev(data):
    data.reverse()
    return data

app.add_template_filter(rev,'myrev')

@app.route("/filter")
def myfilter():

    g.list1 = ['睡覺', '吹牛', '敲程式碼']

    return render_template("f.html")

if __name__ == '__main__':
    app.run()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
    <h1>{{title}}</h1>
    <table border="1" width="400">
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>存款</th>
        </tr>
        <tr>
            <td>{{ dict1["id"] }}</td>
            <td>{{ dict1["username"] }}</td>
            <td>{{ dict1["money"] }}</td>
        </tr>
        <tr>
            <td>{{ dict1.id }}</td>
            <td>{{ dict1.username }}</td>
            <td>{{ dict1.money }}</td>
        </tr>
    </table>
    <ul>
        <li>{{ love.0 }}</li>
        <li>{{ love.1 }}</li>
        <li>{{ love.2 }}</li>

        <li>{{ love[0] }}</li>
        <li>{{ love[1] }}</li>
        <li>{{ love[2] }}</li>

        {# for迴圈也支援多層的巢狀或者其他語句 #}
        {% for item in love %}
        <li>{{ item }}</li>
        {% endfor %}

    </ul>

    {# 這裡是if判斷,if還可以支援多個條件的判斷 #}
    {% if dict1.money < 20 %}
        <p>存款太少了,需要充值</p>
    {% endif %}

    {# 特殊變數 #}
    {# 支援直接讀取配置資訊 #}
    <p>{{ config.SECRET_KEY }}</p>

    {# 支援獲取請求資訊 #}
    <p>{{ request.method }}</p>

    {# 支援獲取session資料 #}
    <p>{{ session.username }}</p>

    {# 支援獲取g變數 #}
    <p>{{ g.list1 }}</p>

    {# 支援使用url_for生成地址 #}
    <a href="{{ url_for("set_session") }}">跳轉到sesion</a>

    {# for迴圈中還內建了迴圈物件loop,可以操作迴圈過程中的索引 #}
    <table border="1" width="600">
        <tr>
            <th>序號</th>
            <th>ID</th>
            <th>書名</th>
            <th>價格</th>
        </tr>
        {% for book in g.book_list %}
        {% if loop.first %}
        <tr style="background-color: orange;">
        {% elif loop.last %}
        <tr style="background-color: blue;color: #fff;">
        {% else %}
        <tr>
        {% endif %}
            <td>{{ loop.revindex }}</td>
            <td>{{ "%03d" % book.id  }}</td>
            <td>{{ book.name }}</td>
            <td>{{ book.price }}</td>
        </tr>
        {% endfor %}
    </table>

    {# 過濾器 #}
    {{ dict1.username | upper | reverse }}

    {#
       預設情況下, flask會自動針對html標籤進行實體化轉移,目的時為了防止xss攻擊
       但是,我們後端也會有一些包含樣式的內容要輸出頁面中,此時可以使用 safe 過濾器
     #}
    {{ g.title2 | safe }}

    {# 這個過濾器會直接刪除html標籤,也是為了防止xss攻擊,但是這個過濾器慎用,在遇到數學公式的時候,會誤傷. #}
    {{ g.title2 | striptags }}
    {{ g.question | striptags }}


    {# 字元長度擷取 #}
    <p>{{ 'hello every one' | truncate(9)}}</p>

{% filter upper %}
dasdasd
dsa
das
ds
ad
<p>{{ 'hello every one' | truncate(9)}}</p>
asd
asdas
{% endfilter %}

{{ "hwello" }}

</body>
</html>

在 Flask 專案中解決 CSRF 攻擊

在 Flask 中, Flask-wtf 擴充套件有一套完善的 csrf 防護體系,對於我們開發者來說,使用起來非常簡單

1 設定應用程式的 secret_key,用於加密生成的 csrf_token 的值

# session加密的時候已經配置過了.如果沒有在配置項中設定,則如下:
app.secret_key = "#此處可以寫隨機字串#"

2 匯入 flask_wtf.csrf 中的 CSRFProtect 類,進行初始化,並在初始化的時候關聯 app

from flask.ext.wtf import CSRFProtect
CSRFProtect(app)

3 在表單中使用 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

相關文章