《Flask 入門教程》第 6 章:模板優化

這一章咱們會繼續完善模板,學習幾個很是實用的模板編寫技巧,爲下一章實現建立、編輯電影條目打下基礎。css

自定義錯誤頁面

爲了引出相關知識點,咱們首先要爲 Watchlist 編寫一個錯誤頁面。目前的程序中,若是你訪問一個不存在的 URL,好比 /hello,Flask 會自動返回一個 404 錯誤響應。默認的錯誤頁面很是簡陋,以下圖所示:html



在 Flask 程序中自定義錯誤頁面很是簡單,咱們先編寫一個 404 錯誤頁面模板,以下所示:git

templates/404.html:404 錯誤頁面模板github

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ user.name }}'s Watchlist</title>
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
</head>
<body>
    <h2>
        <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
        {{ user.name }}'s Watchlist
    </h2>
    <ul class="movie-list">
        <li>
            Page Not Found - 404
            <span class="float-right">
                <a href="{{ url_for('index') }}">Go Back</a>
            </span>
        </li>
    </ul>
    <footer>
        <small>&copy; 2018 <a href="http://helloflask.com/tutorial">HelloFlask</a></small>
	</footer>
</body>
</html>複製代碼

接着使用 app.errorhandler() 裝飾器註冊一個錯誤處理函數,它的做用和視圖函數相似,當 404 錯誤發生時,這個函數會被觸發,返回值會做爲響應主體返回給客戶端:數據庫

app.py:404 錯誤處理函數flask

@app.errorhandler(404)  # 傳入要處理的錯誤代碼
def page_not_found(e):  # 接受異常對象做爲參數
    user = User.query.first()
    return render_template('404.html', user=user), 404  # 返回模板和狀態碼複製代碼

提示 和咱們前面編寫的視圖函數相比,這個函數返回了狀態碼做爲第二個參數,普通的視圖函數之因此不用寫出狀態碼,是由於默認會使用 200 狀態碼,表示成功。app

這個視圖返回渲染好的錯誤模板,由於模板中使用了 user 變量,這裏也要一併傳入。如今訪問一個不存在的 URL,會顯示咱們自定義的錯誤頁面:函數



編寫完這部分代碼後,你會發現兩個問題:學習

  • 錯誤頁面和主頁都須要使用 user 變量,因此在對應的處理函數裏都要查詢數據庫並傳入 user 變量。由於每個頁面都須要獲取用戶名顯示在頁面頂部,若是有更多的頁面,那麼每個對應的視圖函數都要重複傳入這個變量。
  • 錯誤頁面模板和主頁模板有大量重複的代碼,好比 <head> 標籤的內容,頁首的標題,頁腳信息等。這種重複不只帶來沒必要要的工做量,並且會讓修改變得更加麻煩。舉例來講,若是頁腳信息須要更新,那麼每一個頁面都要一一進行修改。

顯而易見,這兩個問題有更優雅的處理方法,下面咱們來一一瞭解。網站

模板上下文處理函數

對於多個模板內都須要使用的變量,咱們能夠使用 app.context_processor 裝飾器註冊一個模板上下文處理函數,以下所示:

app.py:模板上下文處理函數

@app.context_processor
def inject_user():  # 函數名能夠隨意修改
    user = User.query.first()
    return dict(user=user)  # 須要返回字典,等同於return {'user': user}複製代碼

這個函數返回的變量(以字典鍵值對的形式)將會統一注入到每個模板的上下文環境中,所以能夠直接在模板中使用。

如今咱們能夠刪除 404 錯誤處理函數和主頁視圖函數中的 user 變量定義,並刪除在 render_template() 函數裏傳入的關鍵字參數:

@app.context_processor
def inject_user():
    user = User.query.first()
    return dict(user=user)


@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404


@app.route('/')
def index():
    movies = Movie.query.all()
    return render_template('index.html', movies=movies)複製代碼

一樣的,後面咱們建立的任意一個模板,均可以在模板中直接使用 user 變量。

使用模板繼承組織模板

對於模板內容重複的問題,Jinja2 提供了模板繼承的支持。這個機制和 Python 類繼承很是相似:咱們能夠定義一個父模板,通常會稱之爲基模板(base template)。基模板中包含完整的 HTML 結構和導航欄、頁首、頁腳都通用部分。在子模板裏,咱們能夠使用 extends 標籤來聲明繼承自某個基模板。

基模板中須要在實際的子模板中追加或重寫的部分則能夠定義成塊(block)。塊使用 block 標籤建立, {% block 塊名稱 %}做爲開始標記,{% endblock %}{% endblock 塊名稱 %} 做爲結束標記。經過在子模板裏定義一個一樣名稱的塊,你能夠向基模板的對應塊位置追加或重寫內容。

編寫基礎模板

下面是新編寫的基模板 base.html:

templates/base.html:基模板

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ user.name }}'s Watchlist</title>
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
    {% endblock %}
