返回文章列表

Flask檢視與樣板渲染和XHR請求處理

本文探討 Flask 框架中檢視、樣板渲染和 XHR 請求處理的技巧。涵蓋 URL 路由、分頁、Jinja2 樣板引擎的使用、XHR 請求的傳送與接收,以及自定義錯誤處理等導向,提供建構動態網頁應用程式的實務。

Web 開發 後端開發

Flask 應用程式開發中,檢視函式處理使用者請求並渲染樣板至關重要。本文將探討如何使用 URL 轉換器解析不同 URL 格式、實作產品分頁提升效能、運用 Jinja2 樣板引擎渲染資料,以及處理 XHR 請求實作非同步更新。同時,也將介紹如何建立裝飾器簡化 XHR 請求處理流程,並示範自定義錯誤處理機制以提供更友善的使用者經驗。透過這些技術,可以有效地管理應用程式的路由、資料呈現和使用者互動,建構更具彈性且使用者友善的網頁應用程式。

使用檢視(Working with Views)

實作 URL 路由和產品分頁

在某些情況下,我們需要以不同的方式解析 URL 的各個部分。例如,URL 可能包含整數部分、字串部分、特定長度的字串部分,以及斜線。我們可以使用 URL 轉換器來解析 URL 中的這些組合。在本章節中,我們將學習如何實作 URL 路由和產品分頁。

基礎 URL 轉換器

首先,我們來看看基本的 URL 轉換器。假設我們有一個 URL 路由定義如下:

@app.route('/test/<name>')
def get_name(name):
    return name

在這個例子中,URL http://127.0.0.1:5000/test/Shalabh 將會解析出 Shalabh 並傳遞給 get_name 方法的 name 引數。這是預設的字串轉換器,不需要明確指定。

特定長度的字串轉換器

我們也可以指定字串的最小和最大長度。例如,若要解析包含國家程式碼或貨幣程式碼的 URL,可以這樣做:

@app.route('/test/<string(minlength=2,maxlength=3):code>')
def get_code(code):
    return code

整數和浮點數轉換器

同樣地,我們可以解析整數值:

@app.route('/test/<int:age>')
def get_age(age):
    return str(age)

也可以指定可接受的最小和最大值:

@app.route('/test/<int(min=18,max=99):age>')
def get_age(age):
    return str(age)

實作產品分頁

接下來,我們來瞭解分頁的概念。在之前的章節中,我們建立了一個處理器來列出資料函式庫中的所有產品。如果我們有成千上萬的產品,一次性生成所有產品的列表將會非常耗時。分頁可以幫助我們建立更好的應用程式。

讓我們修改 products() 方法來支援分頁:

@catalog.route('/products')
@catalog.route('/products/<int:page>')
def products(page=1):
    products = Product.query.paginate(page, 10).items
    res = {}
    for product in products:
        res[product.id] = {
            'name': product.name,
            'price': product.price,
            'category': product.category.name
        }
    return jsonify(res)

分頁方法解析

paginate() 方法接受四個引數並傳回一個 Pagination 物件的例項。這四個引數分別是:

  • page:目前要列出的頁面。
  • per_page:每頁要列出的專案數量。
  • error_out:如果找不到該頁的專案,則會中止並傳回 404 錯誤。若要防止這種行為,請將此引數設為 False,這樣就會傳回一個空列表。
  • max_per_page:如果指定了這個值,則 per_page 將被限制在同樣的值。

渲染到範本

在編寫檢視後,我們通常會想要在範本中渲染內容並從底層資料函式庫取得資訊。

首先,修改我們的檢視來渲染範本:

from flask import request, Blueprint, render_template
from my_app import db
from my_app.catalog.models import Product, Category

catalog = Blueprint('catalog', __name__)

@catalog.route('/')
@catalog.route('/home')
def home():
    return render_template('home.html')

@catalog.route('/product/<id>')
def product(id):
    # 渲染 product.html 範本,並傳遞 product 物件
    product_obj = Product.query.get_or_404(id)
    return render_template('product.html', product=product_obj)

內容解密:

  1. render_template() 方法:用於渲染指定的範本,並將範本變數傳遞給範本引擎。
  2. Product.query.get_or_404(id):根據指定的 id 從資料函式庫中查詢產品,如果找不到,則傳回 404 錯誤。
  3. 範本變數:在 render_template() 方法中傳遞的變數可以在範本中使用,例如 product 物件。

樣板渲染與XHR請求處理

在Flask應用程式中,樣板渲染和XHR(XMLHttpRequest)請求處理是兩個重要的功能。本篇文章將介紹如何使用Flask進行樣板渲染和處理XHR請求。

樣板渲染

樣板渲染是指將資料渲染到HTML樣板中,以產生最終的網頁內容。Flask使用Jinja2作為其樣板引擎。

產品相關樣板的渲染

在產品目錄應用程式中,我們定義了多個樣板來顯示產品資訊。以下是一個例子:

@catalog.route('/product/<id>')
def product(id):
    product = Product.query.get_or_404(id)
    return render_template('product.html', product=product)

在這個例子中,我們從資料函式庫中查詢產品資訊,然後將其渲染到product.html樣板中。

產品列表的渲染

我們還可以渲染產品列表,如下所示:

@catalog.route('/products')
@catalog.route('/products/<int:page>')
def products(page=1):
    products = Product.query.paginate(page, 10)
    return render_template('products.html', products=products)

在這個例子中,我們使用paginate方法來取得產品列表,並將其渲染到products.html樣板中。

XHR請求處理

