Flask之模版(2)

Flask默認使用Jinja2作爲模版引擎。模版引擎包含了變量和表達式,當模版被渲染時,它們被替換爲值和標籤,它們控制着模版的邏輯。下面主要回顧模版的渲染使用,以及Jinja2的語法。

1 模版渲染

1.1 目錄結構

Flask 會在 templates 文件夾裏尋找模板。所以,如果你的應用是個模塊,這個文件夾應該與模塊同級;如果它是一個包,那麼這個文件夾作爲包的子目錄:
場景1: 模塊
application文件應該與templates目錄同級

/application.py
/templates
    /hello.html

場景2: 包
templates目錄作爲application的子目錄

/application
    /__init__.py
    /templates
        /hello.html

1.2 渲染

Flask使用 render_template() 方法來渲染模板。渲染時框架會自動尋找templates目錄下的網頁文件,不必添加"templates"這個路徑。
render_template()方法第一個參數爲html文件名,後續參數爲模版文件中的變量名,可支持字典類型變量。
在模板裏,也可以訪問 request 、 session 和 g 對象, 以及 get_flashed_messages() 函數。

用法示例
文件目錄結構:
在這裏插入圖片描述
html文件user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask首頁</title>
</head>
<body>
    {% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello world!</h1>
{% endif %}
</body>
</html>

模版渲染代碼manager.py:

from flask import Flask, render_template
app = Flask(__name__)

@app.route("/user/")
@app.route("/user/<name>")
def index(name=None):
   return render_template("user.html", name=name)

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

在瀏覽器中輸入URL:http://127.0.0.1:5000/user/bruce
頁面顯示如下
在這裏插入圖片描述

1.3 字典類型變量

當模版中變量比較多時,可以直接使用字典類型變量。
修改user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask首頁</title>
</head>
<body>
    {% if name %}
  <h1>Hello {{ name }}!</h1>
    <h2>user age: {{age}}, phone: {{phone}}!</h2>
{% else %}
  <h1>Hello world!</h1>
{% endif %}
</body>
</html>

修改渲染代碼:

from flask import Flask, render_template
app = Flask(__name__)

@app.route("/user/")
@app.route("/user/<name>")
def index(name=None):
    info = {
        "name": name,
        "age": 33,
        "phone": 123456789
    }
    return render_template("user.html", **info)

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

瀏覽器輸入URL:http://127.0.0.1:5000/user/bruce.xu
在這裏插入圖片描述

2 Flask變量傳遞

Jinja 2 默認配置如下:
所有擴展名爲 .html 、 .htm 、 .xml 以及 .xhtml 的模板會開啓自動轉義
模板可以利用 {% autoescape %} 標籤選擇自動轉義的開關。
Flask 在 Jinja2 上下文中插入了幾個全局函數和助手,另外還有一些目前默認的值。

Flask全局變量
下面的全局變量默認在 Jinja2 模板中可用

變量 說明
config 當前的配置對象 (flask.config)
request 當前的請求對象 (flask.request)。當模版不是在活動的請求上下文中渲染時這個變量不可用。
session 當前的會話對象 (flask.session)。當模版不是在活動的請求上下文中渲染時這個變量不可用。
g 請求相關的全局變量 (flask.g)。當模版不是在活動的請求上下文中渲染時這個變量不可用。
url_for() flask.url_for() 函數
get_flashed_messages() flask.get_flashed_messages() 函數

(1)請求對象request
request對象可以用來獲取請求的方法”request.method”,表單”request.form”,請求的參數”request.args”,請求地址”request.url”等。它本身是一個字典。在模板中,你一樣可以獲取這些內容,只要用表達式符號”{{ }}”括起來即可。

<p>{{request.url}}</p>

(2)會話對象session
session對象可以用來獲取當前會話中保存的狀態,它本身是一個字典。在模板中,你可以用表達式符號”{{ }}”來獲取這個對象。
在request對象中設置session變量

@app.route('/')
def index():
    session['user']='guest'
    returnrender_template('hello.html')
 
app.secret_key='123456'

然後在html中可以使用session變量

<p>User:{{session.user}}</p>

(3)全局對象g
全局變量g,用來保存請求中會用到全局內容,比如數據庫連接。模板中也可以訪問。
g對象是保存在應用上下文環境中的,也只在一個請求生命週期內有效。在沒有應用上下文的環境中,這個對象不可用。

Flask配置對象config

@app.route('/')
def index():
    g.db='mysql'
    returnrender_template('hello.html')

在模版中使用:

<p>DB:{{g.db}}</p>

(4)Flask配置對象config
導入的配置信息,就保存在”app.config”對象中。這個配置對象在模板中也可以訪問。
“config”是全局對象,離開了請求生命週期也可以訪問。

<p>Host:{{config.DEBUG}}</p>

Flask自定義變量
除了Flask提供的標準上下文變量和函數,我們還可以自己定義。

from flask import current_app
 
@app.context_processor
def appinfo():
    return dict(appname=current_app.name)

函數返回的是一個字典,裏面有一個屬性”appname”,值爲當前應用的名稱。我們曾經介紹過,這裏的”current_app”對象是一個定義在應用上下文中的代理。函數用”@app.context_processor”裝飾器修飾,它是一個上下文處理器,它的作用是在模板被渲染前運行其所修飾的函數,並將函數返回的字典導入到模板上下文環境中,與模板上下文合併。然後,在模板中”appname”就如同上節介紹的”request”, “session”一樣,成爲了可訪問的上下文對象。我們可以在模板中將其輸出:

<p>Current App is:{{appname}}</p>

Flask自定義函數
同理我們可以自定義上下文函數,只需將上例中返回字典的屬性指向一個函數即可,下面我們就來定義一個上下文函數來獲取系統當前時間:

import time
@app.context_processor
def get_current_time():
    def get_time(timeFormat="%b %d, %Y - %H:%M:%S"):
        return time.strftime(timeFormat)
    return dict(current_time=get_time)

在模版中使用:

<p>Current Time is:{{current_time()}}</p>
<p>Current Day is:{{current_time("%Y-%m-%d")}}</p>

上下文處理器
Flask 上下文處理器自動向模板的上下文中插入新變量。上下文處理器在模板渲染之前運行,並且可以在模板上下文中插入新值。上下文處理器是一個返回字典的函數,這個字典的鍵值最終將傳入應用中所有模板的上下文:

@app.context_processor
def inject_user():
    return dict(user=g.user)

上面的上下文處理器使得模板可以使用一個名爲 user ,值爲 g.user 的變量。 不過這個例子不是很有意思,因爲 g 在模板中本來就是可用的,但它解釋了上下文處理器是如何工作的。
變量不僅限於值,上下文處理器也可以使某個函數在模板中可用(由於 Python 允許傳遞函數):

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}.format(amount, currency)
    return dict(format_price=format_price)

