1. 程式人生 > 其它 >Flask Web開發——(二)模板

Flask Web開發——(二)模板

技術標籤:Flask學習flask

檢視函式的作用是生成請求的響應。很多情況下,請求會改變應用的狀態,而這種變化就發生在檢視函式中。

以使用者在網站中註冊新賬戶的過程為例。使用者在表單中輸入電子郵件地址和密碼,然後提交。伺服器接收到包含使用者輸入資料的請求,然後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是當前作用域中的變數,表示同名引數的值。

講道理,以前我總以為是書不對,書不貼近實操,還得靠別人的經驗教程,但學到現在我也算明白了,這一系列動物書,裡面都是精華內容,吃透了絕對有很大的提高,而網上的教程只是輔助,只是更靈活,但核心知識還是在書上的,怎麼說吧,兩邊都要看,但書還是更重要一些吧。
我的三大錯覺:書寫錯了,編譯器有問題,標準庫不對。

(二)、變數
{{ name }}結構表示一個變數,是一種特殊的佔位符,告訴模板引擎這個位置的值從渲染模板時使用的資料中獲取。

Jinja2能識別所有型別的變數,甚至是一些複雜的型別,如列表、字典和物件。

變數的值可以通過過濾器修改,過濾器新增在變數名之後,二者之間以豎線分隔。

例,下述模板把name變數的值變成首字母大寫的形式:
Hello, {{ name|capitalize}}

Jinja2變數過濾器表
過濾器名 說明
safe 渲染值時不轉義
capitalize 把值的首字母轉換成大寫,其他轉換成小寫
lower 把值轉換成小寫
upper 把值轉換成大寫
title 把值中每個單詞的首字母都轉換成大寫
trim 把值的首尾空格刪掉
striptags 渲染之前把值中所有的HTML標籤都刪掉

預設情況下,出於安全考慮,Jinja2會轉義所有變數。例如,如果一個變數的值為<h1>Hello</h1>,Jinja2會將其渲染成’<h1>Hello</h1&gt’,瀏覽器能顯示這個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和content這兩個區塊分別表示頁面中的導航欄和主體內容。

在這個模板中,navbar區塊使用BootStrap元件定義了一個簡單的導航欄。content區塊中有個

容器,其中包含一個頁頭。改動之後如圖所示:

成功後的圖。

Flask-Bootstrap基模板中定義的區塊
區塊名 說明
doc 整個HTML文件
html_attribs 標籤中的屬性
html 標籤中的內容
head 標籤中的內容
title 標籤中的內容<br/> metas 一組標籤<br/> styles CSS宣告<br/> body_attribs 標籤的屬性<br/> body 標籤中的內容

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() }}

{% endblock %}

這個圖示的宣告插入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