XHR請求是一種用於在網頁上進行非同步請求的技術。Flask提供了簡便的方式來處理XHR請求。

檢查XHR請求

我們可以使用request.headers.get("X-Requested-With")來檢查請求是否為XHR請求。

from flask import request, render_template, jsonify

@catalog.route('/')
@catalog.route('/home')
def home():
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        products = Product.query.all()
        return jsonify({'count': len(products)})
    return render_template('home.html')

在這個例子中,如果請求是XHR請求,我們傳回JSON資料;否則,我們渲染home.html樣板。

樣板範例

以下是一些樣板的範例:

base.html

<!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>
    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="{{ url_for('catalog.home') }}">Flask Cookbook</a>
            </div>
        </div>
    </div>
    <div class="container">
        {% block container %}{% endblock %}
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
</body>
</html>

product.html

{% extends 'home.html' %}

{% block container %}
<div class="top-pad">
    <h1>{{ product.name }}<small> {{ product.category.name }}</small></h1>
    <h4>{{ product.company }}</h4>
    <h3>{{ product.price }}</h3>
</div>
{% endblock %}

products.html

{% extends 'home.html' %}

{% block container %}
<div class="top-pad">
    {% for product in products.items %}
    <div class="well">
        <h2>
            <a href="{{ url_for('catalog.product', id=product.id) }}">{{ product.name }}</a>
            <small>$ {{ product.price }}</small>
        </h2>
    </div>
    {% endfor %}
    {% if products.has_prev %}
    <a href="{{ url_for(request.endpoint, page=products.prev_num) }}">{{ "<< Previous Page" }}</a>
    {% else %}
    {{ "<< Previous Page" }}
    {% endif %} |
    {% if products.has_next %}
    <a href="{{ url_for(request.endpoint, page=products.next_num) }}">{{ "Next page >>" }}</a>
    {% else %}
    {{ "Next page >>" }}
    {% endif %}
</div>
{% endblock %}

內容解密:

此範例展示瞭如何在Flask中使用Jinja2樣板引擎來渲染產品資訊。透過繼承home.html,我們可以在product.htmlproducts.html中重用相同的佈局。同時,products.html中使用了分頁功能,以顯示多個產品。

使用 XHR 請求處理資料

在現代 Web 應用程式中,使用 Ajax(Asynchronous JavaScript and XML)或 XHR(XMLHttpRequest)請求來非同步更新網頁內容已成為常見的做法。本文將探討如何在 Flask 應用程式中處理 XHR 請求,並提供一個簡單的範例來說明其實作方式。

修改範本以支援 XHR 請求

首先,我們需要修改 flask_catalog_template/my_app/templates/base.html 範本,以新增一個用於放置 JavaScript 程式碼的區塊。這個區塊將被用來處理 XHR 請求後的資料更新。

{% block scripts %}
{% endblock %}

處理 XHR 請求的檢視函式

接下來,在 flask_catalog_template/my_app/templates/home.html 範本中,我們將傳送一個 Ajax 請求到 home() 檢視函式。該函式將檢查請求是否為 XHR 請求,如果是,則從資料函式庫中取得產品數量並以 JSON 物件的形式傳回。

{% extends 'base.html' %}

{% block container %}
  <h1>歡迎來到產品目錄首頁</h1>
  <a href="{{ url_for('catalog.products') }}" id="catalog_link">
    點選這裡檢視產品目錄
  </a>
{% endblock %}

{% block scripts %}
  <script>
    $(document).ready(function(){
      $.getJSON("/home", function(data) {
        $('#catalog_link').append('<span class="badge">' + data.count + '</span>');
      });
    });
  </script>
{% endblock %}

程式碼解析:

  1. 範本繼承home.html 繼承自 base.html,並覆寫了 containerscripts 區塊。
  2. Ajax 請求:當檔案載入完成後,傳送一個 GET 請求到 /home 路由,並預期傳回 JSON 資料。
  3. 更新 DOM:將傳回的產品數量資料附加到 #catalog_link 元素的後面。

使用裝飾器簡化 XHR 請求處理

檢查每個請求是否為 XHR 可能會降低程式碼的可讀性。為瞭解決這個問題,我們可以建立一個簡單的裝飾器來處理這項工作。

from functools import wraps
from flask import request, jsonify, render_template

def template_or_json(template=None):
    def decorated(f):
        @wraps(f)
        def decorated_fn(*args, **kwargs):
            ctx = f(*args, **kwargs)
            if request.headers.get("X-Requested-With") == "XMLHttpRequest" or not template:
                return jsonify(ctx)
            else:
                return render_template(template, **ctx)
        return decorated_fn
    return decorated

裝飾器解析:

  • template_or_json 裝飾器:根據請求是否為 XHR 或是否提供了範本名稱,決定傳回 JSON 資料或渲染範本。

自定義錯誤處理器

每個應用程式都可能在某些時候向使用者丟擲錯誤。這些錯誤可能是由於使用者輸入了不存在的 URL(404)、應用程式過載(500)或是某些資源對特定使用者禁止存取(403)。Flask 提供了一個簡單易用的裝飾器來處理這些錯誤。

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

自定義錯誤頁面範例:

{% extends 'home.html' %}

{% block container %}
  <div class="top-pad">
    <h3>朋友,您似乎來到了不存在的地方。</h3>
    <h4>請檢查您的地圖位置(URL)或回到 <a href="{{ url_for('catalog.home') }}">首頁</a></h4>
  </div>
{% endblock %}

自定義錯誤解析:

  • @app.errorhandler(404):定義了一個處理 404 錯誤的函式,傳回一個渲染後的 404.html 範本。