上面的上下文處理器使得 format_price 函數在所有模板中可用:

{{ format_price(0.33) }}

標準過濾器
過濾器的本質就是函數。有時候我們不僅僅只是需要輸出變量的值,我們還需要修改變量的顯示,甚至格式化、運算等等,這就用到了過濾器。 過濾器的使用方式爲:變量名 | 過濾器。 過濾器名寫在變量名後面,中間用 | 分隔。如:{{variable | capitalize}},這個過濾器的作用:把變量variable的值的首字母轉換爲大寫,其他字母轉換爲小寫。
這些過濾器在 Jinja2 中可用,也是 Jinja2 自帶的過濾器:
tojson()
這個函數把給定的對象轉換爲 JSON 表示,如果你要動態生成 JavaScript 這裏有一個非常有用的例子。
注意 script 標籤裏的東西不應該被轉義,因此如果你想在 script 標籤裏使用它, 請使用 |safe 來禁用轉義,:

<script type=text/javascript>
    doSomethingWith({{ user.username|tojson|safe }});
</script>

常用過濾器
字符串操作:
safe:禁用轉義;

{{ 'hello' | safe }}

capitalize:把變量值的首字母轉成大寫,其餘字母轉小寫;

{{ 'hello' | capitalize }}

lower:把值轉成小寫;

