Flask Web開發——(二)模板
檢視函式的作用是生成請求的響應。很多情況下,請求會改變應用的狀態,而這種變化就發生在檢視函式中。
以使用者在網站中註冊新賬戶的過程為例。使用者在表單中輸入電子郵件地址和密碼,然後提交。伺服器接收到包含使用者輸入資料的請求,然後Flask把請求分派給處理註冊請求的檢視函式。這個檢視函式訪問資料庫,新增新使用者,生成響應回送瀏覽器,指明操作成功/失敗。這兩個過程分別為業務邏輯和表現邏輯。
檢視函式裡包含函式和HTML程式碼,但這兩部分在一起會影響理解和維護,所有用模板來儲存HTML程式碼。
模板是包含響應的文字檔案,其中包含用佔位變量表示的動態部分,其具體值只在請求上下文中才能知道。使用真實值替換變數,再返回最終得到的響應字串,這一過程稱為渲染。Flask使用Jinja2的模板引擎。
一、Jinja2模板引擎
例1 templates/index.html:Jinja2模板
<h1>Hello World!</h1>
例2 templates/user.html:Jinja2moban
<h1>Hello, {{ name }}!</h1>
(一)、渲染模板
在Flask根目錄中新建子目錄,將上面的兩個模板(html)檔案儲存在裡面。
這塊網上的教程也沒清楚的說在哪建,看了好幾遍書裡的那段句話“Flask在應用目錄中的templates子目錄裡尋找模板。在下一個hello.py版本中,你要新建templates子目錄,再把前面定義的模板儲存在裡面,分別命名為index.html和user.html。”我才明白是在根目錄下建立子目錄templates,而不是在 檔案-設定-編輯器-檔案和程式碼模組 裡建立模板。並且吧,templates子目錄建立完後,我一直沒法選中它,建立的兩個html檔案全在.idea資料夾裡,我還要剪下過來,emmmmm希望一邊學一邊能弄清楚吧。
例3 hello.py:渲染模板
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
Flask提供的render_template()函式把Jinja2模板引擎整合到了應用中。這個函式的第一個引數名是模板的檔名,隨後的引數都是鍵-值對,表示模板中變數對應的具體值。
name=name是經常使用的關鍵字引數。左邊的name表示引數名,即模板中的佔位符;右邊的name是當前作用域中的變數,表示同名引數的值。
講道理,以前我總以為是書不對,書不貼近實操,還得靠別人的經驗教程,但學到現在我也算明白了,這一系列動物書,裡面都是精華內容,吃透了絕對有很大的提高,而網上的教程只是輔助,只是更靈活,但核心知識還是在書上的,怎麼說吧,兩邊都要看,但書還是更重要一些吧。
我的三大錯覺:書寫錯了,編譯器有問題,標準庫不對。
(二)、變數
{{ name }}結構表示一個變數,是一種特殊的佔位符,告訴模板引擎這個位置的值從渲染模板時使用的資料中獲取。
Jinja2能識別所有型別的變數,甚至是一些複雜的型別,如列表、字典和物件。
變數的值可以通過過濾器修改,過濾器新增在變數名之後,二者之間以豎線分隔。
例,下述模板把name變數的值變成首字母大寫的形式:
Hello, {{ name|capitalize}}
Jinja2變數過濾器表
過濾器名 說明
safe 渲染值時不轉義
capitalize 把值的首字母轉換成大寫,其他轉換成小寫
lower 把值轉換成小寫
upper 把值轉換成大寫
title 把值中每個單詞的首字母都轉換成大寫
trim 把值的首尾空格刪掉
striptags 渲染之前把值中所有的HTML標籤都刪掉
預設情況下,出於安全考慮,Jinja2會轉義所有變數。例如,如果一個變數的值為<h1>Hello</h1>
,Jinja2會將其渲染成’<h1>Hello</h1>’,瀏覽器能顯示這個h1元素,但不會解釋它。
很多情況下需要顯示變數中儲存的HTML程式碼,這時就可以使用safe過濾器。
一定不能在不可信的值上使用safe過濾器,例如使用者在表單中輸入的文字。
(三)、控制結構
Jinja2提供了多種控制結構,可用來改變模板的渲染流程。
例,如何在模板中使用條件判斷語句:
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
例,在模板中渲染一組元素,使用for迴圈實現需求:
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
Jinja2還支援巨集。巨集類似於Python程式碼中的函式:
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
為了重複使用巨集,可以把巨集儲存在單獨的檔案中,然後在需要使用的模板中匯入:
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
需要在多處重複使用的模板程式碼片段可以寫入單獨的檔案,再引入所有模板中,以避免重複:
{% include 'common.html' %}
另一種重複使用程式碼的方式是模板繼承,這類似於Python中的類繼承。
首先,建立一個名為base.html的基模板:
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
<% endblock %}
</body>
<html>
基模板中定義的區塊可在衍生模板中覆蓋。Jinja2使用block和endblock指令在基模板中定義內容區塊。
在本例中,我們定義了名為head、title和body的區塊(title包含在head中)。
下例是基模板的衍生模板:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}}
extends指令宣告這個模板衍生自base.html。
如果基模板和衍生模板中的同名區塊中都有內容,衍生模板中的內容將顯示出來。在衍生模板的區塊裡可以呼叫super(),引用基模板中同名區塊裡的內容(如上例中的head)。
二、使用Flask-Bootstrap整合Bootstrap
Bootstrap是Twitter開發的一個開源Web框架,它提供的使用者介面元件可用於建立整潔且具有吸引力的網頁,而且相容所有現代的桌面和移動平臺Web瀏覽器。
Bootstrap是客戶端框架,因此不會涉及伺服器。伺服器需要做的只是提供引用了Bootstrap層疊樣式表(CSS,cascading style sheet)和JavaScript檔案的HTML響應,並在HTML、CSS和JavaScript程式碼中例項化所需的使用者介面元素。
這些操作最理想的執行場所就是模板。
要想在應用中整合BootStrap,最直接的方法是根據BootStrap文件中的說明對HTML模板進行必要的改動。但這個任務使用Flask擴充套件處理要簡單得多,相關得改動不會導致主邏輯混亂不堪。
安裝擴充套件Flask-BootStrap:
(venv) $ pip install flask-bootstrap
怎麼碩呢,Flask有關得第三方庫一般都推薦在虛擬環境下安裝,但是把,cmd安裝這些第三方庫實在是太麻煩了,最開始搭環境就搭了兩三天,後來我乾脆直接全域性安裝第三方庫了。後來聽老師的用了pycharm…簡單了太多,所以直接在pycharm裡對應的虛擬環境下安裝Flask-BootStrap吧。
例 hello.py:初始化Flask-BootStrap
from flask_bootstrap import Bootstrap
#...
bootstrap = Bootstrap(app)
擴充套件通常從flask_包中匯入,其中是擴充套件的名稱。多數Flask擴展采用兩種初始化方式中的一種。
在上例中,初始化擴充套件的方式是把應用例項作為引數傳給建構函式。
初始化Flask-BootStrap之後,就可以在應用中使用一個包含所有BootStrap檔案和一般結構的基模板。應用利用Jinja2的模板繼承機制來擴充套件這個基模板。
下例是把user.html改寫為衍生模板後的新版本。
例 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="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">
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
</div>
{% endblock %}
效果如下圖所示:
Jinja2中的extends指令從Flask-BootStrap中匯入bootstrap/base.html,從而實現繼承。Flask-BootStrap的基模板提供了一個網頁骨架,引入了BootStrap的所有CSS和JavaScript檔案。
上面的user.html模板定義了3個區塊,分別為title、navbar和content。這些區塊都是基模板提供的,可在衍生模板中重新定義。title區塊的內容會出現在渲染後的HTML文件頭部,放在
在這個模板中,navbar區塊使用BootStrap元件定義了一個簡單的導航欄。content區塊中有個
容器,其中包含一個頁頭。改動之後如圖所示:成功後的圖。
Flask-Bootstrap基模板中定義的區塊
區塊名 說明
doc 整個HTML文件
html_attribs 標籤中的屬性
html 標籤中的內容
head 標籤中的內容
title
navbar 使用者定義的導航欄
content 使用者定義的頁面內容
scripts 文件底部的JavaScripts宣告
表中很多區塊都Flask-Bootstrap自用的,如果直接覆蓋可能會導致一些問題。
例如,Bootstrap的CSS和JavaScript檔案在styles和scripts區塊中宣告。如果應用需要向已經有內容的塊中新增新內容,必須使用Jinja2提供的super()函式。
例如,如果要在衍生模板中新增新的JavaScripts檔案,需要這麼定義scripts區塊:
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}
首先,.py檔案裡不能忘記加from flask_bootstrap import Bootstrap和bootstrap = Bootstrap(app)兩行程式碼,並且要加到對應的地方去,不然會報錯。那串巨長的程式碼直接覆蓋原user.html就行。
三、自定義錯誤頁面
如果你在瀏覽器位址列中輸入了無效的路由,會看到一個狀態碼為404的錯誤頁面。
Flask允許應用使用模板自定義錯誤頁面。最常見的錯誤程式碼有兩個:404,客戶端請求未知頁面或路由時顯示;500,應用有未處理的異常時顯示。
使用app.errorhandler裝飾器為這兩個錯誤提供自定義的處理函式。
例 hello.py:自定義錯誤頁面
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
與檢視函式一樣,錯誤處理函式也返回一個響應。此外,錯誤處理函式還要返回與錯誤對應的數字狀態碼。狀態碼可以直接通過第二個返回值指定。
錯誤處理函式中引用的模板也需要我們編寫。這些模板應該和常規頁面使用相同的佈局,因此要有一個導航欄和顯示錯誤訊息的頁頭。
編寫這些模板最直接的方法是複製templates/user.html,分別建立templates/404.html和templates/500.html,然後把這兩個檔案中的頁頭元素改為相應的錯誤下訊息。但這樣很麻煩。
Jinja2的模板繼承機制!Flask-Bootstrap提供了一個具有頁面基本佈局的基模板,同樣,應用也可以定義一個具有統一頁面佈局的基模板,其中包含導航欄,而頁面內容則留給衍生模板定義。
下例展示了templates/base.html的內容,這是一個繼承自bootstrap/base.html的新模板,其中定義了導航欄。這個模板本身也可以作為其他模板的二級基模板,例如templates/user.html、templates/404.html和templates/500.html。
例 templates/base.html:包含導航欄的應用基模板
{% 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 %}
這個模板中的content區塊只有一個
容器,其中一個包含一個新的空區塊,名為page_content,區塊中的內容由衍生模板定義。現在,應用中的模板繼承自這個模板,而不直接繼承自Flask-Bootstrap的基模板。通過繼承templates/base.html模板編寫自定義的404錯誤頁面就簡單了。
例 templates/404.html:使用模板繼承機制自定義404錯誤頁面
{% extends "base.html" %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Not Found</h1>
</div>
{% endblock %}
效果如下圖所示;
templates/user.html模板也可以通過繼承這個基模板來簡化內容。
例 templates/user.html:使用模板繼承機制簡化頁面模板:
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
{% endblock %}
模板繼承extends,html的知識忘記了好些,看程式碼是能看懂,背不下來啊。。。Bootstrap的語法有點難背嗷。。。
四、連結
任何具有多個路由的應用都需要可以連線不同頁面的連結,例如導航欄。
在模板中直接編寫簡單路由的URL連結不難,但對於包含可變部分的動態路由,在模板中構建正確的URL就很難了。而且直接編寫URL會對程式碼中定義的路由產生不必要的依賴關係。如果重新定義路由,模板中的連結可能會失效。
url_for()輔助函式,它使用應用的URL對映儲存的資訊生成URL。
url_for()函式最簡單的用法是以檢視函式名(或者app.add_url_route()定義路由時使用的端點名)作為引數,返回對應的URL。
例如,在當前版本的hello.py應用中呼叫url_for(‘index’)得到的結果是/,即應用的根URL。
呼叫url_for(‘index’, _external=True)返回的則是絕對地址,在這個示例中是http://localhost:5000/。
生成連線應用內不同路由的連結時,使用相對地址就足夠了。如果要生成在瀏覽器之外使用的連結,則必須使用絕對地址,例如電子郵件中傳送的連結。
使用url_for()生成動態URL時,將動態部分作為關鍵字引數傳入。例如,url_for(‘user’, name=‘json’, _external=True)的返回結果是http://localhost:5000/user/john。
傳給url_for()的關鍵字引數不僅限於動態路由中的引數,非動態的引數也會新增到查詢字串中。
例如,url_for(‘user’, name=‘john’, page=2, version=1)的返回結果是/user/john?page=2&version=1。
五、靜態檔案
Web應用不是僅由Python程式碼和模組組成。多數應用還會使用靜態檔案,例如模板中HTML程式碼引用影象、JavaScript原始碼檔案和CSS。
在審查hello.py應用的URL對映,其中有一個static路由。這是Flask為了支援靜態檔案而自動新增的,這個特殊路由的URL是/static/。例如,呼叫url_for(‘static’, filename='css/style.css, _external=True)得到的結果是http://loacalhost:5000/static/css/styles.css。
預設設定下,Flask在應用根目錄中名為static的子目錄中尋找靜態檔案。如果需要,可在static資料夾中使用子資料夾存放檔案。伺服器收到對映到static路由上的URL後,生成的響應包含檔案系統中對應檔案裡的內容。
例 templates/base.html:定義收藏夾圖示
{% block head %}
{{ super() }}
這個圖示的宣告插入head區塊的末尾。注意,為了保留基模板中這個區塊裡的原始內容,我們呼叫了super()。
六、使用Flask-Moment本地化日期和時間
伺服器需要統一時間單位,這和使用者所在的地理位置無關,所以一般使用協調世界時(UTC,coordinad universal time)。不過使用者看到UTC格式的時間會感到困惑,他們更希望看到當地時間,而且採用當地慣用的格式。
要想在伺服器上只使用UTC時間,一個優雅的解決方案是,把時間單位傳送給Web瀏覽器,轉換成當地時間,然後用JavaScript渲染。Web瀏覽器能更好地完成這一任務,因為它能獲取使用者計算機中的時間和區域設定。
有一個使用JavaScript開發的優秀客戶端開源庫,名為Moment.js,它可以在瀏覽器中渲染日期和時間。Flask-Moment是一個Flask擴充套件,能簡化把Moment.js整合到Jinja2模板中的過程。
Flask-Moment安裝:
(venv) $ pip install flask-moment
例 hello.py:初始化Flask-Moment
from flask_moment import Moment
moment = Moment(app)
除了Moment.js,Flask-Moment還依賴jQuery.js。要在HTML文件的某個地方引入這兩個庫,可以直接引入,這樣可以選擇使用哪個版本,也可使用擴充套件提供的輔助函式,從內容 分發網路(Content Delivery Network,CDN)中引入通過測試的版本。Bootstrap已經引入了 jQuery.js,因此只需引入Moment.js即可。
例 templates/base.html:引入Moments.js庫
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
這個區塊放在templates/base.html的最後。
為了處理時間戳,Flask-Moment向模板開放了moment物件。
例 hello.py:新增一個datetime變數
from datetime import datetime
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())
把變數current_time傳入模板進行渲染。
例 templates/index.html:使用Flask-Moment變數
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
展示瞭如何渲染模板變數current_time。
format(‘LLL’)函式根據客戶端計算機中的時區和區域設定渲染日期和時間。引數決定了渲染的方式,從’L’到’LLL’分貝對應不同的複雜度。format()函式還可以接受很多自定義的格式說明符。
我一直以為是我程式碼打錯了。。。原來是def index()部分程式碼重複了,改完忘刪了。。。
但是這裡一直有一個錯誤,暫時存疑吧,詳見:
https://blog.csdn.net/csdnjava2017/article/details/78168861