在 Flask Web 開發中,Jinja2 範本引擎扮演著至關重要的角色,它讓開發者能有效地分離程式邏輯和介面呈現。本文將逐步示範如何使用 Jinja2 建立具備繼承、區塊組合的範本結構,並整合 Bootstrap 框架,開發簡潔易維護的前端頁面。同時,我們還會探討自定義篩選器和巨集的開發技巧,讓範本更具彈性和可重用性,並示範如何利用外部函式庫處理貨幣格式化等實務應用。透過這些技巧,開發者能更有效地管理和擴充套件 Flask 應用程式的前端介面。

使用 Jinja 進行範本化

本章節將從 Flask 的角度介紹 Jinja 範本化的基礎知識,並學習如何設計和開發具有模組化和可擴充套件性的範本應用程式。

為什麼需要範本化?

在 Flask 中,可以編寫一個完整的 Web 應用程式而無需使用第三方範本引擎。但是,對於涉及大量 HTML、JS 和 CSS 程式碼的大型應用程式,直接在 Python 程式碼中嵌入 HTML 是一種不可行的方案。幸運的是,範本化提供了一種解決方案,使我們能夠將檢視程式碼結構化,保持範本的可擴充套件性和分離性。

啟動標準佈局

大多數 Flask 應用程式都遵循特定的範本佈局模式。在本文中,我們將實作推薦的範本佈局結構。

如何實作

首先,在應用程式根目錄下建立一個名為 templates 的資料夾。然後,在 my_app 資料夾下建立一個名為 hello 的子資料夾,並在其中建立 views.py 檔案。

# my_app/hello/views.py
from flask import render_template, request

@hello.route('/')
@hello.route('/hello')
def hello_world():
    user = request.args.get('user', 'Shalabh')
    return render_template('index.html', user=user)

接下來,在 templates 資料夾下建立 index.html 檔案。

<!-- templates/index.html -->
<html>
<head>
    <title>Flask Framework Cookbook</title>
</head>
<body>
    <h1>Hello {{ user }}!</h1>
    <p>Welcome to the world of Flask!</p>
</body>
</html>

程式碼解說

views.py 中,我們使用 render_template 函式渲染 index.html 範本,並將 user 變數傳遞給範本。在 index.html 中,我們使用 Jinja 的佔位符 {{ user }} 來顯示 user 變數的值。

執行結果

當我們開啟 http://127.0.0.1:5000/hello 網址時,將看到一個類別似於下圖的回應: 此圖示顯示了渲染後的範本頁面。

實作區塊組合和佈局繼承

通常,Web 應用程式會有多個不同的網頁,但是像頁首和頁尾這樣的程式碼區塊在整個網站中保持一致。Jinja 提供了一種確保範本之間繼承關係的好方法。

如何實作

建立一個基本範本,在其中定義網站的基本佈局,包括頁首和頁尾。然後,其他範本可以繼承這個基本範本。

<!-- templates/base.html -->
<html>
<head>
    <title>Flask Framework Cookbook</title>
</head>
<body>
    <header>
        <!-- 頁首內容 -->
    </header>
    <div id="content">
        {% block content %}{% endblock %}
    </div>
    <footer>
        <!-- 頁尾內容 -->
    </footer>
</body>
</html>
<!-- templates/index.html -->
{% extends 'base.html' %}

{% block content %}
    <h1>Hello {{ user }}!</h1>
    <p>Welcome to the world of Flask!</p>
{% endblock %}

程式碼解說

base.html 中,我們定義了一個名為 content 的區塊,其他範本可以覆寫這個區塊。在 index.html 中,我們繼承了 base.html,並覆寫了 content 區塊。

這種方式使得我們可以輕鬆地在多個範本之間共用相同的佈局和結構,同時保持每個範本的獨特內容。

使用 Bootstrap 框架建立標準佈局

在本篇技術文章中,我們將介紹如何使用 Bootstrap 框架來建立一個具有基本設計和主題的小型應用程式。這個應用程式將包含一個首頁和一個產品頁面,類別似於電子商務網站。

準備工作

首先,我們需要下載 Bootstrap v5 框架,可以從官方網站 http://getbootstrap.com/ 下載。截至撰寫本文時,Bootstrap 的最新版本是 v5。

建立應用程式結構

我們的應用程式結構如下:

flask_app/
- run.py
- my_app/
  - __init__.py
  - product/
    - __init__.py
    - views.py
    - models.py
  - templates/
    - base.html
    - home.html
    - product.html
  - static/
    - js/
      - bootstrap.bundle.min.js
      - jquery.min.js
    - css/
      - bootstrap.min.css
      - main.css

定義模型和檢視

my_app/product/models.py 中,我們定義了一個簡單的非永續性鍵值儲存,用於存放產品資料。

PRODUCTS = {
    'iphone': {
        'name': 'iPhone 5S',
        'category': 'Phones',
        'price': 699,
    },
    'galaxy': {
        'name': 'Samsung Galaxy 5',
        'category': 'Phones',
        'price': 649,
    },
    'ipad-air': {
        'name': 'iPad Air',
        'category': 'Tablets',
        'price': 649,
    },
    'ipad-mini': {
        'name': 'iPad Mini',
        'category': 'Tablets',
        'price': 549
    }
}

my_app/product/views.py 中,我們定義了檢視函式,使用 Blueprint 風格來撰寫應用程式。

from werkzeug.exceptions import abort
from flask import render_template
from flask import Blueprint
from my_app.product.models import PRODUCTS

product_blueprint = Blueprint('product', __name__)

@product_blueprint.route('/')
@product_blueprint.route('/home')
def home():
    return render_template('home.html', products=PRODUCTS)

