Sunday, December 27, 2015

The Flask Mega-Tutorial, Part II 導讀

之前為了讓台灣的朋友能夠較快上手,寫了 The Flask Mega-Tutorial, Part I解說加上 Windows 環境設定。似乎對一些人還蠻有用,所以這裡繼續寫了 Part II 。這並不是要直接翻譯 Miguel Grinberg 寫的 The Flask Mega-Tutorial ,主要是為了導讀。請務必閱讀原文。好啦,你們只想看中文,不過真的,讀讀看!他寫的簡單易懂。
我們來看看 The Flask Mega-Tutoral Part II: Templates 說了什麼。Template 是套版的意思,那套版是什麼呢?我們慢慢看下去。
這個教學系列最後會完成一個微博網站。作者命名他為 microblog ,也就是微博的意思。
開始之前請確定已經有 Part I 練習的完成品。執行 flask/Scripts/python run.py 後,可以從瀏覽器打開 http://localhost:500 看到到 Hello, World! 的字樣。

要解決的問題

我們讓這小程式熱鬧、好看一點。所以畫面上加的大標題,然後跟使用者打個招呼。但是目前為止還沒有登入的機制,所以使用者就先直接寫死 Minguel (原文作者的名),當然因為只是臨時寫死的東西,所以這使用者要叫什麼都可以。因為要加大標題就要動到畫面的排版,所以只是回傳沒有格式的文字不夠用了,我們把 app/views.py 內容改成 HTML 。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from app import app

@app.route('/')
@app.route('/index')
def index():
	user = { 'nickname': 'Miguel' } # fake user
	return '''
<html>
	<head>
		<title>Home Page</title>
	</head>
	<body>
		<h1>Hello, ''' + user['nickname'] + '''</h1>
	</body>
</html>
'''
程式改好後,可以再跑一次 run.py ,瀏覽器中應該要可以看到大大的 Hello, Miguel ,頁籤的標題也顯示了 Home Page 。
程式中的 user 變數是以大括號建立的 dictionary 物件,因為沒有登入的機制,給的資料只是隨便的假資料,也就是所謂的假物件( mock object )。回傳內容用了三個單引號包著可以直接寫出多行的內容建立含有換行資訊的字串。
如果你還沒發現的話,直接把網頁內容寫到程式中回傳是個很糟的做法。
當畫面開始豐富一點時,程式很快的會變得非常複雜。如果每個畫面的程式都是各自回傳寫好的網頁內容,想要修改整個網站排版時就得一個一個程式修改。這種做法在網站越來越大時完全會行不通。

用套版來處理

如果我們把程式的處理邏輯跟網頁呈現方式分開來的話,就會乾淨很多。你還可以去找個網頁設計師幫你寫超棒的網頁畫面,你來負責寫背後的 Python 程式就好。 (依照過往的經驗,網頁設計師寫出來的網頁畫面最好還是重做,不過目前為止應該可以感受到把邏輯跟畫面分開的好處吧!)套版就是可以把這邏輯跟畫面分開的工具。
那就來寫第一個套版吧,寫在 app/templates/index.html :
1
2
3
4
5
6
7
8
<html>
	<head>
		<title>{{ title }} - microblog</title>
	</head>
	<body>
		<h1>Hello, {{ user.nickname }}!</h1>
	</body>
</html>
這看起來會很像一般的 HTML 網頁,不過有幾個地方用 {{ ... }} 加了保留區給動態的內容用。
接下來看如何在 app/views.py 中用這個套版,記得要在第一行加 from flask import render_template :
1
2
3
4
5
6
7
8
from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
	user = { 'nickname': 'Miguel' } # fake user
	return render_template('index.html', title='Home', user=user)
再執行一次程式,可以在瀏覽器看到跟改成套版之前一樣的畫面,用瀏覽器檢視原始碼功能看看是不是跟我們的套版一樣。
render_template 可以把指定的套版檔案套入傳入的參數。背後使用 Jinja2 套版引擎。 Jinja2 以同名參數的內容將套版中的 {{ ... }} 取代。

