什么是藍(lán)圖宰啦?
一個(gè)藍(lán)圖定義了視圖谋竖,模板,靜態(tài)文件以及可以用于應(yīng)用程序的其它元素的集合檐蚜。例如蟆技,讓我們假設(shè)下我們有一個(gè)管理面板的藍(lán)圖玩敏。這個(gè)藍(lán)圖會(huì)定義一些包含像 /admin/login 和 /admin/dashboard 路由的視圖。它也可能包含服務(wù)于這些路由的模板以及靜態(tài)文件质礼。接著我們可以使用這個(gè)藍(lán)圖添加一個(gè)管理面板到我們的應(yīng)用程序中旺聚,不論我們的應(yīng)用程序是什么類型的。
為什么要使用藍(lán)圖眶蕉?
藍(lán)圖“殺手級(jí)”使用場(chǎng)景就是把我們的應(yīng)用程序組織成不同的組件砰粹。對(duì)于一個(gè)類似 Twitter 的微型博客,我們可能有一個(gè)針對(duì)網(wǎng)站頁(yè)面的藍(lán)圖妻坝,例如伸眶,index.html 和 about.html。接著我們還有另外一個(gè)帶有登錄面板的藍(lán)圖刽宪,在那里我們顯示了所有最新的文章,然后我們還有一個(gè)用于后臺(tái)管理的面板的藍(lán)圖界酒。網(wǎng)站的每一個(gè)不同的區(qū)域也能夠被分成不同區(qū)域的代碼來(lái)實(shí)現(xiàn)圣拄。這能夠讓我們用幾個(gè)小的 “apps” 構(gòu)建我們的應(yīng)用程序,每一個(gè) apps 都在做一件事情毁欣。
你把它們放哪里庇谆?
使用藍(lán)圖組織我們的應(yīng)用程序有很多的方式。通常情況下凭疮,我們可以考慮按功能結(jié)構(gòu)和分區(qū)這兩種選擇(功能結(jié)構(gòu)和分區(qū)這兩個(gè)詞語(yǔ)我借鑒了商業(yè)上的概念)饭耳。
功能結(jié)構(gòu)
按照功能結(jié)構(gòu)的話,你可以通過(guò)它們所做的事情來(lái)組織你的應(yīng)用程序的結(jié)構(gòu)执解。模板在一個(gè)文件夾中寞肖,靜態(tài)文件在另一個(gè)文件夾中,視圖在第三個(gè)文件夾中衰腌。
yourapp/
__init__.py
static/
templates/
home/
control_panel/
admin/
views/
__init__.py
home.py
control_panel.py
admin.py
models.py
除了 yourapp/views/init.py新蟆,在上面列表中的 yourapp/views/ 文件夾中的每一個(gè) .py 文件都是一個(gè)藍(lán)圖。在 yourapp/init.py 中我們要導(dǎo)入這些藍(lán)圖并且在我們的 Flask() 對(duì)象中 注冊(cè) 它們右蕊。我們會(huì)在本章的后面看看實(shí)現(xiàn)方式琼稻。
分區(qū)
對(duì)于分區(qū)結(jié)構(gòu)了,你可以基于它們有助于應(yīng)用程序的哪一部分來(lái)組織應(yīng)用程序的結(jié)構(gòu)饶囚。管理面板所有的模板帕翻,視圖以及靜態(tài)文件都在一個(gè)文件夾中鸠补,用戶控制的所有的模板,視圖和靜態(tài)文件在另一個(gè)文件夾中嘀掸。
yourapp/
__init__.py
admin/
__init__.py
views.py
static/
templates/
home/
__init__.py
views.py
static/
templates/
control_panel/
__init__.py
views.py
static/
templates/
models.py
像上面列出的應(yīng)用程序的分區(qū)結(jié)構(gòu)莫鸭,在 yourapp/ 中每一個(gè)文件夾都是一個(gè)單獨(dú)的藍(lán)圖。所有的這些藍(lán)圖都會(huì)應(yīng)用到頂層 \\init\\.py 中的 Flask() 對(duì)象中横殴。
哪一個(gè)是最好的被因?
你選擇的組織結(jié)構(gòu)很大程度上是一種個(gè)人決定。唯一的區(qū)別是層次結(jié)構(gòu)的表示方式衫仑,因此你可以自由地決策要使用的組織結(jié)構(gòu)梨与,你可以選擇一個(gè)對(duì)自己有意義的。
如果你的應(yīng)用程序大部分是獨(dú)立的結(jié)構(gòu)文狱,僅僅共享著像模型和配置粥鞋,分區(qū)結(jié)構(gòu)就是合適的選擇方式。一個(gè)例子就是讓用戶建立網(wǎng)站的 SaaS 應(yīng)用程序瞄崇。你可能就有藍(lán)圖分別針對(duì)主頁(yè)呻粹,控制面板,用戶網(wǎng)站苏研,以及管理面板等浊。這些組件可能有完全不同的靜態(tài)文件和布局。如果考慮負(fù)責(zé)這個(gè)應(yīng)用程序或者分拆/重構(gòu)這個(gè)應(yīng)用程序的話摹蘑,分區(qū)結(jié)構(gòu)會(huì)更加適用一些筹燕。
另一方面,如果你的應(yīng)用程序聯(lián)系地更加緊密一些的話衅鹿,它可能用一個(gè)功能結(jié)構(gòu)呈現(xiàn)更加合適撒踪。一個(gè)示例就是 Facebook。如果 Facebook 使用 Flask 的話大渤,它可能就有靜態(tài)頁(yè)(例如制妄,登錄-注銷頁(yè),注冊(cè)泵三,關(guān)于等等)耕捞,控制面板(例如,新聞源)切黔,個(gè)人主頁(yè)(/robert/about 以及 /robert/photos)砸脊,設(shè)置(/settings/security 和 /settings/privacy)等等一些藍(lán)圖。這些組件共享一個(gè)通用的布局和樣式纬霞,但是每一個(gè)也會(huì)有自己的布局凌埂。下面的列表中展示了一個(gè)進(jìn)行大量刪減版的 Facebook 的樣子,如果它是使用 Flask 構(gòu)建的話诗芜。
facebook/
__init__.py
templates/
layout.html
home/
layout.html
index.html
about.html
signup.html
login.html
dashboard/
layout.html
news_feed.html
welcome.html
find_friends.html
profile/
layout.html
timeline.html
about.html
photos.html
friends.html
edit.html
settings/
layout.html
privacy.html
security.html
general.html
views/
__init__.py
home.py
dashboard.py
profile.py
settings.py
static/
style.css
logo.png
models.py
在 facebook/views/ 中的藍(lán)圖僅僅是視圖的集合而不是完全獨(dú)立的組件瞳抓。同一的靜態(tài)文件將會(huì)被大多數(shù)的藍(lán)圖的視圖使用埃疫。大多數(shù)模板都會(huì)擴(kuò)展一個(gè)主模板。功能結(jié)構(gòu)是組織這個(gè)項(xiàng)目的一種好的方式孩哑。
你如何使用它們栓霜?
基本用法
讓我們看看 Facebook 示例中的其中一個(gè)藍(lán)圖的代碼。
# facebook/views/profile.py
from flask import Blueprint, render_template
profile = Blueprint('profile', __name__)
@profile.route('/<user_url_slug\\>')
def timeline(user_url_slug):
# Do some stuff
return render_template('profile/timeline.html')
@profile.route('/<user_url_slug\\>/photos')
def photos(user_url_slug):
# Do some stuff
return render_template('profile/photos.html')
@profile.route('/<user_url_slug\\>/about')
def about(user_url_slug):
# Do some stuff
return render_template('profile/about.html')
要?jiǎng)?chuàng)建一個(gè)藍(lán)圖對(duì)象横蜒,我們先導(dǎo)入 Blueprint() 類并且用參數(shù) name 和 import_name
初始化它胳蛮。通常情況下,import_name
就是 __name__
丛晌,這是一個(gè)包含當(dāng)前模塊名稱的特殊 Python 變量仅炊。
在這個(gè) Facebook 示例中我們使用了一個(gè)功能結(jié)構(gòu)。如果我們使用分區(qū)結(jié)構(gòu)的話澎蛛,我們要通知 Flask 藍(lán)圖有自己的模板和靜態(tài)文件夾抚垄。此塊的代碼大概的樣子如下所示。
profile = Blueprint('profile', __name__,
template_folder='templates',
static_folder='static')
現(xiàn)在我們已經(jīng)定義我們的藍(lán)圖谋逻。是時(shí)候在我們的 Flask 應(yīng)用程序中注冊(cè)它呆馁。
# facebook/__init__.py
from flask import Flask
from .views.profile import profile
app = Flask(\\_\\_name\\_\\_)
app.register_blueprint(profile)
現(xiàn)在定義在 facebook/views/profile.py 上的路由(例如,/<user\_url\_slug>)在應(yīng)用程序上注冊(cè)并且表現(xiàn)得像你使用 @app.route() 定義它們一樣毁兆。
使用動(dòng)態(tài)的 URL 前綴
繼續(xù) Facebook 例子浙滤,注意到所有的用戶資料路由都是以 <user\_url\_slug> 開(kāi)始并且把它的值傳遞給視圖。我們希望用戶們能夠通過(guò)瀏覽像 https://facebo-ok.com/john.doe 類似的網(wǎng)址訪問(wèn)用戶資料頁(yè)荧恍。我們可以通過(guò)為所有的藍(lán)圖的路由定義一個(gè)動(dòng)態(tài)的前綴來(lái)停止重復(fù)工作瓷叫。
藍(lán)圖可以讓我們定義動(dòng)態(tài)和靜態(tài)的前綴。我們可以通知 Flask 在一個(gè)藍(lán)圖中的所有的路由都是以 /profile 為前綴的(這里的 /profile 只是一個(gè)示例)送巡,這就是一個(gè)靜態(tài)的前綴。至于 Facebook 示例盒卸,前綴是基于瀏覽的用戶資料而變化骗爆。無(wú)論他們?yōu)g覽哪個(gè)用戶的個(gè)人資料,我們都應(yīng)該在 URL 標(biāo)簽中顯示蔽介。這就是一個(gè)動(dòng)態(tài)的前綴摘投。
我們可以選擇在什么時(shí)候定義我們的前綴。我們可以在兩個(gè)地方中的任意一個(gè)定義前綴:當(dāng)我們實(shí)例化 Blueprint() 類或者當(dāng)我們用 app.register_blueprint() 注冊(cè)它的時(shí)候虹蓄。
# facebook/views/profile.py
from flask import Blueprint, render_template
profile = Blueprint('profile', __name__, url_prefix='/<user_url_slug>')
# [...]
{% endhighlight %}
{% highlight python %}
# facebook/__init__.py
from flask import Flask
from .views.profile import profile
app = Flask(__name__)
app.register_blueprint(profile, url_prefix='/<user_url_slug>')
盡管沒(méi)有任何技術(shù)因素限制任何一種方法犀呼,最好是在注冊(cè)的時(shí)候統(tǒng)一定義可用的前綴。這使得以后修改或者調(diào)整更加容易和方便些薇组。因?yàn)檫@個(gè)原因外臂,我建議在注冊(cè)的時(shí)候設(shè)置 url\_prefix。
我們可以在動(dòng)態(tài)前綴中使用轉(zhuǎn)換器律胀,就像在 route() 調(diào)用中一樣宋光。這個(gè)也包含了我們自定義的轉(zhuǎn)換器貌矿。當(dāng)使用了轉(zhuǎn)換器,我們可以在把前綴交給視圖之前進(jìn)行預(yù)處理罪佳。在這個(gè)例子中我們要基于傳入到我們用戶資料藍(lán)圖的 URL 中的 user\_url\_slug 來(lái)獲取用戶對(duì)象逛漫。這里我們需要使用 url\_value\_preprocessor() 裝飾一個(gè)函數(shù)來(lái)完成這個(gè)需求。
# facebook/views/profile.py
from flask import Blueprint, render_template, g
from ..models import User
# The prefix is defined on registration in facebook/\\_\\_init\\_\\_.py.
profile = Blueprint('profile', \\_\\_name\\_\\_)
@profile.url_value_preprocessor
def get_profile_owner(endpoint, values):
query = User.query.filter_by(url_slug=values.pop('user_url_slug'))
g.profile_owner = query.first_or_404()
@profile.route('/')
def timeline():
return render_template('profile/timeline.html')
@profile.route('/photos')
def photos():
return render_template('profile/photos.html')
@profile.route('/about')
def about():
return render_template('profile/about.html')
我們使用 g 對(duì)象來(lái)存儲(chǔ)用戶對(duì)象并且 g 可以在 Jinja2 模板中使用赘艳。這就意味著對(duì)于實(shí)現(xiàn)一個(gè)極其簡(jiǎn)單的系統(tǒng)的話酌毡,我們現(xiàn)在要做的就是在視圖中渲染模板。
{% facebook/templates/profile/photos.html %}
{% extends "profile/layout.html" %}
{% for photo in g.profile_owner.photos.all() %}
<img src="{{ photo.source_url }}" alt="{{ photo.alt_text }}" />
{% endfor %}
使用動(dòng)態(tài)的子域(subdomain)
許多 SaaS(軟件即服務(wù))的應(yīng)用程序目前提供用戶一個(gè)子域蕾管,用戶可以使用這個(gè)子域來(lái)訪問(wèn)他們的軟件枷踏。例如,Harvest 是一個(gè)時(shí)間追蹤管理應(yīng)用程序娇掏,它允許你從 yourname.harvestapp.com 訪問(wèn)你的控制面板呕寝。這里我將向你展示如何使用 Flask 處理像 Harvest 一樣自動(dòng)生成的子域。
對(duì)于這一部分婴梧,我們將要使用允許用戶創(chuàng)建他們自己的網(wǎng)站的應(yīng)用程序示例下梢。假設(shè)我們的應(yīng)用程序有三個(gè)藍(lán)圖,它們分別用于用戶登錄的主頁(yè)塞蹭,用戶構(gòu)建他們的網(wǎng)站的用戶管理面板以及用戶的網(wǎng)站孽江。由于這三部分是不相關(guān)的,我們用分區(qū)結(jié)構(gòu)來(lái)組織結(jié)構(gòu)番电。
sitemaker/
__init__.py
home/
__init__.py
views.py
templates/
home/
static/
home/
dash/
__init__.py
views.py
templates/
dash/
static/
dash/
site/
__init__.py
views.py
templates/
site/
static/
site/
models.py
下面的描述展示了本應(yīng)用程序中所有的藍(lán)圖岗屏。
- URL: sitemaker.com,Route: sitemaker/home漱办,描述: 只是一個(gè)普通的藍(lán)圖这刷。圍繞 index.html,about.html 以及 pricing.html 的視圖娩井,模板以及靜態(tài)文件暇屋。
- URL: bigdaddy.sitemaker.com,Route:sitemaker/site洞辣,描述:這個(gè)藍(lán)圖使用一個(gè)動(dòng)態(tài)的子域并且包含用戶網(wǎng)站的元素咐刨。 我們會(huì)在下面介紹一些用于實(shí)現(xiàn)這個(gè)藍(lán)圖的代碼。
- URL:bigdaddy.sitemaker.com/admin扬霜,Route: sitemaker/dash定鸟,描述:這個(gè)藍(lán)圖使用了一個(gè)動(dòng)態(tài)的子域和一個(gè) URL 前綴。
我們可以用定義我們 URL 前綴同樣的方式來(lái)定義我們的動(dòng)態(tài)子域著瓶。兩個(gè)選擇:在藍(lán)圖文件夾或者在頂層的 init.py 中都是可用的联予,但是我們堅(jiān)持再一次把它定義在 sitemaker/\\init.py\\ 中。
# sitemaker/\\_\\_init\\_\\_.py
from flask import Flask
from .site import site
app = Flask(\\_\\_name\\_\\_)
app.register_blueprint(site, subdomain='<site_subdomain>')
因?yàn)槲覀兪褂昧朔謱咏Y(jié)構(gòu),我們會(huì)在 sitema-ker/site/\\init\\.py 中定義藍(lán)圖躯泰。
# sitemaker/site/\\_\\_init\\_\\_py
from flask import Blueprint
from ..models import Site
# Note that the capitalized Site and the lowercase site
# are two completely separate variables. Site is a model
# and site is a blueprint.
site = Blueprint('site', __name__)
@site.url_value_preprocessor
def get_site(endpoint, values):
query = Site.query.filter_by(subdomain=values.pop('site_subdomain'))
g.site = query.first_or_404()
# Import the views after site has been defined. The views
# module will needto import 'site' so we need to make
# sure that we import views after site has been defined.
import .views
現(xiàn)在我們從數(shù)據(jù)庫(kù)中獲取了站點(diǎn)信息谭羔,我們將會(huì)把用戶的站點(diǎn)展示給正在請(qǐng)求他們子域的訪問(wèn)者。
為了讓 Flask 能和子域一起工作麦向,我們將需要指定 SERVER_NAME 配置變量瘟裸。
# config.py
SERVER_NAME = 'sitemaker.com'
使用藍(lán)圖重構(gòu)小的應(yīng)用程序
我們將會(huì)介紹把一個(gè)應(yīng)用程序重構(gòu)成使用藍(lán)圖的步驟。我們選擇一個(gè)很典型的 Flask 應(yīng)用程序并且重構(gòu)它诵竭。
config.txt
requirements.txt
run.py
U2FtIEJsYWNr/
__init__.py
views.py
models.py
templates/
static/
tests/
views.py 文件已經(jīng)增長(zhǎng)到 10,000 行的代碼话告!我們一直在拖延重構(gòu)它的時(shí)間,但是現(xiàn)在是時(shí)候重構(gòu)卵慰。views.py 文件包含我們網(wǎng)站每一部分的視圖沙郭。這些部分分別是主頁(yè),用戶控制面板裳朋,管理控制面板病线,API 和公司的博客。
步驟 1:分區(qū)或者功能鲤嫡?
這個(gè)應(yīng)用是有完全不同的部分組成送挑。例如,用戶控制面板和公司博客之間的模板和靜態(tài)文件是完全不共享的暖眼。我們將選擇分區(qū)結(jié)構(gòu)惕耕。
步驟 2:移動(dòng)一些文件
下一步我們將繼續(xù)前進(jìn),并且為我們新的應(yīng)用程序創(chuàng)建目錄樹(shù)诫肠。我們可以在一個(gè)包目錄里為每一個(gè)藍(lán)圖創(chuàng)建一個(gè)文件夾司澎。接著我們將完整地復(fù)制 views.py,static/ 和 templates/ 到每個(gè)藍(lán)圖目錄栋豫。最后挤安,我們可以從頂層包目錄中刪除它們(views.py,static/ 和 templates/)丧鸯。
config.txt
requirements.txt
run.py
U2FtIEJsYWNr/
__init__.py
home/
views.py
static/
templates/
dash/
views.py
static/
templates/
admin/
views.py
static/
templates/
api/
views.py
static/
templates/
blog/
views.py
static/
templates/
models.py
tests/
步驟 3:廢話少說(shuō)
現(xiàn)在我們可以到每一個(gè)藍(lán)圖目錄中去刪除那些不屬于該藍(lán)圖的視圖漱受,靜態(tài)文件和模板。你如何做這一步很大程度上取決你的應(yīng)用程序是如何組織結(jié)構(gòu)的骡送。
最終的結(jié)果就是每一個(gè)藍(lán)圖只有一個(gè) views.py 文件,并且 views.py 文件內(nèi)的函數(shù)只適用于本藍(lán)圖絮记。沒(méi)有兩個(gè)藍(lán)圖會(huì)為同一個(gè)路由定義一個(gè)視圖摔踱。每一個(gè) templates/ 目錄只包含在本藍(lán)圖的視圖中使用的模板。每一個(gè) static/ 目錄應(yīng)該只包含有本藍(lán)圖使用的靜態(tài)文件怨愤。
步驟 4:藍(lán)圖
這是我們把我們的目錄轉(zhuǎn)變成為藍(lán)圖的關(guān)鍵一步派敷。關(guān)鍵就是在 init.py 文件。首先,我們看看 API 藍(lán)圖的定義篮愉。
# U2FtIEJsYWNr/api/__init__.py
from flask import Blueprint
api = Blueprint(
'site',
__name__,
template_folder='templates',
static_folder='static'
)
import .views
接下來(lái)我們?cè)?U2FtIEJsYWNr 包頂層 init.py 文件里注冊(cè)這個(gè)藍(lán)圖腐芍。
# U2FtIEJsYWNr/__init__.py
from flask import Flask
from .api import api
app = Flask(__name__)
# Puts the API blueprint on api.U2FtIEJsYWNr.com.
app.register_blueprint(api, subdomain='api')
確保路由是注冊(cè)到藍(lán)圖上而不是應(yīng)用程序(app)對(duì)象上。
# U2FtIEJsYWNr/views.py
from . import app
@app.route('/search', subdomain='api')
def api_search():
pass
# U2FtIEJsYWNr/api/views.py
from . import api
@api.route('/search')
def search():
pass
步驟 5:享受
現(xiàn)在我們應(yīng)用程序比起它原來(lái)一個(gè)龐大的 views.py 文件已經(jīng)是大大地模塊化了试躏。路由的定義十分簡(jiǎn)單猪勇,因?yàn)槲覀兛梢栽诿恳粋€(gè)藍(lán)圖里面單獨(dú)定義并且可以為每個(gè)藍(lán)圖像子域和 URL 前綴一樣配置。
- 一個(gè)藍(lán)圖定義了視圖颠蕴,模板泣刹,靜態(tài)文件以及可以用于應(yīng)用程序的其它元素的集合。
- 藍(lán)圖是組織你的應(yīng)用程序的一種很好的方式犀被。
- 在分區(qū)結(jié)構(gòu)中椅您,每一個(gè)藍(lán)圖是一個(gè)視圖,模板寡键,靜態(tài)文件的集合掀泳,它們構(gòu)成了應(yīng)用程序的一部分。
- 在功能結(jié)構(gòu)中西轩,每一個(gè)藍(lán)圖只是視圖的集合员舵。所有的模板放在一起,靜態(tài)文件也一樣遭商。
- 要使用一個(gè)藍(lán)圖固灵,你首先需要定義它,接著通過(guò)調(diào)用
Flask.register\\_blueprint()
來(lái)注冊(cè)它劫流。 - 你可以定義一個(gè)動(dòng)態(tài)的 URL 前綴巫玻,它能夠用于在一個(gè)藍(lán)圖里所有的路由。
- 你也可以定義一個(gè)動(dòng)態(tài)的子域祠汇,它能夠用于一個(gè)藍(lán)圖里所有的路由仍秤。
- 使用藍(lán)圖重構(gòu)一個(gè)越來(lái)越大的應(yīng)用程序能夠用 5 個(gè)小步驟來(lái)完成。