{{ 'HELLO' | lower }}

upper:把值轉成大寫;

{{ 'hello' | upper }}

title:把值中的每個單詞的首字母都轉成大寫;

{{ 'hello' | title }}

trim:把值的首尾空格去掉;

{{ ' hello world ' | trim }}

reverse:字符串反轉;

{{ 'olleh' | reverse }}

format:格式化輸出;

{{ '%s is %d' | format('name',17) }}

striptags:渲染之前把值中所有的HTML標籤都刪掉;

{{ 'hello' | striptags }}

列表操作
first:取第一個元素

{{ [1,2,3,4,5,6] | first }}

last:取最後一個元素

{{ [1,2,3,4,5,6] | last }}

length:獲取列表長度

{{ [1,2,3,4,5,6] | length }}

sum:列表求和

{{ [1,2,3,4,5,6] | sum }}

sort:列表排序

{{ [6,2,3,1,5,4] | sort }}

控制自動轉義
自動轉義的概念是自動轉義特殊字符。 HTML (或 XML ,因此也有 XHTML )意義下的特殊字符是 & , > , < , " 以及 ’ 。因爲這些字符在文檔中表示它們特定的含義,如果你想在文本中使用它們,應該把它們替換成相應的“實體”。不這麼做不僅會導致用戶疲於在文本中使用這些字符,也會導致安全問題。
雖然你有時會需要在模板中禁用自動轉義,比如在頁面中顯式地插入 HTML , 可以是一個來自於 markdown 到 HTML 轉換器的安全輸出。
有三種可行的解決方案:

  • 在傳遞到模板之前,用 Markup 對象封裝 HTML字符串。一般推薦這個方法。
  • 在模板中,使用 |safe 過濾器顯式地標記一個字符串爲安全的 HTML ( {{ myvariable|safe }} )。
  • 臨時地完全禁用自動轉義系統。
  • 在模板中禁用自動轉義系統,可以使用 {%autoescape %} 塊:
{% autoescape false %}
    <p>autoescaping is disabled here
    <p>{{ will_not_be_escaped }}
{% endautoescape %}

註冊過濾器
如果你要在 Jinja2 中註冊你自己的過濾器,你有兩種方法。你可以把它們手動添加到應用的 jinja_env 或者使用 template_filter() 裝飾器。
下面兩個例子作用相同,都是反轉一個對象:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

在使用裝飾器的情況下,如果你想以函數名作爲過濾器名,參數是可選的。註冊之後, 你可以在模板中像使用 Jinja2 內置過濾器一樣使用你的過濾器,例如你在上下文中有一個名爲 mylist 的 Python 列表:

{% for x in mylist | reverse %}
{% endfor %}

3 Jinja2模版語法

3.1 模版內部變量

可以使用點( . )來訪問變量的屬性,作爲替代,也可以使用所謂的“下標”語 法( [])。下面的幾行效果是一樣的:

{{ foo.bar }}
{{ foo['bar'] }}

如果你訪問標籤 裏的不帶括號的變量。
如果變量或屬性不存在,會返回一個未定義值。你可以對這類值做什麼取決於應用的配 置,默認的行爲是它如果被打印,其求值爲一個空字符串,並且你可以迭代它,但其它 操作會失敗。

爲方便起見,Jinja2 中 foo.bar 在 Python 層中做下面的事情:
檢查 foo 上是否有一個名爲 bar 的屬性。
如果沒有,檢查 foo 中是否有一個 ‘bar’ 項 。
如果沒有,返回一個未定義對象。

