配套視頻教程
本章將學習基於Bootstrap用戶界面框架的模板替換基礎的HTML模板。
這個Microblog應用程序已有一段時間了,或許已經注意到,並沒有花時間來美化它的頁面。所有的模板是使用的基礎樣式,沒有自定義樣式。這對於我們來說非常有用,可以專注於應用程序的實際邏輯,不用分心去編寫好看的HTML和CSS代碼。
目前已經專注於應用程序的后端部分有一段時間了。因此,本章將暫停一下,並花一些時間來學習 如何使應用程序看起來更加精致、專業。
本章將與之前的章節略有不同,因為不會像往常那樣詳細地關注Python方面。創建漂亮的網頁是一個很大的主題,而與Python web后端開發很大程度上無關,因此將討論一些基本指導和想法,將重新設計應用的外觀來研究和學習它。
CSS框架
雖然我們可以說 編碼很難,但與網頁設計師相比,我們的痛苦還是無足輕重的,畢竟網頁設計師必須讓網頁在所有Web瀏覽器上呈現良好一致外觀的模板。近年來,變得越來越好,但在某些瀏覽器上仍然存在一些模糊的錯誤或奇怪的設定,使得設計網頁的任務還是很困難。如果還要兼容屏幕限制設備(如平板電腦、智能手機)上的瀏覽器,則更加困難。
如果你像我一樣,只是一個想創建規范網頁的開發人員,沒有時間或興趣去學習底層機制,並通過編寫原生HTML和CSS來實現它,那么唯一可行的解決方案是使用CSS框架來簡化任務。通過這條路徑,將失去一些操作自由,但另一方面,我們的網頁在所有瀏覽器中會看起來很不錯,而也不需要花費太多精力。CSS框架為普通類型的用戶界面元素提供了高級CSS類的集合,其中包含預定義樣式。這些框架中的大多數還為 不能使用HTML和CSS嚴格執行的操作提供JavaScript插件。
介紹Bootstrap
最受歡迎的一個CSS框架是由Twitter建立的Bootstrap。如果想看使用這個框架設計的頁面類型,可查看文檔中的示例。
使用Bootstrap為網頁設置樣式的好處:
- 在所有主流Web瀏覽器上看起來相似;
- 處理台式機、平板電腦、手機屏幕尺寸;
- 可定制布局;
- 風格精美的導航欄、表單、按鈕、提示、彈出窗口等。
使用Bootstrap最直接方法是在基礎模板中導入 bootstrap.min.css文件。可以下載這個文件副本,並將其添加到項目中;也可以直接從CDN導入(這里查看)。然后,可以根據文檔開始使用它提供的通用CSS類。還可以導入包含框架JavaScript代碼的bootstrap.min.js文件,以便使用更高級的功能。
幸運的是,有一個名為 Flask-Bootstrap的Flask擴展,它提供一個隨時可用的基本模板,它安裝了Bootstrap框架。安裝這個擴展:版本3.3.7.1;附帶安裝dominate 2.3.1(用於使用優雅的DOM API創建和操作HTML文檔)、visitor 0.1.3(一個微型Pythonic訪客實現)。
(venv) D:\microblog>pip install flask-bootstrap
Collecting flask-bootstrap
Using cached https://files.pythonhosted.org/packages/88/53/958ce7c2aa26280b7fd7f3eecbf13053f1302ee2acb1db58ef32e1c23c2a/Flask-Bootstrap-3.3.7.1.tar.gz
Requirement already satisfied: Flask>=0.8 in d:\microblog\venv\lib\site-packages (from flask-bootstrap)
Collecting dominate (from flask-bootstrap)
Using cached https://files.pythonhosted.org/packages/43/b2/3b7d67dd59dab93ae08569384b254323516e8868b453eea5614a53835baf/dominate-2.3.1.tar.gz
Collecting visitor (from flask-bootstrap)
Using cached https://files.pythonhosted.org/packages/d7/58/785fcd6de4210049da5fafe62301b197f044f3835393594be368547142b0/visitor-0.1.3.tar.gz
Requirement already satisfied: click>=5.1 in d:\microblog\venv\lib\site-packages (from Flask>=0.8->flask-bootstrap)
Requirement already satisfied: itsdangerous>=0.24 in d:\microblog\venv\lib\site-packages (from Flask>=0.8->flask-bootstrap)
Requirement already satisfied: Werkzeug>=0.14 in d:\microblog\venv\lib\site-packages (from Flask>=0.8->flask-bootstrap)
Requirement already satisfied: Jinja2>=2.10 in d:\microblog\venv\lib\site-packages (from Flask>=0.8->flask-bootstrap)
Requirement already satisfied: MarkupSafe>=0.23 in d:\microblog\venv\lib\site-packages (from Jinja2>=2.10->Flask>=0.8->flask-bootstrap)
Installing collected packages: dominate, visitor, flask-bootstrap
Running setup.py install for dominate ... done
Running setup.py install for visitor ... done
Running setup.py install for flask-bootstrap ... done
Successfully installed dominate-2.3.1 flask-bootstrap-3.3.7.1 visitor-0.1.3
使用Flask-Bootstrap
Flask-Bootstrap和大多數其他Flask擴展一樣,需要進行初始化:
app/init.py:添加Flask-Bootstrap實例
# ...
from flask_bootstrap import Bootstrap
app = Flask(__name__)
# ...
mail = Mail(app)
bootstrap = Bootstrap(app)
#...
初始化擴展后,bootstrap/base.html模板
變為可用,並可以使用extends子句
從應用程序模板中引用。
但是記得,我已經使用了extends子句
繼承自己的基礎模板,這允許將頁面的公共部分放在一個地方。我的 base.html模板
定義了導航欄,其中包含一些鏈接,還導出了一個content塊
。目前為止,應用程序中所有其他模板都是從基礎模板繼承,並為content塊
提供頁面的主要內容。
那么,該如何適應 Bootstrap基礎模板呢?想法是使用三級層次結構,而不是兩層。bootstrap/base.html模板
提供頁面的基本結構,其中包含Bootstrap框架文件。這個模板 為了派生模板導出幾個塊,如title
、navbar
、content
(查看完整的 塊列表)。我將更改我的base.html
模板,讓它從bootstrap/base.html模板
派生,並為title
、navbar
、content
塊提供實現。反過來,base.html
將為了它派生模板去定義頁面內容而導出自己的app_content
塊。
下方將看到base.html
在修改后如何從Bootstrap基礎模板繼承。
app/templates/base.html:重新設計基礎模板
{% extends "bootstrap/base.html" %}
{% block title %}
{% if title %}
{{ title }} - Microblog
{% else %}
Welcome to Microblog
{% endif %}
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<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="{{ url_for('index') }}">Microblog</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('explore') }}">Explore</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_anonymous %}
<li><a href="{{ url_for('login') }}">Login</a></li>
{% else %}
<li><a href="{{ url_for('user', username=current_user.username) }}">Profile</a></li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
{% endblock %}
{% block content %}
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info" role="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{# application content needs to be provided in the app_content block #}
{% block app_content %}{% endblock %}
</div>
{% endblock %}
在上述代碼中,可看到如何從bootstrap/base.html
派生這個模板,然后分別實現頁面標題、導航欄、頁面內容 三個塊。
title 塊
使用 <title>
標簽 定義將用於頁面標題的文本。對於個塊,只是移動了在原始基礎模板中<title>
標簽內的邏輯。
navbar 塊
是可選塊,可用於定義導航欄。對於這個塊,在Bootstrap導航欄文檔中的改寫了這個示例,以便它在左端包含站點標記,接着是 Home 和Explore鏈接。然后,添加了與頁面右邊框對齊的Profile和Login或Logout鏈接。
最后,在content塊
中定義了一個頂級容器,在其中編寫了渲染 閃爍消息的邏輯,現在它們顯示為Bootstrap警報。接着是一個新的app_content塊
,它被定義為 只有派生模板才能定義它們自己的內容。
所有頁面模板的原始版本 都在名為content
的塊中定義它們的內容。如上所示,名為content
的塊被Flask-Bootstrap所使用,因此將內容塊 重命名為app_content
。所以,我們所有的模板都必須重命名才能去使用app_content塊 作為它們的內容塊。例如,如下是404.html
模板 如何修改版本的演示:
{% extends "base.html" %}
{% block app_content %}
<h1>File Not Found</h1>
<p>
<a href="{{ url_for('index') }}">Back</a>
</p>
{% endblock %}
剩余的所有其他模板一一按上例修改即可。
渲染Bootstarp表單
Flask-Bootstrap有一個很棒的工作是渲染表單。Flask-Bootstrap不用逐一設置表單字段的樣式,而是附帶一個宏(macro),它接受一個Flask-WTF表單對象作為參數,並使用Bootstrap樣式呈現完整的表單。
下方將看到重新設計的register.html
模板作為一個示例:
app/templates/register.html:
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Register</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
上述代碼中,在頂部聲明的import
工作原理與在模板上邊的一個Python import
類似。這還添加了一個wtf.quick_form()
宏,它在一行簡單代碼中呈現完整的表單,包括支持顯示驗證錯誤,並且所有樣式都適合Bootstrap框架。
以下展示為應用程序中的其他表單
所做的更改:
app/templates/edit_profile.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Edit Profile</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
app/templates/index.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Hello,{{ current_user.username }}!</h1>
{% if form %}
{{ wtf.quick_form(form) }}
<br>
{% endif %}
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
<nav aria-label="...">
<ul class="pager">
<li class="previous{% if not prev_url %} disabled{% endif %}">
<a href="{{ prev_url or '#' }}">
<span aria-hidden="true">←</span> Newer posts
</a>
</li>
<li class="next{% if not next_url %} disabled{% endif %}">
<a href="{{ next_url or '#' }}">
Older posts <span aria-hidden="true">→</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
app/templates/login.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Sign In</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
<br>
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
<p>
Forgot Your Password?
<a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>
</p>
{% endblock %}
app/templates/reset_password.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Reset Your Password</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
app/templates/reset_password_request.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Reset Password</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
app/templates/user.html
{% extends "base.html" %}
{% block app_content %}
<table class="table table-hover">
<tr>
<td width="256px"><img src="{{ user.avatar(256) }}"></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 %}
{% if user == current_user %}
<p>
<a href="{{ url_for('edit_profile') }}">Edit your profile</a>
</p>
{% elif not current_user.is_following(user) %}
<p>
<a href="{{ url_for('follow', username=user.username) }}">Follow</a>
</p>
{% else %}
<p>
<a href="{{ url_for('unfollow', username=user.username) }}">Unfollow</a>
</p>
{% endif %}
</td>
</tr>
</table>
{% for post in posts %}
{% include '_post.html' %}
{% endfor%}
<nav aria-label="...">
<ul class="pager">
<li class="previous{% if not prev_url %} disabled{% endif %}">
<a href="{{ prev_url or '#' }}">
<span aria-hidden="true">←</span> Newer posts
</a>
</li>
<li class="next{% if not next_url %} disabled{% endif %}">
<a href="{{ next_url or '#' }}">
Older posts <span aria-hidden="true">→</span>
</a>
</li>
</ul>
</nav>
{% endblock%}
博客帖子的渲染
呈現單個博客帖子的表示邏輯被抽象為 名為_post.html
的子模板。對其小調整,以便在Bootstrap下看起來更好。 app/templates/_post.html:
<table class="table table-hover">
<tr>
<td width="70px">
<a href="{{ url_for('user', username=post.author.username) }}">
<img src="{{ post.author.avatar(70) }}" />
</a>
</td>
<td>
<a href="{{ url_for('user', username=post.author.username) }}">
{{ post.author.username }}
</a>
says:
<br>
{{ post.body }}
</td>
</tr>
</table>
渲染分頁鏈接
分頁鏈接是Bootstrap提供支持的另一個領域。為此,再一次參考Bootstrap文檔,並調整其中一個示例。app/templates/index.html:重新設計的分頁鏈接
[← Newer posts]
<li class="next{% if not next_url %} disabled{% endif %}">
<a href="{{ next_url or '#' }}">
Older posts <span aria-hidden="true">→</span>
</a>
</li>
</ul>
</nav>
不過注意,在上述實現中,當某個方向沒有更多內容時,將運用禁用狀態,而不是隱藏下一個或上一個鏈接,這將使鏈接顯示為灰色。
類似的更改也需要運用於user.html
,這個更改在上一小節上展示了。
目前為止,項目結構:
microblog/
app/
templates/
email/
reset_password.html
reset_password.txt
_post.html
404.html
500.html
base.html
edit_profile.html
index.html
login.html
register.html
reset_password.html
reset_password_request.html
user.html
__init__.py
email.py
errors.py
forms.py
models.py
routes.py
logs/
microblog.log
migrations/
venv/
app.db
config.py
microblog.py
tests.py
參考
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-facelift