Jinja2模板引擎
轉載請在文章開頭附上原文鏈接地址:https://www.cnblogs.com/Sunzz/p/10959471.html
Flask內置的模板語言,它的設計思想來源於 Django 的模板引擎,並擴展了其語法和一系列強大的功能。
渲染模版函數
- Flask提供的 render_template 函數封裝了該模板引擎
- render_template 函數的第一個參數是模板的文件名,后面的參數都是鍵值對,表示模板中變量對應的真實值。
模板基本使用
- 在視圖函數中設置渲染模板
from flask import Flask, render_template
@app.route('/')
def index():
return render_template('index.html')
- 在項目下創建
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 %}
模板繼承使用時注意點:
- 不支持多繼承
- 為了便於閱讀,在子模板中使用extends時,盡量寫在模板的第一行。
- 不能在一個模板文件中定義多個相同名字的block標簽。
- 當在頁面中使用多個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>