</head>
<body>
    <h2>
        <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
        {{ user.name }}'s Watchlist
    </h2>
    <nav>
        <ul>
            <li><a href="{{ url_for('index') }}">Home</a></li>
        </ul>
    </nav>
    {% block content %}{% endblock %}
    <footer>
        <small>&copy; 2018 <a href="http://helloflask.com/tutorial">HelloFlask</a></small>
	</footer>
</body>
</html>複製代碼

在基模板裏,咱們添加了兩個塊,一個是包含 <head></head> 內容的 head 塊,另外一個是用來在子模板中插入頁面主體內容的 content 塊。在複雜的項目裏,你能夠定義更多的塊,方便在子模板中對基模板的各個部分插入內容。另外,塊的名字沒有特定要求,你能夠自由修改。

在編寫子模板以前,咱們先來看一下基模板中的兩處新變化。

第一處,咱們添加了一個新的 <meta> 元素,這個元素會設置頁面的視口,讓頁面根據設備的寬度來自動縮放頁面,讓移動設備擁有更好的瀏覽體驗:

<meta name="viewport" content="width=device-width, initial-scale=1.0">複製代碼

第二處,新的頁面添加了一個導航欄:

<nav>
    <ul>
        <li><a href="{{ url_for('index') }}">Home</a></li>
    </ul>
</nav>複製代碼

導航欄對應的 CSS 代碼以下所示:

nav ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333;
}

nav li {
    float: left;
}

nav li a {
    display: block;
    color: white;
    text-align: center;
    padding: 8px 12px;
    text-decoration: none;
}

nav li a:hover {
    background-color: #111;
}複製代碼

編寫子模板

建立了基模板後,子模板的編寫會變得很是簡單。下面是新的主頁模板(index.html):

templates/index.html:繼承基模板的主頁模板

{% extends 'base.html' %}

{% block content %}
<p>{{ movies|length }} Titles</p>
<ul class="movie-list">
    {% for movie in movies %}
    <li>{{ movie.title }} - {{ movie.year }}
        <span class="float-right">
            <a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
        </span>
    </li>
    {% endfor %}
</ul>
<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}" title="to~to~ro~">
{% endblock %}複製代碼

第一行使用 extends 標籤聲明擴展自模板 base.html,能夠理解成「這個模板繼承自 base.html「。接着咱們定義了 content塊,這裏的內容會插入到基模板中 content 塊的位置。

提示 默認的塊重寫行爲是覆蓋,若是你想向父塊裏追加內容,能夠在子塊中使用 super() 聲明,即 {{ super() }}

404 錯誤頁面的模板相似,以下所示:

templates/index.html:繼承基模板的 404 錯誤頁面模板

{% extends 'base.html' %}

{% block content %}
<ul class="movie-list">
    <li>
        Page Not Found - 404
        <span class="float-right">
            <a href="{{ url_for('index') }}">Go Back</a>
        </span>
    </li>
</ul>
{% endblock %}複製代碼

添加 IMDb 連接

在主頁模板裏,咱們還爲每個電影條目右側添加了一個 IMDb 連接:

<span class="float-right">
    <a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>
</span>複製代碼

這個連接的 href 屬性的值爲 IMDb 搜索頁面的 URL,搜索關鍵詞經過查詢參數 q 傳入,這裏傳入了電影的標題。

對應的 CSS 定義以下所示:

.float-right {
    float: right;
}

.imdb {
    font-size: 12px;
    font-weight: bold;
    color: black;
    text-decoration: none;
    background: #F5C518;
    border-radius: 5px;
    padding: 3px 5px;
}複製代碼

如今,咱們的程序主頁以下所示:



本章小結

本章咱們主要學習了 Jinja2 的模板繼承機制,去掉了大量的重複代碼,這讓後續的模板編寫工做變得更加輕鬆。結束前,讓咱們提交代碼:

$ git add .
$ git commit -m "Add base template and error template"
$ git push複製代碼

提示 你能夠在 GitHub 上查看本書示例程序的對應 commit:cfc08fa

進階提示

  • 本章介紹的自定義錯誤頁面是爲了引出兩個重要的知識點,所以並無着重介紹錯誤頁面自己。這裏只爲 404 錯誤編寫了自定義錯誤頁面,對於另外兩個常見的錯誤 400 錯誤和 500 錯誤,你能夠本身試着爲它們編寫錯誤處理函數和對應的模板。
  • 由於示例程序的語言和電影標題使用了英文,因此電影網站的搜索連接使用了 IMDb,對於中文,你能夠使用豆瓣電影或時光網。以豆瓣電影爲例,它的搜索連接爲 movie.douban.com/subject_sea…,對應的 href 屬性即 https://movie.douban.com/subject_search?search_text={{ movie.title }}
  • 由於基模板會被全部其餘頁面模板繼承,若是你在基模板中使用了某個變量,那麼這個變量也須要使用模板上下文處理函數注入到模板裏。
  • 本書主頁 & 相關資源索引:http://helloflask.com/tutorial
相關文章
相關標籤/搜索