# Python jinja2 + flask
###### tags: `python` `jinja2` `flask`
jinja2是flask預載的樣板語法來源,核心開發人員跟flask是相同的。
開發網頁上是不可多得的得力幫手!
畢竟開發的時候不的希望在flask的view中return寫一堆html語法。
:::info
預設上,jinja2會自動從flask專案中的templates資料夾尋找相對應的html文檔
:::
## 基礎
模板內包含了變數跟表達式,而表達式的表示的部份也有區隔。
### 變數
jinja2的變數來源,可以是直接結合python格式,透過view來傳遞。
* pyView
```python
@app.route('/<name>'):
def index(name):
return render_template('user.html', name=name)
```
* user.html
```htmlembedded=
<p>
i am {{ name }}
</p>
```
不僅字串可以傳遞,如dict、list、甚至是method都可以傳遞。
```htmlmixed=
<p>
dict:{{ dict['keys']}}
</p>
<p>
lsit:{{ list[0] }}
</p>
<p>
method:{{ obj.method() }}
</p>
```
並且可以配合過濾器來做加工
```htmlmixed=
<p>
name = {{ name|upper }}
</p>
```
上面的範例就是透過過濾器來將字母轉大寫
### 過濾器
變數搭配過濾器可以做顯示上的一些調控,或是傳值上的一些調控。
比如說有些報表的參數只吃大寫字母之類的。
而且不限搭一個
```htmlmixed=
<p>
name = {{ name|upper|striptags }}
</p>
```
上面的範例我們除了做大寫之外,還將html的標籤拿掉。
#### 過濾器清單
| 過濾器 | 說明 | 備註 |
| -------- | -------- | -------- |
| abs(number) | 絕對值 | |
|attr(obj,name)|取得物件屬性|foo|attr('bar')取得foo的bar屬性|
|batch(value,linecount,fill_with=None) |||
|capitalize (s) |第一個字母轉大寫,其餘為小寫||
|center(value,width=80)|置中||
|default(value,default_value=u'',boolean=False)|如果為undefined,就回給予預設置,否則維持原變數的值||
|dictsort(value,case_sensitive=False,by='key')|排序dict,因為dict本身無序||
|escape(s)|||
|filesizeformat(value,binary=False)|||
|first(seq)|回傳序列的第一個項目||
|float(value,default=0.0)|格式轉為浮點數,若轉換失敗則回傳default||
|forceescape(value)|||
|format(value,*args,**kwargs)|如python的字串格式化應用||
|groupby(value,attribute)|||
|indent(s,width=4,indentfirst=False)|||
|int(value,default=0)|格式轉為整數,若轉換失敗則回傳default||
|join(value,d=u'',attribute=None)|如python的join般串接字串|參數d是串接符號設置|
|last(seq)|回傳序列的最後一個項目||
|length(object)|回傳序列內的數量||
|list(value|將值轉list||
|lower(s)|將字母轉小寫||
|map()|||
|pprint(value,verbose=False|漂亮的列印出資料,用於測試||
|random(seq)|從序列中隨機返回一個項目||
|reject()|||
|rejectattr()|||
|replace(s, old, new, count=None)|字串替換||
|reverse(value)|||
|round(value, precision=0, method='common')|四捨五入|method有三種可參閱官方說明|
|safe(value)|信任來源資料||
|select()|||
|selectattr()|||
|slice(value, slices, fill_with=None)|||
|sort(value, reverse=False, case_sensitive=False, attribute=None)|排序迭代器|可調控由大至小或由小至大|
|string(object)|||
|striptags(value)|||
|sum(iterable, attribute=None, start=0)|返回一系列加上start值的數列||
|title(s)|||
|trim(value)|去頭尾空白||
|truncate(s, length=255, killwords=False, end='...')|截斷字串,由length控制長度||
|upper(s)|字串轉大寫||
|urlencode(value)|轉url編碼||
|urlize(value, trim_url_limit=None, nofollow=False)|將純文字的url轉可按連結||
|wordcount(s)|計算字串總字數||
|wordwrap(s, width=79, break_long_words=True, wrapstring=None)|||
|xmlattr(d, autospace=True)|依dict項目生成sgml/xml屬性字串符號||
## 測試
在jinja2中,除了過濾器之外,也還可以透過測試的方式來確認相關變數是否正常傳遞。
```python
{% if loop.index is divisibleby 3 %}
```
:::info
name is defined
:::
### 測試清單
| 測試模式 | 說明 | 備註 |
| -------- | -------- | -------- |
|callable(object)|回傳物件是否可以調用||
|defined(value)|如果變數已宣告,回傳true||
|divisibleby(value, num)|檢查是否可以變整除||
|escaped(value)|檢查是否被轉譯||
|even(value)|若為偶數,回傳true||
|iterable(value)|檢查是否可被迭代||
|lower(value)|若為小寫,回傳true||
|mapping(value)|若為dict,回傳true||
|none(value)|若為none,回傳true||
|number(value)|若為數值,回傳true||
|odd(value)|若為奇數,回傳true||
|sameas(value, other)|檢查兩物件是否指向相同記憶體位址||
|sequence(value)|若為序列,回傳true||
|string(value)|若為字串,回傳true||
|undefined(value)|若為undefined,回傳true||
|upper(value)|若為大寫,回傳true||
## 註解的寫法
{# 我的註解 #}
```python
{# note: disabled template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}
```
## 空白控制
預設空白的部份不會有任何的修正,但如果在表示式中加入『-』就會移除
```python
{% for item in seq -%}
{{ item }}
{%- endfor %}
```
如果seq是1到9,那就會產出123456789,中間不帶任何空白。
:::info
tag跟-之間是不允許空格,否則空白的控制是會失效的。
:::
:::warning
```python
# 有效的:
{%- if foo -%}...{% endif %}
# 无效的:
{% - if foo - %}...{% endif %}
```
:::
## 行語句
使用了行語句,就可以透過一個tag來代表表達式。
```python
<ul>
# for item in seq
<li>{{ item }}</li>
# endfor
</ul>
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
```
## 樣板繼承
jinja2中最好用的部份就是樣板的繼承。
好處就是不用寫太多重覆性的東西。
透過block的調整即可。
直接看官方的範例!
### 樣板繼承範例
* base.html
```htmlmixed=
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
```
上面的部份我們定義為基板,其中會發現有幾個表達式
{% block head%}...{% endblock head%}
這個block就是讓我們可以操作的區塊。
* i_base_1
```htmlmixed=
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}
```
可以看到在一開始的時候我們宣告了一段
:::info
```htmlmixed=
{% extends "base.html" %}
```
:::
這段即宣告樣板繼承自base.html
接著,針對了block_title的部份設置為index,也保留了block_head。
這樣子的作法另一個好處大概就是分離關注點吧?
:::warning
每個block的name是唯一的。
:::
### 保留樣板block內容
剛才的案例,關於block_head的部份提到可以保留。
靠的就是在裡面插入一個表達式來繼承母板表達式內的內容
:::info
{{ super() }}
:::
```htmlmixed=
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
```
## 轉譯
預設情況下,jinja2會做自動轉譯
轉譯的意思是什麼?
舉例來說
```python
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello/<name>')
def hello(name=None):
if name is not None:
name = '<em>Yes Man</em>'
return render_template('hello.html', name=name)
```
```htmlmixed=
<h1>Hello {{ name }}!</h1>
```
在時候,在網址上填入『http://127.0.0.1:5000/hello/go』
這時候,網頁上所顯示的是 Hello <em>Yes Man</em>
![](https://i.imgur.com/nSM5LjB.png)
這就是jinja2將所拋過來的資料做了轉譯,預防html被注入!
如果不想被自動轉譯的話,就可以加上表達式。
```htmlmixed=
{% autoescape false %}
<h1>Hello {{ name }}!</h1>
{% endautoescape %}
```
## 表達式
### for
for的表達式跟pytho用法相似,但在jinja2的部份有支援else。
在整個for走完的時候就會進入else內。
#### dict範例
```htmlmixed=
<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
```
#### list範例
```htmlmixed=
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
```
在jinja2的迭代中無法break,所以只能以透過過濾來處理。
```htmlmixed=
{% for user in users if not user.hidden %}
<li>{{ user.username|e }}</li>
{% endfor %}
```
#### 特殊變數
在for的期間有些特殊的變數是可以取得的
|變數| 描述|
|-----|-----|
|loop.index| 目前迭代的次數(從1開始)|
|loop.index0 |目前迭代的次數(從0開始)|
|loop.revindex |到迭代結束還需要的次數(從1開始|
|loop.revindex0 |到迭代結束還需要的次數(從0開始|
|loop.first| 如果是第一次迭代,則為True。|
|loop.last| 如果是最後一次迭代,則為True。|
|loop.length| 序列中的項目數。|
|loop.cycle| 在一串序列間取值的輔助函數。|
### if
用法上跟python是一致的
```htmlmixed=
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
```
### macro_宏
一個類似function的作法!
#### macro_導入
實務上會將macro寫在一個html內,需要的時候再import就可以了。
```htmlmixed=
{% import 'form.html' as form %}
<p>{{ form.input('input1', value='user') }}</p>
<p>{{ form.input('input2', 'password') }}</p>
```
也可以跟python一樣的作法
```htmlmixed=
{% from 'form.html' import input %}
<p>{{ input('input1', value='user') }}</p>
<p>{{ input('input2', 'password') }}</p>
```
#### macro_範例
```htmlmixed=
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}">
{%- endmacro %}
```
以macro開始,以endmacro來結束。
input是它的名稱。
使用方式如下
```htmlmixed=
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
```
當你去透過macro input來做渲染的時候,就會生成一個input的元件在你的html上,並且value預設為空,type為text。
看下面的password,type的部份是可以再調整的,此例即調整為password。
這麼做的好處在於,你要建置一個input的時候不需要<input ..xxx>的輸入一堆,而僅僅是透過macro來宣告你要的input的name、value、type即可。
也算是一種優雅!
![](https://i.imgur.com/A95SK4f.png)
![](https://i.imgur.com/QHaXwbX.png)
#### macro caller
caller的部份,可以想成是丟一個參數給macro去生成。
怎麼說,看下面的例子!
一樣的,我們產生一個macro,並且在後面的部份加了一個{{ caller() }}的表達式。
```htmlmixed=
{% macro list_users(users) -%}
<table>
<tr><th>Name</th><th>Memo</th></tr>
{%- for user in users %}
<tr><td>{{ user.name |e }}</td>{{ caller() }}</tr>
{%- endfor %}
</table>
{%- endmacro %}
```
接著,我們透過set來設置一些資料
```htmlmixed=
{% set users=[{'name':'Tomcat'},
{'name':'Johndog'},
{'name':'Marybird'}]
%}
```
使用macro
```htmlmixed=
{% call list_users(users) %}
<td><input name="Call" type="button" value="Caller"></td>
{% endcall %}
```
在渲染網頁的時候,後面的caller就會被call內的元件給取代掉
![](https://i.imgur.com/aCqbD8A.png)
所以在迭代的同時,也將每個欄位都加入一個button。
#### macro_call_帶參數
```htmlmixed=
{% macro render_dialog(title, class='dialog') -%}
<div class="{{ class }}">
<h2>{{ title }}</h2>
<div class="contents">
{{ caller() }}
</div>
</div>
{%- endmacro %}
{% call render_dialog('Hello World') %}
This is a simple dialog rendered by using a macro and
a call block.
{% endcall %}
```
![](https://i.imgur.com/L6Extfj.png)
可以看到,title的部份是以Hello World去帶入。
### 過濾_filter
```htmlmixed=
{% filter upper %}
This text becomes uppercase
{% endfilter %}
```
### 賦值_set
```htmlmixed=
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}
```
## include
不同於extend的繼承方式,include的話就是把你include過來的網頁直接整個渲染在設置的位置上。
```htmlmixed=
<body>
...
{% include 'footer.html' %}
</body>
```
一個問題即是,如果沒有這個來源檔案會報錯。
所以在設置上可以透過igonre來調適。
```htmlmixed=
{% include 'footer.html' ignore missing %}
```
上面案例的意思即是如果沒有就放棄引入渲染。
```htmlmixed=
{% include 'footer.html','footer2.html','footer3.html'] ignore missing %}
```
include也可以多板,以上例來看,沒有footer就footer2...
## 上下文變量與函數使用
### request
[官方文件_request method](http://flask.pocoo.org/docs/0.10/api/#incoming-request-data)
利用表達式的方式,也可以取得請求端的資訊!
```htmlmixed=
{{ request.url }}
```
更多的應用可以參閱官方文件說明
### session
session是一個dict性質的物件。
```htmlmixed=
{{ session.Count }}
```
舉例來說
```python
from flask import Flask, render_template, session
app = Flask(__name__)
@app.route('/ddd')
def index():
session['Count'] = 'admin'
return render_template('hello.html')
# Key的部份在正式環境上會置於instanceos\config內
app.secret_key = '77'
```
```htmlmixed=
<p>{{ request.url }}</p>
<p>{{ session['Count'] }}</p>
```
或是
```htmlmixed=
<p>{{ request.url }}</p>
<p>{{ session.Count }}</p>
```
### g
g的部份本身用來記錄全域變數。
在後端所定義的g,前端也是可以呼叫使用。
```python
from flask import Flask, render_template, g
@app.route('/ccc')
def indexddd():
g.db = 'mysql'
return render_template('hello.html')
```
```htmlmixed=
<p>db: {{ g.db }}</p>
```
### config
對於flask的config的部份,本身亦為全域變數。
故在前端也可以調用。
```htmlmixed=
{{ config.DEBUG }}
```
## 自定義
### 自定義變數
待理解
### 自定義函數
透過裝飾器context_processor可以自定義函數提供前端調用。
```python
from datetime import datetime
@app.context_processor
def get_current_time():
def get_time():
return datetime.now()
```
```htmlmixed=
<p>Now Time is: {{ current_time() }}</p>
```