**
什麼是模板?
**
user = {'username': 'Miguel'}
創建模擬對象是一項實用的技術,它可以讓你專注於應用程序的一部分,而無需爲系統中尚不存在的其他部分分心。 在設計應用程序主頁的時候,我可不希望因爲沒有一個用戶系統來分散我的注意力,因此我使用了模擬用戶對象,來繼續接下來的工作。
原先的視圖函數返回簡單的字符串,我現在要將其擴展爲包含完整HTML頁面元素的字符串,如下所示:
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
return '''
<html>
<head>
<title>Home Page - Microblog</title>
</head>
<body>
<h1>Hello, ''' + user['username'] + '''!</h1>
</body>
</html>'''
對HTML標記語言不熟悉的話,建議閱讀一下Wikipedia上的簡介HTML Markup
利用上述的代碼更新這個視圖函數,然後再次在瀏覽器打開它的URL看看結果。
模擬用戶
如果我說這個函數返回HTML的方式並不友好的話,你可能會覺得詫異。設想一下,當這個視圖函數中的用戶和博客不斷變化時,裏面的代碼將會變得多麼的複雜。應用的視圖函數及其關聯的URL也會持續增長。如果哪天我決定更改這個應用的佈局,那就不得不更新每個視圖函數的HTML字符串。顯然,隨着應用的擴張,這種方式完全不可行。
將應用程序的後臺邏輯和網頁佈局劃分開來,你不覺得更容易組織管理嗎?甚至你可以聘請一位Web設計師來設計一個殺手級的網站前端,而你只需要用Python編寫後臺應用邏輯。
模板有助於實現頁面展現和業務邏輯之間的分離。 在Flask中,模板被編寫爲單獨的文件,存儲在應用程序包內的templates文件夾中。 在確定你在microblog目錄後,創建一個存儲模板的目錄:
(venv) $ mkdir app/templates
在下面可以看到你的第一個模板,它的功能與上面的index()視圖函數返回的HTML頁面相似。 把這個文件寫在app/templates/index.html中:
<html>
<head>
<title>{{ title }} - Microblog</title>
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
這個HTML頁面看起來非常簡單,唯一值得關注的地方是{{ … }}。{{ … }}包含的內容是動態的,只有在運行時才知道具體表示成什麼樣子。
網頁渲染轉移到HTML模板之後,視圖函數就能被簡化:
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
return render_template('index.html', title='Home', user=user)
看起來好多了吧? 趕緊試試這個新版本的應用程序,看看模板是如何工作的。 在瀏覽器中加載頁面後,你需要從瀏覽器查看HTML源代碼並將其與原始模板進行比較。
將模板轉換爲完整的HTML頁面的操作稱爲渲染。 爲了渲染模板,需要從Flask框架中導入一個名爲render_template()的函數。 該函數需要傳入模板文件名和模板參數的變量列表,並返回模板中所有佔位符都用實際變量值替換後的字符串結果。
render_template()函數調用Flask框架原生依賴的Jinja2模板引擎。 Jinja2用render_template()函數傳入的參數中的相應值替換{{…}}塊。
條件語句
在渲染過程中使用實際值替換佔位符,只是Jinja2在模板文件中支持的諸多強大操作之一。 模板也支持在{%…%}塊內使用控制語句。 index.html模板的下一個版本添加了一個條件語句:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
現在,模板變得聰明點兒了,如果視圖函數忘記給渲染函數傳入一個名爲title的關鍵字參數,那麼模板將顯示一個默認的標題,而不是顯示一個空的標題。 你可以通過在視圖函數的render_template()調用中去除title參數來試試這個條件語句是如何生效的。
循環
登錄後的用戶可能想要在主頁上查看其他用戶的最新動態,針對這個需求,我現在要做的是豐富這個應用來滿足它。
我將會故技重施,使用模擬對象的把戲來創建一些模擬用戶和動態:
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'huazai'}
posts = [
{
'author': {'username': 'lulu'},
'body': 'Maths how to study'
},
{
'author': {'username': 'maomao'},
'body': 'kuaiji ru he lian cheng'
}
]
return render_template('index.html', title='Home', user=user, posts=posts)
我使用了一個列表來表示用戶動態,其中每個元素是一個具有author和body字段的字典。 未來設計用戶和其動態時,我將儘可能地保留這些字段名稱,以便在使用真實用戶和其動態的時候不會出現問題。
在模板方面,我必須解決一個新問題。 用戶動態列表擁有的元素數量由視圖函數決定。 那麼模板不能對有多少個用戶動態進行任何假設,因此需要準備好以通用方式渲染任意數量的用戶動態。
Jinja2提供了for控制結構來應對這類問題:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
</body>
</html>
大道至簡,對吧? 玩玩這個新版本的應用程序,一定要逐步添加更多的內容到用戶動態列表,看看模板如何調度以展現視圖函數傳入的所有用戶動態。
模板的繼承
絕大多數Web應用程序在頁面的頂部都有一個導航欄,其中帶有一些常用的鏈接,例如編輯配置文件,登錄,註銷等。我可以輕鬆地用HTML標記語言將導航欄添加到index.html模板上,但隨着應用程序的增長,我將需要在其他頁面重複同樣的工作。儘量不要編寫重複的代碼,這是一個良好的編程習慣,畢竟我真的不想在諸多HTML模板上保留同樣的代碼。
Jinja2有一個模板繼承特性,專門解決這個問題。從本質上來講,就是將所有模板中相同的部分轉移到一個基礎模板中,然後再從它繼承過來。
所以我現在要做的是定義一個名爲base.html的基本模板,其中包含一個簡單的導航欄,以及我之前實現的標題邏輯。 您需要在模板文件app/templates/base.html中編寫代碼如下:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<div>Microblog: <a href="/index">Home</a></div>
<hr>
{% block content %}{% endblock %}
</body>
</html>
在這個模板中,我使用block控制語句來定義派生模板可以插入代碼的位置。 block被賦予一個唯一的名稱,派生的模板可以在提供其內容時進行引用。
通過從基礎模板base.html繼承HTML元素,我現在可以簡化模板index.html了:
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
自從基礎模板base.html接手頁面的佈局之後,我就可以從index.html中刪除所有這方面的元素,只留下內容部分。 extends語句用來建立了兩個模板之間的繼承關係,這樣Jinja2才知道當要求呈現index.html時,需要將其嵌入到base.html中。 而兩個模板中匹配的block語句和其名稱content,讓Jinja2知道如何將這兩個模板合併成在一起。 現在,擴展應用程序的頁面就變得極其方便了,我可以創建從同一個基礎模板base.html繼承的派生模板,這就是我讓應用程序的所有頁面擁有統一外觀佈局而不用重複編寫代碼的祕訣。