本章將為應(yīng)用添加個(gè)人主頁(yè)肝箱。個(gè)人主頁(yè)用來(lái)展示用戶的相關(guān)信息彰导,其個(gè)人信息可由本用戶編輯矢腻。我將為你展示如何動(dòng)態(tài)地生成每個(gè)用戶的主頁(yè),并提供一個(gè)編輯頁(yè)面給他們來(lái)更新個(gè)人信息学搜。
個(gè)人主頁(yè)
作為創(chuàng)建個(gè)人主頁(yè)的第一步娃善,先要編寫(xiě)一個(gè)與個(gè)人主頁(yè)對(duì)應(yīng)的視圖函數(shù)。
# app\routes.py
from flask_login import login_required
# ...
@main_routes.route('/user/<username>')
@login_required
def user(username):
user = User.query.filter_by(username=username).first_or_404()
posts = [
{'author': user, 'body': 'Test post #1'},
{'author': user, 'body': 'Test post #2'}
]
return render_template('user.html', user=user, posts=posts)
我們注意到視圖函數(shù)的 @app.route
裝飾器多了被尖括號(hào) <>
包裹的部分瑞佩。 現(xiàn)在 <username>
是動(dòng)態(tài)的聚磺,F(xiàn)lask 將接受該部分 URL 中的任何文本,并以 username
作為參數(shù)名傳遞給視圖函數(shù)钉凌。
例如咧最,如果瀏覽器請(qǐng)求 URL /user/susan
,則 user(username)
視圖函數(shù)將被調(diào)用御雕,其參數(shù) username
被設(shè)置為 'susan'
。
另外這個(gè)視圖函數(shù)只能被已登錄的用戶訪問(wèn)滥搭,所以添加了 @login_required
裝飾器酸纲。
個(gè)人主頁(yè)的視圖函數(shù)當(dāng)然需要查先找用戶實(shí)例再返回其個(gè)人資料,這里我們使用 username
作為唯一標(biāo)識(shí)符來(lái)獲得相應(yīng)的用戶對(duì)象瑟匆。這里我們使用 first_or_404()
方法闽坡,在沒(méi)有與 URL 中傳來(lái)的 username
相匹配的用戶實(shí)例時(shí),它將自動(dòng)發(fā)送 404 error 給客戶端愁溜。
以這種方式執(zhí)行查詢疾嗅,省去了檢查用戶是否處在的邏輯,因?yàn)楫?dāng)用戶名不存在于數(shù)據(jù)庫(kù)中時(shí)冕象,函數(shù)將不會(huì)返回代承,而是會(huì)引發(fā) 404 異常。
如果執(zhí)行數(shù)據(jù)庫(kù)查詢沒(méi)有觸發(fā) 404 錯(cuò)誤渐扮,那么這意味著找到了與給定用戶名匹配的用戶论悴。接下來(lái)掖棉,我們編寫(xiě)一個(gè)新的 user.html
模板,傳入用戶對(duì)象并渲染出來(lái)膀估。
# app\templates\user.html
{% extends "base.html" %}
{% block content %}
<h1>User: {{ user.username }}</h1>
<hr>
{% for post in posts %}
<p>
{{ post.author.username }} says: <b>{{ post.body }}</b>
</p>
{% endfor %}
{% endblock %}
個(gè)人主頁(yè)完成了幔亥,在頂部的導(dǎo)航欄中添加個(gè)人主頁(yè)的入口鏈接,以便用戶可以查看自己的個(gè)人資料:
# app\templates\base.html
<div>
Microblog:
<a href="{{ url_for('main.index') }}">Home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('main.login') }}">Login</a>
{% else %}
<a href="{{ url_for('main.user', username=current_user.username) }}">
Profile
</a>
<a href="{{ url_for('main.logout') }}">Logout</a>
{% endif %}
</div>
注意這里生成個(gè)人主頁(yè)的 url_for()
函數(shù)察纯,它接受一個(gè)動(dòng)態(tài)參數(shù)帕棉,所以它可以根據(jù)當(dāng)前登錄用戶 current_user
對(duì)象的 username
動(dòng)態(tài)生成個(gè)人主頁(yè)鏈接。
現(xiàn)在我們的個(gè)人主頁(yè)頁(yè)面如下:
靜態(tài)文件
我們的網(wǎng)站并不僅僅有來(lái)自數(shù)據(jù)庫(kù)中存儲(chǔ)的數(shù)據(jù)記錄饼记,它也包含各種靜態(tài)文件笤昨,可以是圖片、視頻握恳,也可以是日后要用到的 Javascript瞒窒、CSS 文件等。在這里我們先使用靜態(tài)文件的方法管理用戶頭像的圖片乡洼。
Flask 會(huì)在 static
文件夾里尋找靜態(tài)文件崇裁,現(xiàn)在我們?cè)?app
文件夾內(nèi)創(chuàng)建名為 static
的文件夾,靜態(tài)文件相關(guān)的內(nèi)容放置在這里束昵。為了更進(jìn)一步細(xì)化靜態(tài)文件的分類(lèi)管理拔稳,我們繼續(xù)創(chuàng)建 upload/avatar
路徑。
文件組織結(jié)構(gòu)如下:
microblog/
app/
static/
upload/
avatar/
templates/
routes.py
# ...
microblog.py
# ...
我們先在 app\static\upload\avatar
文件夾內(nèi)放置喜歡的圖片作為頭像使用锹雏,為了方便查找到對(duì)應(yīng)的頭像巴比,我們用用戶名來(lái)為圖片重命名。為了符合審美礁遵,最好使用正方形的圖片且文件后綴名為 .png
轻绞。
那么怎么訪問(wèn)靜態(tài)文件呢?假設(shè)現(xiàn)在有 diego.png
的圖片文件佣耐,那么訪問(wèn)它的路徑就是 /static/upload/avatar/diego.png
政勃。
當(dāng)然更好的方法是使用 url_for()
方法:
url_for("static", filename="/upload/avatar/diego.png")
用靜態(tài)文件構(gòu)建頭像
為了踐行低耦合原則,我們先在 config.py
中增添一些配置:
# config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
# ...
AVATAR_ROOT = os.path.join(basedir, 'app', 'static', 'upload', 'avatar')
AVATAR_URL = '/upload/avatar/'
AVATAR_EXTENSION = 'png'
這兩條并非 Flask 官方或者任何第三方庫(kù)所必須的配置項(xiàng)兼砖,只是我為了方便個(gè)人添加上去的奸远,可以在其他模塊引入后作為常量使用。
AVATAR_ROOT
記錄頭像文件夾的絕對(duì)路徑讽挟;AVATAR_URL
記錄了頭像文件夾相對(duì)于 static
文件夾的路徑懒叛;如果以后位置變化了,直接修改配置文件的相應(yīng)配置即可耽梅,不需要在代碼里每一處再修改 薛窥;AVATAR_EXTENSION
記錄了頭像圖片文件的后綴名。
現(xiàn)在在數(shù)據(jù)庫(kù)模型的 User
類(lèi)中添加 avatar()
方法用于生產(chǎn)用戶頭像的靜態(tài)文件路徑:
import os
from flask import url_for
from config import Config
class User(db.Model, UserMixin):
# ...
def avatar(self):
root = Config.AVATAR_ROOT
url = Config.AVATAR_URL
extension = Config.AVATAR_EXTENSION
filename = "{}/{}.{}".format(url, self.username, extension)
avatar_path = os.path.join(root, '{}.{}'.format(self.username, extension))
if os.path.exists(avatar_path):
return url_for("static", filename=filename)
else:
filename = "{}{}.{}".format(url, 'default', extension)
return url_for("static", filename=filenam
現(xiàn)在我們把之前在配置中設(shè)置的 AVATAR_ROOT
和 AVATAR_URL
等配置項(xiàng)引入進(jìn)了作為常量來(lái)使用了褐墅。本函數(shù)我們通過(guò)用戶名來(lái)構(gòu)建用戶對(duì)應(yīng)的頭像文件路徑拆檬,如果對(duì)應(yīng)的文件不存在洪己,則返回一個(gè)默認(rèn)頭像。
下一步我們改寫(xiě)個(gè)人主頁(yè)的 HTML 模板文件竟贯,來(lái)顯示用戶的頭像:
# app\templates\user.html
{% extends "base.html" %}
{% block content %}
<h1>User: {{ user.username }}</h1>
<img src="{{ user.avatar() }}" weight="80" height="80">
<hr>
{% for post in posts %}
<p>
{{ post.author.username }} says: <b>{{ post.body }}</b>
</p>
{% endfor %}
{% endblock %}
Jinja2 模板語(yǔ)法里除了可以調(diào)用對(duì)象的屬性外答捕,還能直接調(diào)用對(duì)象的方法,這里我們直接使用了 user
對(duì)象的 avatar()
方法屑那。
現(xiàn)在我們的個(gè)人主頁(yè)是這個(gè)樣子:
使用 Gravatar 構(gòu)建頭像
上一節(jié)我們嘗試用本地的靜態(tài)文件來(lái)實(shí)現(xiàn)頭像系統(tǒng)拱镐,這意味著我們需要把服務(wù)器空間里相當(dāng)一部分空間用于存放圖片,在本節(jié)持际,我們使用一個(gè)第三方服務(wù) Gravatar 來(lái)實(shí)現(xiàn)頭像模塊沃琅,上一節(jié)的代碼我們暫且清空。
Gravatar(https://en.gravatar.com/)是一項(xiàng)全球通用頭像服務(wù)蜘欲,它允許我們把頭像文件存儲(chǔ)到 Gravatar 服務(wù)器中并在其他網(wǎng)站或應(yīng)用里使用益眉,只要提供你與這個(gè)頭像關(guān)聯(lián)的 email 地址,就能夠顯示出你的 Gravatar 頭像來(lái)姥份。
Gravatar 頭像的基礎(chǔ)用法是 https://www.gravatar.com/avatar/
+ 電子郵箱的 MD5 哈希值郭脂。URL 查詢字符串 s
定義圖片大小,查詢字符串 d
定義默認(rèn)頭像澈歉。
以我的頭像為例:https://www.gravatar.com/avatar/222db16df13a040d59400787573725bb?s=128&d=robohash
更多用法展鸡,我們參見(jiàn):Gravatar 文檔。
現(xiàn)在在數(shù)據(jù)庫(kù)模型的 User
類(lèi)中編寫(xiě) avatar()
方法埃难,用來(lái)生成 Gravatar 頭像 URL莹弊。
# app\models.py
from hashlib import md5
# ...
class User(db.Model, UserMixin):
# ...
def avatar(self, size):
digest = md5(self.email.lower().encode('utf-8')).hexdigest()
return 'https://www.gravatar.com/avatar/{}?s={}&d=robohash' \
.format(digest, size)
我們先把 email 轉(zhuǎn)化為小寫(xiě)字母,再把字符串編碼為字節(jié)后生成 MD5 哈希值涡尘,用拼接字符串的方式生成頭像的 URL忍弛。這里用 size
參數(shù)設(shè)置頭像大小,如果頭像不存在則隨機(jī)生成一個(gè)機(jī)器人圖片來(lái)做頭像悟衩,這是 URL 查詢字符串 &d=robohash
定義的剧罩。
下一步把頭像圖片插入到個(gè)人主頁(yè)的模板中:
# app\templates\user.html
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td><h1>User: {{ user.username }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
<table>
<tr valign="top">
<td><img src="{{ post.author.avatar(36) }}"></td>
<td>{{ post.author.username }} says:<br>{{ post.body }}</td>
</tr>
</table>
{% endfor %}
{% endblock %}
使用 User
類(lèi)來(lái)返回頭像 URL 的好處是,如果有一天我不想繼續(xù)使用 Gravatar 頭像了座泳,我可以重寫(xiě) avatar()
方法來(lái)返回其他頭像服務(wù)網(wǎng)站的 URL。
現(xiàn)在用戶個(gè)人主頁(yè)構(gòu)建完成:
使用 Jinja2 子模板
在個(gè)人主頁(yè)幕与,我們使用頭像和文字組合的方式來(lái)展示用戶動(dòng)態(tài)挑势。如果我想在主頁(yè)也使用一樣的風(fēng)格來(lái)布局,簡(jiǎn)單直接的做法就是把 user.html
的相關(guān)代碼復(fù)制粘貼到 index.html
啦鸣,但如果以后需要修改用戶動(dòng)態(tài)的布局潮饱,就必須同時(shí)修改 user.html
和 index.html
兩個(gè)模板文件。
聰明的做法是把可以重用的用戶動(dòng)態(tài)部分作為子模板诫给,然后在 user.html
和 index.html
模板中引用它香拉。
首先啦扬,創(chuàng)建這個(gè)只有一條用戶動(dòng)態(tài) HTML 元素的子模板。 我將其命名為 app/templates/_post.html
凫碌, _
前綴只是一個(gè)命名約定扑毡,可以幫助我標(biāo)記哪些模板文件是子模板。
# app/templates/_post.html
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td><h1>User: {{ user.username }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
{% endblock %}
在 user.html
模板中使用了 Jinja2
的 include
語(yǔ)句來(lái)調(diào)用該子模板:
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td><h1>User: {{ user.username }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
{% endblock %}
應(yīng)用的主頁(yè)還沒(méi)有完善盛险,所以現(xiàn)在我不打算在其中添加這個(gè)功能瞄摊。
更多有趣的個(gè)人資料
我們繼續(xù)豐富用戶個(gè)人主頁(yè)的內(nèi)容,我會(huì)新增用戶的自我介紹并在個(gè)人主頁(yè)苦掘。同時(shí)也將跟蹤每個(gè)用戶最后一次訪問(wèn)該網(wǎng)站的時(shí)間换帜,并顯示在他們的個(gè)人主頁(yè)上。
為了支持所有這些額外的信息鹤啡,首先需要做的是用兩個(gè)新的字段擴(kuò)展數(shù)據(jù)庫(kù)中的用戶表:
# app\models.py
class User(UserMixin, db.Model):
# ...
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
每次數(shù)據(jù)庫(kù)模型被修改時(shí)惯驼,都需要生成數(shù)據(jù)庫(kù)遷移:
(venv) $ flask db migrate -m "new fields in user model"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'user.about_me'
INFO [alembic.autogenerate.compare] Detected added column 'user.last_seen'
Generating ...\migrations\versions\cf1611920c49_new_fields_in_user_model.py ... done
migrate
命令的輸出表示一切正確運(yùn)行,因?yàn)樗@示 User
類(lèi)中的兩個(gè)新字段已被檢測(cè)到递瑰。 現(xiàn)在我可以將此更改應(yīng)用于數(shù)據(jù)庫(kù):
(venv) $ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade 03f1db8e73ea -> cf1611920c49,new fields in user model
現(xiàn)在數(shù)據(jù)庫(kù)已經(jīng)修改完成祟牲。
下一步,將會(huì)把新增的兩個(gè)字段增加到個(gè)人主頁(yè)中:
# app\templates\user.html
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td>
<h1>User: {{ user.username }}</h1>
{% if user.about_me %}
<p>{{ user.about_me }}</p>
{% endif %}
{% if user.last_seen %}
<p>Last seen on: {{ user.last_seen }}</p>
{% endif %}
</td>
</tr>
</table>
<hr>
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
{% endblock %}
新增的兩個(gè)字段使用了 Jinja2 的 if
語(yǔ)句泣矛,設(shè)置了字段存在才渲染出來(lái)疲眷,現(xiàn)在字段為空,所以暫時(shí)看不見(jiàn)它您朽。
記錄用戶的最后訪問(wèn)時(shí)間
現(xiàn)在我們實(shí)現(xiàn) last_seen
字段狂丝。容易想到這樣的邏輯:一旦某個(gè)用戶向服務(wù)器發(fā)送請(qǐng)求,就將當(dāng)前時(shí)間寫(xiě)入到這個(gè)字段哗总。
為每個(gè)視圖函數(shù)都添加 last_seen
字段的邏輯几颜,顯然這不是合理的做法。在視圖函數(shù)處理請(qǐng)求之前執(zhí)行一段代碼邏輯在 Web 應(yīng)用中十分常見(jiàn)讯屈, 我們利用 Flask 一個(gè)內(nèi)置功能鉤子函數(shù)來(lái)實(shí)現(xiàn)它蛋哭。
我們?cè)?app\routes.py
添加鉤子函數(shù) before_request
:
# app\routes.py
from datetime import datetime
@main_routes.before_request
def before_request():
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
使用了 before_request
裝飾器之后,每一次請(qǐng)求前 before_request
函數(shù)內(nèi)的代碼都會(huì)執(zhí)行涮母。這里會(huì)先檢查當(dāng)前是否有登錄的用戶谆趾,如果當(dāng)前為已登錄用戶,則更新它的 last_seen
字段為當(dāng)前 UTC 時(shí)間叛本。
現(xiàn)在可以在個(gè)人主頁(yè)看見(jiàn)最后訪問(wèn)時(shí)間了沪蓬,它可能和你所在時(shí)區(qū)的實(shí)際時(shí)間有所不同,我們可以很容易地用 Python 做時(shí)區(qū)轉(zhuǎn)換来候,讓在不同地區(qū)的用戶以當(dāng)?shù)貢r(shí)間格式來(lái)顯示□尾妫現(xiàn)在我們暫且不實(shí)現(xiàn)這個(gè)功能。
個(gè)人資料編輯器
這一節(jié)的操作和前面很類(lèi)似,需要給用戶一個(gè)表單云挟,讓他們通過(guò)表單更改個(gè)人介紹或用戶名梆砸。并存儲(chǔ)在新的 about_me
字段中。 現(xiàn)在先編寫(xiě)一個(gè)表單類(lèi)吧:
# app\forms.py
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length
# ...
class EditProfileForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])
submit = SubmitField('Submit')
對(duì)于 about_me
字段园欣,使用 TextAreaField
類(lèi)型帖世,這是一個(gè)多行輸入文本框,用戶可以在其中輸入文本俊庇。為了驗(yàn)證這個(gè)字段的長(zhǎng)度狮暑,我使用了 Length
驗(yàn)證器,它將確保輸入的文本在 0 到 140 個(gè)字符之間辉饱。
該表單的渲染模板代碼如下:
# app\templates\edit_profile.html
{% extends "base.html" %}
{% block content %}
<h1>Edit Profile</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.about_me.label }}<br>
{{ form.about_me(cols=50, rows=4) }}<br>
{% for error in form.about_me.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
最后一步搬男,使用視圖函數(shù)將它們結(jié)合起來(lái):
# app\routes.py
from app.forms import EditProfileForm
@main_routes.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
form = EditProfileForm()
if form.validate_on_submit():
current_user.username = form.username.data
current_user.about_me = form.about_me.data
db.session.commit()
flash('Your changes have been saved.')
return redirect(url_for('main.edit_profile'))
elif request.method == 'GET':
form.username.data = current_user.username
form.about_me.data = current_user.about_me
return render_template(
'edit_profile.html',
title='Edit Profile',
form=form
)
這個(gè)視圖函數(shù)處理表單的方式和其他的視圖函數(shù)略有不同。當(dāng)瀏覽器提交的表單通過(guò)了驗(yàn)證 validate_on_submit()
返回 True
彭沼,將表單中的數(shù)據(jù)復(fù)制到用戶對(duì)象中缔逛,然后將對(duì)象寫(xiě)入數(shù)據(jù)庫(kù)。
但是當(dāng) validate_on_submit()
返回 False
時(shí)姓惑,可能是由于兩個(gè)不同的原因褐奴。一個(gè)是因?yàn)闉g覽器剛剛發(fā)送了一個(gè) GET
請(qǐng)求,這時(shí)需要用存儲(chǔ)在數(shù)據(jù)庫(kù)中的數(shù)據(jù)預(yù)填充字段于毙,以確保這些表單字段具有用戶的當(dāng)前數(shù)據(jù)敦冬。
第二是瀏覽器發(fā)送含表單數(shù)據(jù)的 POST
請(qǐng)求,但該數(shù)據(jù)中的某些內(nèi)容無(wú)效唯沮。這個(gè)時(shí)候我們只要留在原地不動(dòng)脖旱,讓驗(yàn)證器的提示信息出現(xiàn)在網(wǎng)頁(yè)上即可。
最后將個(gè)人資料編輯頁(yè)面的鏈接添加到個(gè)人主頁(yè)介蛉,以便用戶使用:
{% if user == current_user %}
<p>
<a href="{{ url_for('main.edit_profile') }}">Edit your profile</a>
</p>
{% endif %}
請(qǐng)注意這里使用的 if
語(yǔ)句萌庆,它確保編輯個(gè)人資料的鏈接只在瀏覽自己主頁(yè)時(shí)候才出現(xiàn)。
優(yōu)化應(yīng)用結(jié)構(gòu)
回顧我們現(xiàn)在編寫(xiě)的網(wǎng)站應(yīng)用币旧,現(xiàn)在已經(jīng)實(shí)現(xiàn)了比較多的功能践险,隨著開(kāi)發(fā)的深入,其結(jié)構(gòu)也必將變得臃腫吹菱,現(xiàn)在有必要對(duì)其進(jìn)行優(yōu)化巍虫。
我們現(xiàn)在可以按照功能把應(yīng)用拆成兩個(gè)子模塊:一部分稱之為 main
,也就是應(yīng)用的主要功能模塊鳍刷,我會(huì)把跟用戶動(dòng)態(tài)相關(guān)的功能邏輯放在這個(gè)模塊里垫言,如當(dāng)前的 index
主頁(yè)和以后實(shí)現(xiàn)的用戶發(fā)布動(dòng)態(tài)的功能。
另一個(gè)部分叫 auth
倾剿,我會(huì)把跟用戶相關(guān)的功能,比如用戶注冊(cè)、用戶登錄前痘、用戶個(gè)人主頁(yè)等功能邏輯放到該模塊凛捏。
兩個(gè)不同子模塊都會(huì)放在 app
路徑下,包裝成 Python 的包芹缔,我會(huì)分別用不同的藍(lán)圖來(lái)對(duì)兩個(gè)子模塊進(jìn)行組織坯癣。
拆分后文檔結(jié)構(gòu)如下:
app/
auth/ # 與用戶相關(guān)的邏輯
__init__.py
forms.py
routes.py
main/ # 與動(dòng)態(tài)相關(guān)的邏輯
__init__.py
routes.py
templates/
auth\ # 與用戶相關(guān)的模板文件
base.html
# ...
__init__.py
models.py
migrations/
venv/
app.db
config.py
microblog.py
Blueprints
在 Flask 中,blueprint
是代表應(yīng)用子集的邏輯結(jié)構(gòu)最欠。blueprint
可以包括路由示罗,視圖函數(shù),表單芝硬,模板和靜態(tài)文件等元素蚜点。如果在單獨(dú)的 Python 包中編寫(xiě) blueprint
,那么你將擁有一個(gè)封裝了應(yīng)用特定功能的組件拌阴。Blueprint
的內(nèi)容最初處于休眠狀態(tài)绍绘。為了關(guān)聯(lián)這些元素,blueprint
需要在應(yīng)用中注冊(cè)迟赃。
我們會(huì)現(xiàn)在子模塊的 __init__.py
中定義該模塊的 blueprint
陪拘;__init__.py
能讓一個(gè)文件夾變成一個(gè) Python 的包,從而用 import
語(yǔ)句調(diào)用纤壁。比如在 app\main\__init__.py
內(nèi)定義的代碼左刽,我們用 from app.main import xxx
就可引入。
現(xiàn)在按照這個(gè)原則來(lái)拆分改寫(xiě)我們的應(yīng)用酌媒。
編寫(xiě) app\main\__init__.py
:
# app\main\__init__.py
from flask import Blueprint
main_routes = Blueprint('main', __name__)
這里我們?cè)诙x一個(gè) blueprint
名為 main_routes
欠痴,main
子模塊下的路由都注冊(cè)在該藍(lán)圖下。
編寫(xiě) app\main\routes.py
:
# app\main\routes.py
from datetime import datetime
from flask import (
render_template,
)
from flask_login import current_user
from app import db
from app.models import User, Post
from app.main import main_routes
@main_routes.before_request
def before_request():
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
@main_routes.route('/')
@main_routes.route('/index')
def index():
posts = Post.query.all()
return render_template(
'index.html',
title='Home Page',
posts=posts
)
我們只是把跟用戶動(dòng)態(tài)相關(guān)的視圖函數(shù)移動(dòng)到這個(gè)文件下馍佑,然后把剛才創(chuàng)建的 main_routes
引入斋否,再把視圖函數(shù)注冊(cè)到該藍(lán)圖之下。
按照同樣的邏輯拭荤,我們完成 auth
子模塊的拆分茵臭。
# app\auth\__init__.py
from flask import Blueprint
auth_routes = Blueprint('auth', __name__)
app\auth\routes.py
里的邏輯參照 main
的部分,把視圖函數(shù)注冊(cè)到 auth_routes
中舅世,由于代碼過(guò)于冗長(zhǎng)旦委,就不一一列出,參見(jiàn)源碼即可雏亚。forms
里的表單類(lèi)由于全部都是與用戶相關(guān)的缨硝,不做拆分直接移動(dòng)到 app\auth\forms.py
。
接下來(lái)我們也按照功能分類(lèi)的原則罢低,對(duì) templates
中的模板文件進(jìn)行分類(lèi)查辩。新建一個(gè) app\templates\auth
文件夾胖笛,edit_profile.html
、login.html
宜岛、register.html
长踊、user.html
這些和用戶功能相關(guān)的模板文件放置其中。其它的模板文件仍然保留在 templates
目錄萍倡。
修改為成后使用 render_template
調(diào)用模板文件時(shí)候就要加上其相對(duì)路徑身弊,如:'auth/login.html'
。
下一個(gè)任務(wù)就是改寫(xiě) HTML 模板文件的 url_for()
列敲,由于我們把用戶相關(guān)的視圖函數(shù)注冊(cè)到 auth_routes
并命名為 'auth'
阱佛;我們需要把原來(lái) url_for('main.login')
改為 url_for('auth.login')
;同樣地 logout
戴而、register
等路由也同樣處理凑术。
最后一步,在工廠函數(shù)引入新的藍(lán)圖并注冊(cè)到 app
中:
# app\__init__.py
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
def create_app():
app = Flask(__name__)
# 加載配置
app.config.from_object(Config)
# 初始化各種擴(kuò)展庫(kù)
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
# 引入藍(lán)圖并注冊(cè)
from app.main.routes import main_routes
app.register_blueprint(main_routes)
from app.auth.routes import auth_routes
app.register_blueprint(auth_routes)
return app
from app import models
現(xiàn)在應(yīng)用結(jié)構(gòu)的修改已經(jīng)完成填硕,接下來(lái)我會(huì)按照這個(gè)框架繼續(xù)為網(wǎng)站增添新功能麦萤,如果按照功能對(duì)其拆分,如果有比較獨(dú)立的新功能需要開(kāi)發(fā)扁眯,還將增添新的子模塊壮莹。
本章源碼:https://github.com/SingleDiego/Flask-Tutorial-Source-Code/tree/SingleDiego-patch-06