@product_blueprint.route('/product/<key>')
def product(key):
    product = PRODUCTS.get(key)
    if not product:
        abort(404)
    return render_template('product.html', product=product)

建立範本

my_app/templates/base.html 中,我們定義了基本範本,使用 Jinja 語法來渲染頁面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flask Framework Cookbook</title>
    <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-dark bg-dark fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="{{ url_for('product.home') }}">Flask Cookbook</a>
            </div>
        </nav>
    </div>
    <div class="container">
        {% block container %}{% endblock %}
    </div>
    <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
</body>
</html>

程式碼解密:

  1. 範本繼承base.html 是其他範本的基礎範本,使用 Jinja 的 {% block container %}{% endblock %} 定義了可替換的區塊。
  2. 靜態檔案連結:使用 url_for 函式連結靜態檔案,如 CSS 和 JavaScript 檔案。
  3. 導航欄:定義了一個基本的導航欄,使用 Bootstrap 的元件。

my_app/templates/home.html 中,我們迭代顯示所有產品。

{% extends 'base.html' %}

{% block container %}
<div class="top-pad">
    {% for id, product in products.items() %}
    <div class="top-pad offset-1 col-sm-10">
        <div class="card text-center">
            <div class="card-body">
                <h2>
                    <a href="{{ url_for('product.product', key=id) }}">{{ product['name'] }}</a>
                    <small>$ {{ product['price'] }}</small>
                </h2>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

程式碼解密:

  1. 範本繼承home.html 繼承自 base.html,並覆寫了 container 區塊。
  2. 產品迭代:使用 Jinja 的 for 迴圈迭代顯示所有產品。
  3. 產品連結:使用 url_for 函式生成產品頁面的連結。

my_app/templates/product.html 中,我們顯示單個產品的詳細資訊。

{% extends 'home.html' %}

{% block container %}
<div class="top-pad">
    <h1>{{ product['name'] }}</h1>
    <h3><small>{{ product['category'] }}</small></h3>
    <h5>$ {{ product['price'] }}</h5>
</div>
{% endblock %}

程式碼解密:

  1. 範本繼承product.html 繼承自 home.html,並覆寫了 container 區塊。
  2. 產品資訊顯示:顯示單個產品的名稱、類別和價格。

結果

當我們執行這個應用程式時,將看到類別似於以下截圖的頁面:

此圖示展示了首頁的外觀,列出了所有產品。

此圖示展示了產品頁面的外觀,顯示了單個產品的詳細資訊。

自定義上下文處理器

有時,我們可能需要在範本中直接計算或處理某些值。Jinja 維護著這樣一個理念:邏輯處理應該在檢視中進行,而不是在範本中,以保持範本的乾淨。上下文處理器在這種情況下變得非常有用。透過上下文處理器,我們可以將值傳遞給一個方法,該方法將在 Python 方法中進行處理,然後傳回結果值。在下一篇文章中,我們將介紹如何編寫自定義上下文處理器。

自定義 Jinja 篩選器與巨集的應用

在 Flask 應用程式中,Jinja 範本引擎提供了多種方式來增強範本的靈活性與可讀性。本篇文章將重點介紹如何建立自定義 Jinja 篩選器與巨集,以提升範本的處理能力。

建立自定義 Jinja 篩選器

篩選器(Filter)是用於在 Jinja 範本中對變數進行處理的函式。以下範例展示如何建立一個篩選器,用於格式化產品名稱。

程式碼範例

@product_blueprint.app_template_filter('full_name')
def full_name_filter(product):
    return '{0} / {1}'.format(product['category'], product['name'])

內容解密:

  1. 使用 @product_blueprint.app_template_filter 裝飾器將 full_name_filter 函式註冊為一個 Jinja 篩選器。
  2. full_name_filter 函式接受一個產品字典,傳回格式化的產品名稱。
  3. 在範本中使用 {{ product|full_name }} 呼叫該篩選器。

使用外部函式庫進行貨幣格式化

為了更好地格式化貨幣,我們可以使用 ccy 函式庫根據當前的語言環境進行貨幣格式化。

程式碼範例

import ccy
from flask import request

@app.template_filter('format_currency')
def format_currency_filter(amount):
    currency_code = ccy.countryccy(request.accept_languages.best[-2:])
    return '{0} {1}'.format(currency_code, amount)

內容解密:

  1. 使用 ccy.countryccy 函式根據請求的語言環境取得貨幣程式碼。
  2. format_currency_filter 函式接受一個金額,傳回格式化後的貨幣字串。
  3. 在範本中使用 {{ product['price']|format_currency }} 呼叫該篩選器。

建立自定義巨集

巨集(Macro)是用於在 Jinja 範本中定義可重複使用的 HTML 程式碼區塊。以下範例展示如何建立一個巨集,用於渲染表單輸入欄位。

程式碼範例 (_helpers.html)

{% macro render_field(name, class='', value='', type='text') -%}
<input type="{{ type }}" name="{{ name }}" class="{{ class }}" value="{{ value }}"/>
{%- endmacro %}

內容解密:

  1. 使用 {% macro %} 定義一個名為 render_field 的巨集。
  2. render_field 巨集接受多個引數,用於生成不同型別的輸入欄位。
  3. 在其他範本中使用 {% from '_helpers.html' import render_field %} 匯入該巨集。

使用自定義巨集渲染表單

程式碼範例

<fieldset>
    {{ render_field('username', 'icon-user') }}
    {{ render_field('password', 'icon-key', type='password') }}
</fieldset>

內容解密:

  1. 使用匯入的 render_field 巨集生成使用者名稱和密碼輸入欄位。
  2. 該巨集提高了程式碼的可重複使用性和可讀性。