【Chapter 3】模板
模板是一個(gè)包含響應(yīng)文本的文件疯溺,其中個(gè)包含用站位變量表示的動(dòng)態(tài)部分论颅,其具體值只在請(qǐng)求的上下文中才能知道。
渲染是一個(gè)使用真實(shí)值替換變量囱嫩,在返回最終得到的響應(yīng)字符串的過程恃疯。
Jinja2 是一個(gè)強(qiáng)大模板引擎用來渲染模板。
3.1 Jinja2 模板引擎
3.1.1 渲染模板
默認(rèn)情況下墨闲,F(xiàn)lask 在程序文件夾中的 templates 子文件夾中尋找模板今妄。在下一個(gè) hello.py 版本中,要把前面定義的模板保存在 templates 文件夾中鸳碧,并分別命名為 index.html 和 user.html盾鳞。
templates\index.html
<h1>
Hello World
</h1>
templates\user.html
{# {{ name }} 表示一種變量,模板中的占位符 #}
<h1>Hello,{{ name }}!</h1>
hello.py:渲染模板
from flask import Flask,render_template
# 1.初始化 Flask 對(duì)象
app = Flask(__name__)
# manager = Manager(app)
# 2. 設(shè)置路由和視圖函數(shù)
@app.route('/')
def index():
# return '<h1>Hello World!<h1>'
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
# return '<h1>Hello,%s!<h1>' % name
return render_template('user.html',name=name)
# 3.啟動(dòng)服務(wù)
if __name__ == '__main__':
app.run(debug=True)
# manager.run()
Flask 提供的 render_template
函數(shù)吧 Jinja2 模板引擎集成到了程序中瞻离。render_template
函數(shù)的第一個(gè)參數(shù)是模板的文件名腾仅,隨后的參數(shù)都是鍵值對(duì),表示模板中變量對(duì)應(yīng)的真實(shí)值套利。在這段代碼中推励,第二個(gè)參數(shù)收到一個(gè)名為 name 的變量。
3.1.2 變量
Jinja2 能識(shí)別所有類型的變量,甚至是一些復(fù)雜的類型,例如列表肉迫、字典和對(duì)象验辞。
Jinja2 可以使用過濾器修改變量喊衫,過濾器名添加在變量名之后,中間使用豎線分割朝墩。
例如,下述模板以首字母大寫形式顯示變量 name 的值:
Hello,{{ name|capitalize }}
Jinja2 變量過濾器
safe 過濾器懦鼠,默認(rèn)情況下睦袖,出于安全考慮,Jinja2 會(huì)轉(zhuǎn)義所有變量阱飘。
例 如高帖,如果一個(gè)變量的值為 '<h1>Hello</h1>',Jinja2 會(huì)將其渲染成 '<h1>Hello</ h1>'吏祸,瀏覽器能顯示這個(gè) h1 元素,但不會(huì)進(jìn)行解釋刚操。很多情況下需要顯示變量中存儲(chǔ) 的 HTML 代碼,這時(shí)就可使用 safe 過濾器祝闻。
3.1.3 控制結(jié)構(gòu)
在模板中使用條件控制語句:
{% if user %}
Hello,{{ user }}占卧!
{% else %}
Hello,Stranger!
{% endif %}
在模板中渲染一組元素,使用 for 循環(huán)實(shí)現(xiàn):
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
Jinja2 還支持宏 。宏類似 python 代碼中的函數(shù)
{% macro render_comment(comment) %}
<li>{{ render_comment(comment) }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
為了重復(fù)使用宏联喘,我們可以將其保存在單獨(dú)的文件中华蜒,然后在需要使用的模板中導(dǎo)入:
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
需要在多處重復(fù)使用的模板代碼片段可以寫入單獨(dú)的文件,再包含在所有模板中豁遭,以避免重復(fù):
{% include 'comment.html' %}
另一種重復(fù)使用代碼的強(qiáng)大方式是模板繼承叭喜,它類似于 Python 代碼中的類繼承。首先蓖谢,創(chuàng) \建一個(gè)名為 base.html 的基模板:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %} - My Application </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
block 標(biāo)簽定義的元素可在衍生模板中修改捂蕴。在本例中,我們定義了名為 head闪幽、title 和 body 的塊啥辨。注意,title 包含在 head 中盯腌。下面這個(gè)示例是基模板的衍生模板:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello,World</h1>
{% endblock %}
extends 指令聲明這個(gè)模板衍生自 base.html溉知。在 extends 指令之后,基模板中的 3 個(gè)塊被重新定義,模板引擎會(huì)將其插入適當(dāng)?shù)奈恢谩W⒁庑露x的 head 塊淤齐,在基模板中其內(nèi)容不是空的,所以使用 super() 獲取原來的內(nèi)容檬姥。
3.2 使用 Flask-Bootstrap 集成 Twiitter Bootstrap
Bootstrap 是 Twitter 開發(fā)的一個(gè)開源框架,它提供的用戶界面組 件可用于創(chuàng)建整潔且具有吸引力的網(wǎng)頁,而且這些網(wǎng)頁還能兼容所有現(xiàn)代 Web 瀏覽器。
要想在程序中集成 Bootstrap崇决,顯然要對(duì)模板做所有必要的改動(dòng)材诽。不過,更簡(jiǎn)單的方法是使用一個(gè)名為 Flask-Bootstrap 的 Flask 擴(kuò)展恒傻,簡(jiǎn)化集成的過程。Flask-Bootstrap 使用 pip 安裝:
(venv) C:\Users\76152\Desktop\PyProjects\flask\flasky>pip install flask-bootstrap
hello.py 初始化 Flask-Bootstrap
from flask_bootstrap import Bootstrap
#...
#初始化 Flask-Bootstrap
bootstrap = Bootstrap(app)
初始化 Flask-Bootstrap 之后建邓,就可以在程序中使用一個(gè)包含所有 Bootstrap 文件的基模板盈厘。 這個(gè)模板利用 Jinja2 的模板繼承機(jī)制,讓程序擴(kuò)展一個(gè)具有基本頁面結(jié)構(gòu)的基模板官边,其中就有用來引入 Bootstrap 的元素沸手。
templates/user.html:使用 Flask-Bootstrap 的模板
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="nav-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block conntent %}
<div class="container">
<div class="page-header">
<h1>Hello,{{ name|capitalize }}!</h1>
</div>
</div>
{% endblock %}
Jinja2 中的 extends 指令從Flask-Bootstrap 中導(dǎo)入bootstrap/base.html,從而實(shí)現(xiàn)模板繼承注簿。Flask-Bootstrap 中的基模板提供了一個(gè)網(wǎng)頁框架契吉,引入了 Bootstrap 中的所有 CSS 和 JavaScript 文件。
基模板中定義了可在衍生模板中重定義的塊诡渴。block 和 endblock 指令定義的塊中的內(nèi)容可 添加到基模板中捐晶。
上面這個(gè) user.html 模板定義了3 個(gè)塊,分別名為 title妄辩、navbar 和 content惑灵。這些塊都是基模板提供的,可在衍生模板中重新定義眼耀。title 塊的作用很明顯英支,其中的內(nèi)容會(huì)出現(xiàn)在渲染后的 HTML 文檔頭部,放在 <title> 標(biāo)簽中哮伟。navbar 和 content 這兩個(gè)塊分別表示頁面中的導(dǎo)航條和主體內(nèi)容干花。
在這個(gè)模板中,navbar 塊使用 Bootstrap 組件定義了一個(gè)簡(jiǎn)單的導(dǎo)航條楞黄。content 塊中有個(gè) <div> 容器池凄,其中包含一個(gè)頁面頭部。之前版本的模板中的歡迎信息谅辣,現(xiàn)在就放在這個(gè)頁面頭部修赞。改動(dòng)之后效果如下:
Flask-Bootstrap 的 base.html 模板還定義了很多其他塊,都可在衍生模板中使用桑阶。
表中的很多塊都是 Flask-Bootstrap 自用的柏副,如果直接重定義可能會(huì)導(dǎo)致一些問題。例 如蚣录,Bootstrap 所需的文件在 styles 和 scripts 塊中聲明割择。如果程序需要向已經(jīng)有內(nèi)容的塊 中添加新內(nèi)容,必須使用 Jinja2 提供的 super() 函數(shù)萎河。例如荔泳,如果要在衍生模板中添加新 的 JavaScript 文件蕉饼,需要這么定義 scripts 塊:
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}
3.3 自定義錯(cuò)誤頁面
如果你在瀏覽器的地址欄中輸入了不可用的路由,那么會(huì)顯示一個(gè)狀態(tài)碼為 404 的錯(cuò)誤頁 面÷旮瑁現(xiàn)在這個(gè)錯(cuò)誤頁面太簡(jiǎn)陋昧港、平庸,而且樣式和使用了 Bootstrap 的頁面不一致支子。
最常見的錯(cuò)誤代碼有 兩個(gè):404创肥,客戶端請(qǐng)求未知頁面或路由時(shí)顯示;500值朋,有未處理的異常時(shí)顯示叹侄。
hello.py:自定義錯(cuò)誤頁面
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'),404
@app.errorhandler(500)
def page_not_found(e):
return render_template('500.html'),500
和視圖函數(shù)一樣,錯(cuò)誤處理程序也會(huì)返回響應(yīng)昨登。它們還返回與該錯(cuò)誤對(duì)應(yīng)的數(shù)字狀態(tài)碼趾代。
錯(cuò)誤處理程序中引用的模板也需要編寫。這些模板應(yīng)該和常規(guī)頁面使用相同的布局丰辣,因此 要有一個(gè)導(dǎo)航條和顯示錯(cuò)誤消息的頁面頭部撒强。
編寫這些模板最直觀的方法是復(fù)制 templates/user.html,分別創(chuàng)建 templates/404.html 和 templates/500.html糯俗,然后把這兩個(gè)文件中的頁面頭部元素改為相應(yīng)的錯(cuò)誤消息尿褪。
templates/base.html:包含導(dǎo)航條的程序基模板
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div> {% endblock %}
{% block content %}
<div class="container">
{% block page_content %}{% endblock %}
</div>
{% endblock %}
這個(gè)模板的 content 塊中只有一個(gè) <div> 容器,其中包含了一個(gè)名為 page_content 的新的 空塊得湘,塊中的內(nèi)容由衍生模板定義杖玲。
現(xiàn)在,程序使用的模板繼承自這個(gè)模板淘正,而不直接繼承自 Flask-Bootstrap 的基模板摆马。通過 繼承 templates/base.html 模板編寫自定義的 404 錯(cuò)誤頁面很簡(jiǎn)單。
404.html
{% extends "base.html" %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Not Found</h1>
</div>
{% endblock %}
繼承 base.html 簡(jiǎn)化后的 user.html
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello,{{ name|capitalize }}!</h1>
</div>
{% endblock %}
3.4 鏈接
任何具有多個(gè)路由的程序都需要可以連接不同頁面的鏈接鸿吆,例如導(dǎo)航條囤采。
在模板中直接編寫簡(jiǎn)單路由的 URL 鏈接不難,但對(duì)于包含可變部分的動(dòng)態(tài)路由惩淳,在模板中構(gòu)建正確的 URL 就很困難蕉毯。而且,直接編寫 URL 會(huì)對(duì)代碼中定義的路由產(chǎn)生不必要的依賴關(guān)系思犁。如果重新定義路由代虾,模板中的鏈接可能會(huì)失效。
為了避免這些問題激蹲,F(xiàn)lask 提供了 url_for() 輔助函數(shù)棉磨,它可以使用程序 URL 映射中保存 的信息生成 URL。
url_for() 函數(shù)最簡(jiǎn)單的用法是以視圖函數(shù)名(或者 app.add_url_route() 定義路由時(shí)使用 的端點(diǎn)名)作為參數(shù)学辱,返回對(duì)應(yīng)的URL乘瓤。例如环形,在當(dāng)前版本的 hello.py 程序中調(diào)用 url_ for('index') 得到的結(jié)果是 /。調(diào)用 url_for('index', _external=True) 返回的則是絕對(duì)地址衙傀,在這個(gè)示例中是 http://localhost:5000/抬吟。
生成連接程序內(nèi)不同路由的鏈接時(shí),使用相對(duì)地址就足夠了统抬。如果要生成在 瀏覽器之外使用的鏈接拗军,則必須使用絕對(duì)地址,例如在電子郵件中發(fā)送的 鏈接蓄喇。
使用 url_for() 生成動(dòng)態(tài)地址時(shí),將動(dòng)態(tài)部分作為關(guān)鍵字參數(shù)傳入交掏。例如妆偏,url_for ('user', name='john', _external=True) 的返回結(jié)果是 http://localhost:5000/user/john。
傳入 url_for() 的關(guān)鍵字參數(shù)不僅限于動(dòng)態(tài)路由中的參數(shù)盅弛。函數(shù)能將任何額外參數(shù)添加到 查詢字符串中钱骂。例如,url_for('index', page=2) 的返回結(jié)果是 /?page=2挪鹏。
3.5 靜態(tài)文件
Web 程序不是僅由 Python 代碼和模板組成见秽。大多數(shù)程序還會(huì)使用靜態(tài)文件,例如 HTML 代碼中引用的圖片讨盒、JavaScript 源碼文件和 CSS解取。
默認(rèn)設(shè)置下,F(xiàn)lask 在程序根目錄中名為 static 的子目錄中尋找靜態(tài)文件返顺。如果需要禀苦,可在 static 文件夾中使用子文件夾存放文件。服務(wù)器收到前面那個(gè) URL 后遂鹊,會(huì)生成一個(gè)響應(yīng)振乏,包含文件系統(tǒng)中 static/css/styles.css 文件的內(nèi)容。
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static',filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static',filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}
3.6 使用 Flask-Moment 本地化日期和時(shí)間
服務(wù)器需要統(tǒng)一時(shí)間單位秉扑,這和用戶所在的地理位置無關(guān)慧邮,所以一般使用協(xié)調(diào)世界時(shí) (Coordinated Universal Time,UTC)舟陆。不過用戶看到 UTC 格式的時(shí)間會(huì)感到困惑误澳,他們更希望看到當(dāng)?shù)貢r(shí)間,而且采用當(dāng)?shù)貞T用的格式吨娜。
要想在服務(wù)器上只使用 UTC 時(shí)間脓匿,一個(gè)優(yōu)雅的解決方案是,把時(shí)間單位發(fā)送給 Web 瀏覽 器宦赠,轉(zhuǎn)換成當(dāng)?shù)貢r(shí)間陪毡,然后渲染米母。Web 瀏覽器可以更好地完成這一任務(wù),因?yàn)樗塬@取用 戶電腦中的時(shí)區(qū)和區(qū)域設(shè)置毡琉。
有一個(gè)使用JavaScript 開發(fā)的優(yōu)秀客戶端開源代碼庫铁瞒,名為 [moment.js](http://momentjs. com/),它可以在瀏覽器中渲染日期和時(shí)間桅滋。Flask-Moment 是一個(gè)Flask 程序擴(kuò)展慧耍,能把 moment.js 集成到 Jinja2 模板中。
(venv) C:\Users\76152\Desktop\PyProjects\flask\Myflasky>pip install flask-moment
hello.py 初始化 Flask-Moment
from flask_moment import Moment
#初始化 Flask-Moment
moment = Moment(app)
base.html 引入 moment.js 庫
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
hello.py 加入一個(gè) datetime 變量
from datetime import datetime
def index():
# return '<h1>Hello World!<h1>'
return render_template('index.html',
current_time = datetime.utcnow())
修改 index.html: 使用 Flask-Moment 渲染時(shí)間戳
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello World!</h1>
</div>
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}.</p>
{% endblock %}
format('LLL') 根據(jù)客戶端電腦中的時(shí)區(qū)和區(qū)域設(shè)置渲染日期和時(shí)間丐谋。參數(shù)決定了渲染的方式芍碧,'L' 到 'LLLL' 分別對(duì)應(yīng)不同的復(fù)雜度。format() 函數(shù)還可接受自定義的格式說明符号俐。
第二行中的 fromNow() 渲染相對(duì)時(shí)間戳泌豆,而且會(huì)隨著時(shí)間的推移自動(dòng)刷新顯示的時(shí)間。這 個(gè)時(shí)間戳最開始顯示為“a few seconds ago”吏饿,但指定 refresh 參數(shù)后踪危,其內(nèi)容會(huì)隨著時(shí)間的推移而更新。如果一直待在這個(gè)頁面猪落,幾分鐘后贞远,會(huì)看到顯示的文本變成“a minute ago”“ 2 minutes ago”等。
最終效果:
幾分鐘后