儘管Flask並不強迫你使用某個特定的模板語言,它仍是默認你會使用Jinja。在Flask社區的大多數開發者使用Jinja,而且我建議你也跟着作。有一些插件容許你用其餘模板語言進行替代(好比Flask-Genshi和Flask-Mako),但除非你有充分理由(不懂Jinja可不是一個充分的理由!),不然請保持那個默認的選項;這樣你會避免浪費不少時間來焦頭爛額。html
注意 幾乎全部說起Jinja的資源講的都是Jinja2。Jinja1確實曾存在過,但在這裏咱們不會講到它。當你看到Jinja時,咱們討論的是這個Jinja: http://jinja.pocoo.org/python
Jinja文檔在解釋這門語言的語法和特性這方面作得很棒。在這裏我不會囉嗦一遍,但仍是會再一次向你強調下面一點:git
Jinja有兩種定界符。
{% ... %}
和{{ ... }}
。前者用於執行像for循環或賦值等語句,後者向模板輸出一個表達式的結果。github
因此要將模板放進咱們的應用的哪裏呢?若是你是從頭開始閱讀的本文,你可能注意到了Flask在對待你如何組織項目結構的事情上十分隨意。模板也不例外。你大概也已經注意到,總會有一個放置文件的推薦位置。記住兩點。對於模板,這個最佳位置是放在包文件夾下。api
myapp/ __init__.py models.py views/ templates/ static/ run.py requirements.txt
讓咱們打開模板文件夾看看。session
templates/ layout.html index.html about.html profile/ layout.html index.html photos.html admin/ layout.html index.html analytics.html
模板的結構平行於對應的路由的結構。對應於路由myapp.com/admin/analytics的模板是templates/admin/analytics.html。這裏也有一些額外的模板不會被直接渲染。layout.html文件就是用於被其餘模板繼承的。app
就像蝙蝠俠同樣,一個組織良好的模板文件夾也離不開繼承帶來的好處。基礎模板一般定義了一個適用於全部的子模板的主體結構。在咱們的例子裏,layout.html是一個基礎模板,而其餘的html文件都是子模板。模塊化
一般,你會有一個頂級的layout.html定義你的應用的主體佈局,外加站點的每個節點也有本身的一個layout.html。若是再看一眼上面的文件夾結構,你會看到一個頂級的myapp/templates/layout.html,以及myapp/templates/profile/layout.html和myapp/templates/admin/layout.html。後兩個文件繼承並修改第一個文件。函數
繼承是經過{% extends %}
和{% block %}
標籤實現的。在雙親模板中,你能夠定義要給子模板處理的block。
myapp/templates/layout.html
<!DOCTYPE html> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} <h1>這個標題在雙親模板中定義</h1> {% endblock %} </body> </html>
在子模板中,你能夠拓展雙親模板並定義block裏面的內容。
myapp/templates/index.html
{% extends "layout.html" %} {% block title %}Hello world!{% endblock %} {% block body %} {{ super() }} <h2>這個標題在子模板中定義</h2> {% endblock %}
super()
函數讓咱們在子模板里加載雙親模板中這個block的內容。
參見 若想了解更多關於繼承的內容,請移步到Jinja模板繼承方面的文檔。http://jinja.pocoo.org/docs/templates/#template-inheritance
憑藉將反覆出現的代碼片斷抽象成宏,咱們能夠實現DRY原則(Don't Repeat Yourself)。在撰寫用於應用的導航功能的HTML時,咱們可能會須要給「活躍」連接(好比,到當前頁面的連接)一個不一樣的類。若是沒有宏,咱們將不得不使用一大堆if/else語句來從每一個連接中過濾出「活躍」連接。
宏提供了模塊化模板代碼的一種方式;它們就像是函數同樣。讓咱們看一下如何使用宏來標記活躍連接。
myapp/templates/layout.html
{% from "macros.html" import nav_link with context %} <!DOCTYPE html> <html lang="en"> <head> {% block head %} <title>個人應用</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>
如今咱們調用了一個還沒有定義的宏 - nav_link
- 並傳遞兩個參數給它:一個目標(好比目標視圖的函數名)和咱們想要展現的文本。
注意 你可能注意到了咱們在import語句中加入了with context。Jinja的上下文(context)包括了經過
render_template()
函數傳遞的參數以及在咱們的Python代碼的Jinja環境上下文。這些變量可以被用於模板的渲染。一些變量是咱們顯式傳遞過去的,好比
render_template("index.html", color="red")
,但還有些變量和函數是Flask自動加入到上下文的,好比request
,g
和session
。使用了{% from ... import ... with context %}
,咱們告訴Jinja讓全部的變量也在宏裏可用。參見
- 全部的全局變量都是由Flask傳遞給Jinja上下文的:http://flask.pocoo.org/docs/templating/#standard-context
- 經過上下文處理器(context processors),咱們能夠增長傳遞給Jinja上下文的變量和函數:http://flask.pocoo.org/docs/templating/#context-processors
是時候定義模板中用的nav_link
宏了。
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 %}
如今咱們已經在myapp/templates/macros.html中定義了一個宏。咱們所作的,就是使用Flask的request
對象 - 默認在Jinja上下文中可用 - 來檢查當前路由是不是傳遞給nav_link
的那個路由參數。若是是,咱們就在目標連接指向的頁面上,因而能夠標記它爲活躍的。
注意
from x import y
語句中要求x是相對於y的相對路徑。若是咱們的模板位於myapp/templates/user/blog.html,咱們須要使用from "../macros.html" import nav_link with context
。
Jinja過濾器是在渲染成模板以前,做用於{{ ... }}
中的表達式的值的函數。
<h2>{{ article.title|title }}</h2>
在這個代碼中,title
過濾器接受article.title
並返回一個標題格式的文本,用於輸出到模板中。它的語法,以及功能,皆一如Unix中修改程序輸出的「管道」同樣。
參見 除了
title
,還有許許多多別的內建的過濾器。在這裏能夠看到完整的列表:http://jinja.pocoo.org/docs/templates/#builtin-filters
咱們能夠自定義用於Jinja模板的過濾器。做爲例子,咱們將實現一個簡單的caps
過濾器來使字符串中全部的字母大寫。
注意 Jinja已經有一個
upper
過濾器能實現這一點,還有一個capitalize
過濾器能大寫第一個字符並小寫剩餘字符。這些過濾器還能處理Unicode轉換,不過咱們的這個例子將只專一於闡述相關概念。
咱們將在myapp/util/filters.py中定義咱們的過濾器。這個util
包能夠用來放置各類雜項。
myapp/util/filters.py
from .. import app @app.template_filter() def caps(text): """Convert a string to all caps.""" return text.uppercase()
在上面的代碼中,經過@app.template_filter()
裝飾器,咱們能將某個函數註冊成Jinja過濾器。默認的過濾器名字就是函數的名字,可是經過傳遞一個參數給裝飾器,你能夠改變它:
@app.template_filter('make_caps') def caps(text): """Convert a string to all caps.""" return text.uppercase()
如今咱們能夠在模板中調用make_caps
而不是caps
:{{ "hello world!"|make_caps }}
。
爲了讓咱們的過濾器在模板中可用,咱們僅須要在頂級init.py中import它。
myapp/init.py
# 確保app已經被初始化以避免致使循環import from .util import filters
{% ... %}
和{{ ... }}
。前者用於執行相似循環或賦值的語句,後者向模板輸出表達式求值的結果。