16.flask博客項目實戰十一之Bootstrap美化站點


配套視頻教程

本文B站配套視頻教程

本章將學習基於Bootstrap用戶界面框架的模板替換基礎的HTML模板。

這個Microblog應用程序已有一段時間了,或許已經注意到,並沒有花時間來美化它的頁面。所有的模板是使用的基礎樣式,沒有自定義樣式。這對於我們來說非常有用,可以專注於應用程序的實際邏輯,不用分心去編寫好看的HTML和CSS代碼。

目前已經專注於應用程序的后端部分有一段時間了。因此,本章將暫停一下,並花一些時間來學習 如何使應用程序看起來更加精致、專業。

本章將與之前的章節略有不同,因為不會像往常那樣詳細地關注Python方面。創建漂亮的網頁是一個很大的主題,而與Python web后端開發很大程度上無關,因此將討論一些基本指導和想法,將重新設計應用的外觀來研究和學習它。

CSS框架

雖然我們可以說 編碼很難,但與網頁設計師相比,我們的痛苦還是無足輕重的,畢竟網頁設計師必須讓網頁在所有Web瀏覽器上呈現良好一致外觀的模板。近年來,變得越來越好,但在某些瀏覽器上仍然存在一些模糊的錯誤或奇怪的設定,使得設計網頁的任務還是很困難。如果還要兼容屏幕限制設備(如平板電腦、智能手機)上的瀏覽器,則更加困難。

如果你像我一樣,只是一個想創建規范網頁的開發人員,沒有時間或興趣去學習底層機制,並通過編寫原生HTML和CSS來實現它,那么唯一可行的解決方案是使用CSS框架來簡化任務。通過這條路徑,將失去一些操作自由,但另一方面,我們的網頁在所有瀏覽器中會看起來很不錯,而也不需要花費太多精力。CSS框架為普通類型的用戶界面元素提供了高級CSS類的集合,其中包含預定義樣式。這些框架中的大多數還為 不能使用HTML和CSS嚴格執行的操作提供JavaScript插件。

介紹Bootstrap

最受歡迎的一個CSS框架是由Twitter建立的Bootstrap。如果想看使用這個框架設計的頁面類型,可查看文檔中的示例

使用Bootstrap為網頁設置樣式的好處:

  1. 在所有主流Web瀏覽器上看起來相似;
  2. 處理台式機、平板電腦、手機屏幕尺寸;
  3. 可定制布局;
  4. 風格精美的導航欄、表單、按鈕、提示、彈出窗口等。

使用Bootstrap最直接方法是在基礎模板中導入 bootstrap.min.css文件。可以下載這個文件副本,並將其添加到項目中;也可以直接從CDN導入(這里查看)。然后,可以根據文檔開始使用它提供的通用CSS類。還可以導入包含框架JavaScript代碼的bootstrap.min.js文件,以便使用更高級的功能。

幸運的是,有一個名為 Flask-BootstrapFlask擴展,它提供一個隨時可用的基本模板,它安裝了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框架文件。這個模板 為了派生模板導出幾個塊,如titlenavbarcontent(查看完整的 塊列表)。我將更改我的base.html模板,讓它從bootstrap/base.html模板派生,並為titlenavbarcontent塊提供實現。反過來,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">&larr;</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">&rarr;</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">&larr;</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">&rarr;</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">&rarr;</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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM