# 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 %} &copy; 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> ```