foo[‘bar’] 的方式相反,只在順序上有細小差異:
檢查在 foo 中是否有一個 ‘bar’ 項。
如果沒有,檢查 foo 上是否有一個名爲 bar 的屬性。
如果沒有,返回一個未定義對象。
如果一個對象有同名的項和屬性,這很重要。此外,有一個 attr() 過濾 器,它只查找屬性。

3.2 註釋和空白控制

註釋
{# … #} 對於未包含在模板輸出中的註釋
#。。。##註釋行語句

空白控制
默認配置中,模板引擎不會對空白做進一步修改,所以每個空白(空格、製表符、換行符 等等)都會原封不動返回。如果應用配置了 Jinja 的 trim_blocks ,模板標籤後的 第一個換行符會被自動移除。
此外,你也可以手動剝離模板中的空白。當你在塊(比如一個 for 標籤、一段註釋或變 量表達式)的開始或結束放置一個減號( - ),可以移除塊前或塊後的空白:

{% for item in seq -%}
    {{ item }}
{%- endfor %}

這會產出中間不帶空白的所有元素。如果 seq 是 1 到 9 的數字的列表, 輸出會是123456789
如果開啓了 行語句 ,它們會自動去除行首的空白。
注意:標籤和減號之間不能有空白。
有效的:

{%- if foo -%}...{% endif %}

轉義
有時想要或甚至必要讓 Jinja 忽略部分,不會把它作爲變量或塊來處理。例如,如果 使用默認語法,你想在在使用把 {{ 作爲原始字符串使用,並且不會開始一個變量 的語法結構,你需要使用一個技巧。
最簡單的方法是在變量分隔符中( {{ )使用變量表達式輸出:

{{ '{{' }}

對於較大的段落,標記一個塊爲 raw 是有意義的。例如展示 Jinja 語法的實例, 你可以在模板中用這個片段:

{% raw %}
    <ul>
    {% for item in seq %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
{% endraw %}

3.3 控制結構

控制結構指的是所有的那些可以控制程序流的東西 —— 條件(比如 if/elif/ekse )、 for 循環、以及宏和塊之類的東西。控制結構在默認語法中以 {% … %} 塊的形式 出現。
for循環
遍歷序列中的每項。例如,要顯示一個由 users` 變量提供的用戶列表:

<h1>Members</h1>
<ul>
{% for user in users %}
  <li>{{ user.username|e }}</li>
{% endfor %}
</ul>

因爲模板中的變量保留它們的對象屬性,可以迭代像 dict 的容器:

<dl>
{% for key, value in my_dict.iteritems() %}
    <dt>{{ key|e }}</dt>
    <dd>{{ value|e }}</dd>
{% endfor %}
</dl>

注意:無論如何字典通常是無序的,所以你可能需要把它作爲一個已排序的列表傳入 到模板或使用 dictsort 過濾器。
在一個 for 循環塊中你可以訪問這些特殊的變量:

變量 描述
loop.index 當前循環迭代的次數(從 1 開始)
loop.index0 當前循環迭代的次數(從 0 開始)
loop.revindex 到循環結束需要迭代的次數(從 1 開始)
loop.revindex0 到循環結束需要迭代的次數(從 0 開始)
loop.first 如果是第一次迭代,爲 True 。
loop.last 如果是最後一次迭代,爲 True 。
loop.length 序列中的項目數。
loop.cycle 在一串序列間期取值的輔助函數。見下面的解釋。

在 for 循環中,可以使用特殊的 loop.cycle 輔助函數,伴隨循環在一個字符串/變 量列表中週期取值:

{% for row in rows %}
    <li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
{% endfor %}

如果因序列是空或者過濾移除了序列中的所有項目而沒有執行循環,你可以使用 else 渲染一個用於替換的塊:

<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no users found</em></li>
{% endfor %}
</ul>

if條件判斷
Jinja 中的 if 語句可比 Python 中的 if 語句。在最簡單的形式中,你可以測試 一個變量是否未定義,爲空或 false:

{% if kenny.sick %}
    Kenny is sick.
{% elif kenny.dead %}
    You killed Kenny!  You bastard!!!
{% else %}
    Kenny looks okay --- so far
{% endif %}


宏類似常規編程語言中的函數。它們用於把常用行爲作爲可重用的函數,取代 手動重複的工作。
這裏是一個宏渲染表單元素的小例子:

{% macro input(name, value='', type='text', size=20) -%}
    <input type="{{ type }}" name="{{ name }}" value="{{
        value|e }}" size="{{ size }}">
{%- endmacro %}

在命名空間中,宏之後可以像函數一樣調用:

<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>

在宏內部,你可以訪問三個特殊的變量:

變量 說明
varargs 如果有多於宏接受的參數個數的位置參數被傳入,它們會作爲列表的值保存在 varargs 變量上。
kwargs 同 varargs ,但只針對關鍵字參數。所有未使用的關鍵字參數會存儲在 這個特殊變量中。
caller 如果宏通過 call 標籤調用,調用者會作爲可調用的宏被存儲在這個 變量中。

宏也可以暴露某些內部細節。下面的宏對象屬性是可用的:

屬性 說明
name 宏的名稱。 {{ input.name }} 會打印 input 。
arguments 一個宏接受的參數名的元組。
defaults 默認值的元組。
catch_kwargs 如果宏接受額外的關鍵字參數(也就是訪問特殊的 kwargs 變量),爲 true 。
catch_varargs 如果宏接受額外的位置參數(也就是訪問特殊的 varargs 變量),爲 true 。
caller 如果宏訪問特殊的 caller 變量且由 call 標籤調用,爲 true 。如果一個宏的名稱以下劃線開始,它不是導出的且不能被導入。

用法示例:
可以把宏單獨抽取出來,封裝成html文件,其它模板中導入使用。
如:文件名可以自定義mymacro.html

#單獨的html宏
{% macro function() %}
    <input type="text" name="username" placeholde="Username">
    <input type="password" name="password" placeholde="Password">
    <input type="submit">
{% endmacro %}

#在其它模板文件中先導入,再調用
{% import 'macro.html' as func %}
{% func.function() %}

宏提供了一種模塊化代碼的方式:
(1)在macro.html中定義宏函數

{# myapp/templates/macros.html #}

{% macro nav_link(endpoint, text) %}
{% if request.endpoint.endswith(endpoint) %}
    <li class="active"><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% else %}
    <li><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% endif %}
{% endmacro %}

(2)在其他html文件中導入宏,然後使用

{# myapp/templates/layout.html #}
{% from "macros.html" import nav_link with context %}
<!DOCTYPE html>
<html lang="en">
    <head>
    {% block head %}
        <title>My application</title>
    {% endblock %}
    </head>
    <body>
        <ul class="nav-list">
            {{ nav_link('home', 'Home') }}
            {{ nav_link('about', 'About') }}
            {{ nav_link('contact', 'Get in touch') }}
        </ul>
    {% block body %}
    {% endblock %}
    </body>
</html>

注意:

  • 從 x 導入 y 語句採用了 x 的相對路徑。如果我們的模板是 myapp/templates/user/blog.html,我們可以在使用 from “…/macros.html” 導入 nav_link。
  • 當我們說 {% from … import … with context %} 的時候,就是告訴 Jinja 這些變量對宏也可用。

宏調用
在某些情況下,需要把一個宏傳遞到另一個宏。爲此,可以使用特殊的 call 塊。 下面的例子展示瞭如何讓宏利用調用功能:

{% 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 %}

過濾器
過濾器段允許你在一塊模板數據上應用常規 Jinja2 過濾器。只需要把代碼用 filter 節包裹起來:

{% filter upper %}
    This text becomes uppercase
{% endfilter %}

賦值
在代碼塊中,你也可以爲變量賦值。在頂層的(塊、宏、循環之外)賦值是可導出的,即 可以從別的模板中導入。
賦值使用 set 標籤,並且可以爲多個變量賦值:

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

繼承
模板繼承是爲了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部菜單、底部。這些內容可以定義在父模板中,子模板直接繼承,而不需要重複書寫。
{% block top %}``{% endblock %}標籤定義的內容,相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。
子模板使用extends指令聲明這個模板繼承自哪。父模板中定義的塊在子模板中被重新定義,在子模板中調用父模板的內容可以使用super()。
extends 標籤用於從另一個模板繼承。你可以在一個文件中使用多次繼承,但是 只會執行其中的一個。
注意:

  • 不支持多繼承。
  • 爲了便於閱讀,在子模板中使用extends時,儘量寫在模板的第一行。
  • 不能在一個模板文件中定義多個相同名字的block標籤。
  • 當在頁面中使用多個block標籤時,建議給結束標籤起個名字,當多個block嵌套時,閱讀性更好。
  #父模版定義
  {% block top %}
    頂部菜單
  {% endblock top %}

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

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

  #子模版使用,先繼承,重寫對應block部分
  {% extends 'base.html' %}
  {% block content %}
   需要填充的內容
  {% endblock content %}

常用模版設計模式:
父模板 通常定義一個通用的結構,所有 子模板 都能很好的繼承它。在我們的例子中,layout.html 就是一個父模板而其它 .html 文件就是子模板。
templates目錄結構如下:

templates/
    layout.html
    index.html
    about.html
    profile/
        layout.html
        index.html
    photos.html
    admin/
        layout.html
        index.html
        analytics.html

通常有一個頂層的 layout.html,它定義了應用程序的通用佈局以及你的網站的每一部分。如果你看看上面的目錄的話,你會看到一個頂層的 myapp/templates/layout.html,同樣還有 myapp/templates/profile/layout.html 和 myapp/templates/admin/layout.html。最後兩個文件繼承和修改第一個文件。
在父模板中,我們能定義由子模板來填充的塊。

{# _myapp/templates/layout.html_ #}

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
    {% block body %}
        <h1>This heading is defined in the parent.</h1>
    {% endblock %}
    </body>
</html>

在子模板中,我們可以擴展父模板並且定義這些塊的內容。

{# _myapp/templates/index.html_ #}

{% extends "layout.html" %}
{% block title %}Hello world!{% endblock %}
{% block body %}
    {{ super() }} ##super() 函數讓我們渲染父級塊的內容。
    <h2>This heading is defined in the child.</h2>
{% endblock %}


塊用於繼承,同時作爲佔位符和用於替換的內容。 模板繼承 節中詳細地介紹了塊。

包含
include 語句用於包含一個模板,並在當前命名空間中返回那個文件的內容渲 染結果:

{% include 'header.html' %}
    Body
{% include 'footer.html' %}

從 Jinja 2.2 開始,你可以把一句 include 用 ignore missing 標記,這樣 如果模板不存在,Jinja 會忽略這條語句。當與 with 或 without context 語句聯合使用時,它必須被放在上下文可見性語句 之前 。這裏是一些有效的例 子:

{% include "sidebar.html" ignore missing %}
{% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %}

宏、繼承、包含區別
宏(Macro)、繼承(Block)、包含(include)均能實現代碼的複用。
繼承(Block)的本質是代碼替換,一般用來實現多個頁面中重複不變的區域。
宏(Macro)的功能類似函數,可以傳入參數,需要定義、調用。
包含(include)是直接將目標模板文件整個渲染出來。

3.4 導入上下文行爲

默認下,每個包含的模板會被傳遞到當前上下文,而導入的模板不會。這樣做的原因 是導入量不會像包含量被緩存,因爲導入量經常只作容納宏的模塊。
無論如何,這當然也可以顯式地更改。通過在 import/include 聲明中直接添加 with context 或 without context ,當前的上下文可以傳遞到模板,而且不會 自動禁用緩存。

{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}

參考文獻:
http://docs.jinkan.org/docs/flask/quickstart.html#id7
http://docs.jinkan.org/docs/flask/templating.html#jinja

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章