套版中的控制語句

Jinja2 也可以把控制語句用在套版中的 {% ... %} 區塊,我們把 if 加到套版中看看, app/templates/index.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<html>
	<head>
		{% if title %}
		<title>{{ title }} - microblog</title>
		{% else %}
		<title>Welcome to microblog</title>
		{% endif %}
	</head>
	<body>
		<h1>Hello, {{ user.nickname }}!</h1>
	</body>
</html>
這個套版現在變的聰明一點了,他可以在沒有提供標題時自己寫出一個預設的標題Welcome to microblog。可以在 render_template 中不傳入 title 參數看看會有什麼樣的結果。

套版中的迴圈

登入的會員很有可能想看他在追蹤的人的貼文。我們來看看怎麼做。
我們還沒有登入的機制,方便起見,我們先在 views.py 做些假會員資料。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def index():
	user = { 'nickname': 'Miguel' } # fake user
	posts = [ # 假的貼文陣列
		{
			'author': {'nickname': 'John'},
			'body': 'Beautiful day in Portland!'
		},
		{
			'author': {'nickname': 'Susan'},
			'body': 'The Avengers movie was so cool!'
		}
	]
	return render_template('index.html', title='Home', user=user, posts=posts)
我們用一個 list 來存放所有的貼文。每個貼文中含 author 跟 body 欄位(如果英文真的那麼差的話,那兩欄分別代表作者跟貼文內容,nickname 是作者的暱稱)。之後用真的資料庫來存放這些貼文時我們會持續用相同的欄位名稱。這樣就不用回頭改套版中的欄位名稱了。
一個 list 中可以放不定數量的貼文,套版要忠實的把每一筆輸出在結果畫面中。如果要限制顯示的貼文數量,那應該要讓 views.py 中的程式決定 list 中要有幾筆貼文。決定顯示幾筆不是套版的工作。
我們在 app/templates/index.html 加一個 for 迴圈輸出貼文:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<html>
	<head>
		{% if title %}
		<title>{{ title }} - microblog</title>
		{% else %}
		<title>Welcome to microblog</title>
		{% endif %}
	</head>
	<body>
		<h1>Hello, {{ user.nickname }}!</h1>
		{% for post in posts %}
		<div><p>{{ post.author.nickname }} says: <b>{{ post.body }}</b></p></div>
		{% endfor %}
	</body>
</html>
這樣就可以了,再到瀏覽器看看新的畫面。

套版繼承

接下來最後的討論項目。
這個 microblog 網站的網頁的最上方要有一塊導覽列。裡面會包含編輯個人資訊、登入、登出等連結。我們可以直接把導覽列寫到 index.html 中,不過網站越大時網頁也跟著變多,如果每次要改導覽頁時都要從 index.html 複製過去會很麻煩。萬一複製後又要改導覽列,那就得花很多時間改了。
還有 Jinja2 的套版繼承功能,他可以讓我們把所有套版相同的部分都拉出來放到一個共用的套版中,然後再讓所有的套版繼承這個共用的套版。
我們就來新增這個共用的套版,放在 app/templates/base.html ,把導覽列跟一些之前寫好的套版內容寫在裡面:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<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 控制語句被指定了一個名稱,讓繼承他的套版插入專屬的內容。接下來我們把 index.html 改成繼承 base.html 。
1
2
3
4
5
6
7
{% extends "base.html" %}
{% block content %}
	<h1>Hi, {{ user.nickname }}!</h1>
	{% for post in posts %}
	<div><p>{{ post.author.nickname }} says: <b>{{ post.body }}</b></p></div>
	{% endfor %}
{% endblock %}
因為 base.html 已經定義了整體畫面架構,我們可以只留內容的部分。 extends 告訴 Jinja2 他繼承了 base.html ,產生 index.html 得畫面時要先引入 base.html 。因為兩個套版都有叫做 content 的 block ,所以 Jinja2 知道要把同名的內容塞到共用套版的哪個位置。之後新寫的套版都會先繼承 base.html。
套版就講到這了,不要忘了看原文喔。