2019年10月4日 星期五

Python 學習筆記 : Django 2 測試 (三) : 模板的匯入與繼承

由於上一篇 Django 2 測試篇幅太長了, 因此把模板的匯入與繼承獨立出來,本系列之前的測試筆記參考 :

Python 學習筆記 : Django 2 測試 (一) : 請求與回應處理
Python 學習筆記 : Django 2 測試 (二) : 模板基礎與靜態檔案

本篇測試參考了下面幾本書中的範例加以改寫 :
  1. It's django - 用 Python 迅速打造 Web 應用 (楊孟穎, 袁克倫, 碁峰)
  2. Python 新手使用 django 架站的 16 堂課 (何敏煌, 博碩)
  3. Python 新手使用 django 架站技術實作 (何敏煌, 林亮昀, 博碩)
  4. Python 網頁程式交易 App 實作 (林萍珍, 博碩)
  5. Beginning Django (Daniel Rubio, Apress)
  6. Django 2 Web Development Cookbook 3rd Edition(Jake Kronika, Packt)
  7. 一次搞定所有 Python Web 框架開發百科全書 (佳魁, 劉長龍) 
  8. 科班出身的MVC網頁開發 : 使用Python + Django (佳魁, 王友釗)
線上教材參考 :

The Complete Guide To Install Django And Configure It
2019 iT 邦幫忙鐵人賽 : From Django 1.11 to Django 2.1 系列
Django 2 By Example 全书翻译、踩坑及教程

Django 2 官方教學文件參考 :

https://docs.djangoproject.com/en/2.2/

除了在上一篇中提到的 if-elif-else, for, 以及 empty 標籤外, Django 的預設模板引擎還提供了 include (匯入) 與 extend (繼承) 這兩個好用的標籤, 可將模板網頁結構化與模組化, 將相似性高的部分獨立拆開以便網頁可重複使用.


一. 匯入 (include) 模板網頁 : 

在模板網頁中可用 include 標籤匯入其他網頁檔案, 檔名需以引號括起來 :

{% include "other_template.htm" %} 

例如下列的 Hello World 網頁 :

<!--index.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>

可將 Hello World 網頁的內容部分單獨寫成 helloworld.htm 如下 :

<!--helloworld.htm-->
<h1>Hello World</h1>

然後再用 include 將其匯入 :

<!--index.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  {% include "helloworld.htm" %}
</body>
</html>

可見使用 include 之前須對原網頁進行切割, 導致每個網頁 "片段" 檔案可能不是完整網頁而顯得很奇怪, 在網頁結構上將非常混亂. 模板網頁模組化主要還是靠繼承 (extend).


二. 繼承 (extends) 模板網頁 : 

Django 預設的模板語言提供 extends 標籤可以繼承另一個模板網頁, 概念與物件導向中物件的繼承類似. 繼承 (extends) 與匯入 (include) 同樣是讀入另一個模板網頁檔案到目前檔案中, 但不同之處是 :
  1. 被繼承的模板都是完整的網頁, 而不是網頁片段. 被繼承的父模板網頁中定義了若干區塊 (block), 然後在子模板網頁中用具體的網頁填入父模板的指定區塊中. 
  2. 繼承標籤 extend 必須放在網頁的最前面, 而匯入 include 則是網頁中任何地方皆可. 
在父模板中定義一個 (待填入) 區塊的語法為 :

{% block block_name %}{% endblock %}   

子模板會以具體的網頁片段填入 block 與 endblock 之間.

在子模板中繼承父模板時要放在第一行, 父模板檔名需用引號括起來 :

{% extends "father_template.htm" %}

然後就可以在子模板中以具體的網頁片段填入指定的區塊中 :

{% block block_name %} 網頁片段 {% endblock %}

例如上面的 Hello World 網頁, 可以先寫一個基礎模板網頁 base.htm 如下 :

<!--base.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>Base template</title>
  <meta charset="utf-8">
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

然後就可以在子模板 index.htm 中繼承 base.htm, 然後用 block 標籤把網頁片段填入名為 content 的區塊中 :

<!--index.htm-->
{% extends 'base.htm' %}
{% block content %}
<h1>Hello World</h1>
{% endblock %}


上面為模板繼承的基本用法, 如果要在專案中使用各種前端框架, 有必要建立一個可共用, 更完整的 HTML5 基礎模板, 這裡面需包含 link, script, style 以及網頁內容等區塊, 如下所示 :

<!--base.htm-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{% block title %}{% endblock %}</title>
  {% block link %}{% endblock %}
  {% block script %}{% endblock %}
  <style>
  {% block style %}{% endblock %}
  </style>
</head>
<body>
  {% block body %}{% endblock %}
</body>
</html>

注意, 此模板網頁標頭中以 style 標籤設定的樣式在子模板中直接書寫樣式內容即可, 不需要 style 標籤. 有了上面這個完整基礎模板, 就可以繼承它來製作前端框架專案的模板了.

有了 HTML5 的基礎模板 base.htm, 子模板就可以繼承它來建立專案網頁, 並以具體的網頁元素來填入父模板的區塊中 :

{% extends "base.htm" %}
{% block link %}
   link 元素群 (樣式檔)
{% endblock %}
{% block script %}
   script 元素群 (程式檔)
{% endblock %}
{% block body %}
   網頁內容 + script 元素群
{% endblock %}

注意, 網頁內容區塊不一定要在子模板中填入, 可以在孫模板中再填入, 例如我們可以繼承基礎模板 (父模板) 建立各種前端框架模板 (子模板), 然後再於繼承這些框架專案模板的應用網頁模板 (孫模板) 中填入 body 區塊.

base.htm (父模板)
      |____  jqueryui.htm (子模板)
      |                 |____  jqapp.htm (孫模板)
      |____  easyui.htm (子模板)
      |                 |____  ezuiapp.htm (孫模板)
      |____  bootstrap.htm (子模板)
                        |____  bsapp.htm (孫模板)



三. 框架專案模板 :

由於提供 UI 元件的前端框架通常需要許多圖檔與樣式檔, 如果要在專案中自備的話就必須下載後放在 static 子目錄下, 不僅麻煩還占用伺服器硬碟空間, 因此以下的框架專案模板都採用 CDN 供檔的方式取得資源.


1. jQuery UI 專案模板 : 

jQueryUI 是以 jQuery 為基礎的網頁框架, 因此使用 jQuery UI 也必須匯入 jQuery 才行. jQuery UI 提供了許多好用的 UI 元件, 基本上足敷一般網頁專案之用, 另外有許多第三方函式庫可擴充 jQuery UI 的功能, 例如 DataTables 資料表格元件, 參考 :

https://datatables.net/

配置 jQuery UI 執行環境可從官網下載自備, 亦可使用 CDN 資源, 參考 :

jQuery UI 執行環境配置
如何在 GAE 上佈署 jQuery 與 ExtJS 專案

常用的 jQuery 與 jQuery UI 的 CDN 來源如下 :

https://code.jquery.com/ (jQuery 官網)
https://code.jquery.com/ui/ (jQuery UI 官網)
https://docs.microsoft.com/en-us/aspnet/ajax/cdn/ (微軟)
https://cdnjs.com/libraries/jqueryui (CDNJS)
https://cdnjs.com/libraries/jquery (CDNJS)
https://unpkg.com/

以下為使用最新版 jQuery, jQuery UI, DataTables 所製作的框架模板 :

<!--jqueryui.htm-->
{% extends "base.htm" %}
{% block link %}
  <link id="theme" rel="stylesheet"  href="https://code.jquery.com/ui/1.12.0/themes/hot-sneaks/jquery-ui.css">
  <link rel="stylesheet" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css">
{% endblock %}
{% block script %}
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
  <script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>
{% endblock %}

注意, 這裡連結 jquery-ui.css 的 link 元素特地給予一個 id 屬性, 這是為了使用者變更主題布景功能的緣故而設的. 其次, 此模板只是一個中間模板, 因此不須填入 body 區塊, 而由繼承此模板的 jQuery UI 應用模板填入.


2. EasyUI 專案模板 :

jQuery EasyUI 提供類似 ExtJS 華麗的 UI 元件, 提供免費 (freeware) 方案, 使用方式亦如其名好學好用, 是我最喜歡的前端 UI 框架, 環境配置參考 :

如何在 GAE 上佈署 jQuery EasyUI 專案 (二) : CDN 資源

常用的 CDN 如下 :
由於 EasyUI 官網提供了最新版本的 CDN 資源, 以此製作框架模板 easyui.htm 如下 :

{% extends "base.htm" %}
{% block link %}
  <link id="theme" rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/default/easyui.css">
  <link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/icon.css">
{% endblock %}
{% block script %}
  <script src="http://www.jeasyui.com/easyui/jquery.min.js"></script>
  <script src="http://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
  <script src="http://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
{% endblock %}

同樣地, 這裡連結 easyui.css 的 link 元素特地給予一個 id 屬性, 這是為了使用者變更主題布景功能的緣故而設的.


3. Bootstrap 專案模板 :

Bootstrap 是源自 Twiter 內部的前端網頁框架, 包裝了許多 CSS 樣式表功能, 提供漂亮且外觀一致的好用元件, 可讓不擅長利用 CSS 進行網頁外觀與佈局設計的工程師快速建立專業與美觀的網頁.

常用的 Bootstrap 的 CDN 來源如下 :

https://unpkg.com/
https://cdnjs.com/libraries/bootstrap-table
https://www.bootstrapcdn.com/

考慮 unpkg.com 有提供 bootstrap-table 的 CDN 支援, 以下使用 unpkg.com 來製作模板網頁 :

<!--bootstrap.htm-->
{% extends "base.htm" %}
{% block link %}
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
  <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.4/dist/bootstrap-table.min.css">
{% endblock %}
{% block script %}
  <script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
  <script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  <script src="https://unpkg.com/bootstrap-table@1.15.4/dist/bootstrap-table.min.js"></script>
{% endblock %}


四. 框架專案綜合測試 : 

以下根據上面所設計的框架專案模板網頁對 jQuery UI, EasyUI, 以及 Bootstrap 做個綜合測試, 主要對象是主題布景切換, 頁籤面板, 日期選擇器, 以及資料表格等元件.

1. 模板網頁 :

首先製作 home.htm 模板作為測試首頁, 此模板網頁包含全部測試之超連結 , 包含 jQuery UI, EasyUI, 以及 Bootstrap :

<!--home.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>前端框架模板模板測試</title>
  <meta charset="utf-8">
</head>
<body>
  <h3>前端框架模板測試</h3>
  <ul>
    <li><a href="/jqui_test_tabs">jQueryUI 頁嵌面板 Tabs</a></li>
    <li><a href="/jqui_test_themes">jQueryUI 主題布景切換 Themes</a></li>
    <li><a href="/jqui_test_datatables_array">jQueryUI 資料表格 DataTables (陣列)</a></li>
    <li><a href="/jqui_test_datatables_ajax">jQueryUI 資料表格 DataTables (Ajax)</a></li>
    <li><a href="/ezui_test_tabs">EasyUI 頁嵌面板 Tabs</a></li>
    <li><a href="/ezui_test_themes">EasyUI 主題布景切換 themes</a></li>
    <li><a href="/ezui_test_datagrid_ajax">EasyUI 資料表格 Datagrid (Ajax)</a></li>
    <li><a href="/bootstrap_test_table_html">Bootstrap 表格 (HTML)</a></li>
    <li><a href="/bootstrap_test_table_ajax">Bootstrap 表格 (Ajax)</a></li>
    <li><a href="/bootstrap_test_jumbotron">Bootstrap 主題 (jumbotro)</a></li>
  </ul>
</body>
</html>


(1). jQuery UI 應用的模板網頁 : 

jQuery UI 應用模板全部都繼承 jqueryui.htm, 首先是頁籤測試之模板如下 :

<!--jqui_test_tabs.htm-->
{% extends "jqueryui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:10px;
      }
{% endblock%}
{% block body %}
  <div id="tabs">
    <ul>
      <li><a href="#tab1">頁籤 1</a></li>
      <li><a href="/jqui_test_tab2">頁籤 2</a></li>
      <li><a href="/jqui_test_tab3">頁籤 3</a></li>
    </ul>
    <div id="tab1">
      <h3>這是頁籤 1 (網頁內提供)</h3>
    </div>
  </div>
  <script>
    $(document).ready(function(){
      $("#tabs").tabs();
      });
  </script>
{% endblock%}

此網頁中的頁籤面板有三個頁籤, 第一個頁籤內容來自頁內, 另外兩個來自頁外. 第二個頁籤是載入一個模板網頁 jqui_test_tab2.htm, 此網頁很簡單, 只要網頁內容即可, 不需要完整 HTML 結構 (完整也可以) :

<!--jqui_test_tab2.htm-->
<h3>這是頁籤 {{ tab }} (模板 jqui_test_tab2.htm)</h3>

第三個頁籤內容不使用模板網頁, 而是在視圖程式 views.py 中以 HttpResponse() 函數直接回應 HTML 字串.

接著使用模板網頁 jqui_test_themes.htm 測試主題背景切換, 其中含有一個下拉式選單來選取主題, 並以日期選擇器 datepicker 元件來顯示主題之變化.

jQuery UI 頁籤測試用的模板 jqui_test_tabs.htm :

<!--jqui_test_themes.htm-->
{% extends "jqueryui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:10px;
      }
{% endblock%}
{% block body %}
  <select id="themes">
    <option value="base">base</option>
    <option value="black-tie">black-tie</option>
    <option value="blitzer">blitzer</option>
    <option value="cupertino">cupertino</option>
    <option value="dark-hive">dark-hive</option>
    <option value="dot-luv">dot-luv</option>
    <option value="eggplant">eggplant</option>
    <option value="excite-bike">excite-bike</option>
    <option value="flick">flick</option>
    <option value="hot-sneaks">hot-sneaks</option>
    <option value="humanity">humanity</option>
    <option value="le-frog">le-frog</option>
    <option value="mint-choc">mint-choc</option>
    <option value="overcast">overcast</option>
    <option value="pepper-grinder">pepper-grinder</option>
    <option value="redmond">redmond</option>
    <option value="smoothness">smoothness</option>
    <option value="south-street">south-street</option>
    <option value="start">start</option>
    <option value="sunny">sunny</option>
    <option value="swanky-purse">swanky-purse</option>
    <option value="trontastic">trontastic</option>
    <option value="ui-darkness">ui-darkness</option>
    <option value="ui-lightness">ui-lightness</option>
    <option value="vader">vader</option>
  </select><br><br>
  <div id="datepicker"></div>
  <script>
    $(document).ready(function(){
      $("#datepicker").datepicker();
      $("#themes").selectmenu();
      $("#themes").val("hot-sneaks");
      $("#themes").selectmenu("refresh");
      $('#themes').on('selectmenuchange', function() {
        var theme=$(this).val();
        var href="https://code.jquery.com/ui/1.12.0/themes/" + theme +
                 "/jquery-ui.min.css";
        $("#theme").attr("href", href);
        });
      });
  </script>
{% endblock%}

注意, 上面程式中是利用 jQueryUI 框架專案模板 jqueryui.htm 裡面特地加上的 link 元素 id 來變換主題布景樣式檔 jquery-ui.min.css 的上層路徑, 這樣便能套用不同主題之樣式檔.

最後兩個模板是測試 DataTables 用的, jqui_test_datatables_array.htm 是將資料直接寫在二維陣列中 (當然也可以從 views.py 中查詢資料庫後透過變數傳遞填入模板中).

<!--jqui_test_datatables_array.htm-->
{% extends "jqueryui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:14px;
      }
    .colclass {text-align:right;}
{% endblock%}
{% block body %}
  <div id="container" style="width:500px;border:solid 1px;padding:5px;">
    <table id="stocks"></table>
  </div>
  <script>
    $(document).ready(function(){
      var opt={"iDisplayLength":5,
               "aLengthMenu":[5, 10, 15, 20],
               "aoColumns":[{"sTitle":"股票名稱"},
                            {"sTitle":"股票代號"},
                            {"sTitle":"收盤價 (元)"},
                            {"sTitle":"成交量 (張)"}],
               "aaData":[["台積電","2330",111.5,19268],
                         ["中華電","2412",95.1,7096],
                         ["中碳","1723",145.0,317],
                         ["創見","2451",104.0,459],
                         ["華擎","3515",104.0,95],
                         ["訊連","5203",98.5,326]]
               };
      $("#stocks").dataTable(opt);
      });
  </script>
{% endblock%}

這裡我們給 DataTables 元件外面套上一個 div 元素, 目的是利用它來控制資料表格的樣式, 特別是寬度, 否則它預設是占滿整個螢幕寬度的.

另一個資料表格模板 jqui_test_datatables_ajax.htm 則是透過 Ajax 從後端取得要餵給 DataTables 的資料 :

<!--jqui_test_datatables_ajax.htm-->
{% extends "jqueryui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:14px;
      }
{% endblock%}
{% block body %}
  <div id="container" style="width:500px;border:solid 1px;padding:5px;">
    <table id="stocks"></table>
  </div>
  <script>
    $(document).ready(function(){
      var opt={"iDisplayLength":5,
               "aLengthMenu":[5, 10, 15, 20],
               "bProcessing":true,
               "aoColumns":[{"sTitle":"股票名稱","mData":"stock_name"},
                            {"sTitle":"股票代號","mData":"stock_id"},
                            {"sTitle":"收盤價 (元)","mData":"close"},
                            {"sTitle":"成交量 (張)","mData":"volumn"}],
               "sAjaxSource":"/get_stocks_list"
               };
      $("#stocks").dataTable(opt);
      });
  </script>
{% endblock%}

此網頁中是透過 sAjaxSource 選項來指定提供資料的後端程式名稱 get_stocks_list, 透過 urls.py 對映到 views.py 裡面的 get_stocks_list() 函數, 其輸出是以 "aaData" 為鍵的 JSON 格式資料 :

{"aaData": [{"stock_name": "\u53f0\u7a4d\u96fb", "stock_id": "2330", "close": 111.5, "volumn": 19268}, {"stock_name": "\u4e2d\u83ef\u96fb", "stock_id": "2412", "close": 95.1, "volumn": 7096}, {"stock_name": "\u4e2d\u78b3", "stock_id": "1723", "close": 145.0, "volumn": 317}, {"stock_name": "\u5275\u898b", "stock_id": "2451", "close": 104.0, "volumn": 459}, {"stock_name": "\u83ef\u64ce", "stock_id": "3515", "close": 104.0, "volumn": 95}, {"stock_name": "\u8a0a\u9023", "stock_id": "5203", "close": 98.5, "volumn": 326}]}

注意, 使用 Ajax 方式取得資料時, "aoColumns" 選項中必須用 "mData" 鍵定義表格的欄位名稱才行.


(2). EasyUI 應用的模板網頁 : 

EasyUI 應用模板全部都繼承 easyui.htm, 首先是頁籤面板測試的模板 :

<!--ezui_test_tabs.htm-->
{% extends "easyui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:10px;
      }
{% endblock%}
{% block body %}
  <div id="tabs" class="easyui-tabs">
    <div title="頁籤 1" style="padding:10px;">這是頁籤 1</div>
    <div title="頁籤 2" style="padding:10px;" data-options="href:'/ezui_test_tab2'"></div>
    <div title="頁籤 3" style="padding:10px;" data-options="href:'/ezui_test_tab3'"></div>
  </div>
{% endblock%}

與上面 jQuery UI 測試一樣有三個頁籤, 第一個頁籤內容來自頁內, 後兩個則來自後端 Ajax 來源提供, 第二個是透過簡單的模板網頁 ezui_test_tab2.htm :

  <!--ezui_test_tab2.htm-->
  <h3>這是頁籤 {{ tab }} (模板 ezui_test_tab2.htm)</h3>

第三個則是由 views.py 直接回應, 不需要用到模板.

其次是主題布景切換 :

<!--ezui_test_themes.htm-->
{% extends "easyui.htm" %}
{% block style %}
    body {
      font-family: Arial, Helvetica, sans-serif;
      font-size:10px;
      }
{% endblock%}
{% block body %}
  <select id="theme_sel" class="easyui-combobox" style="width:120px" panelHeight="320">
    <option value="default" selected>default</option>
    <option value="gray">gray</option>
    <option value="black">black</option>
    <option value="bootstrap">bootstrap</option>
    <option value="metro">metro</option>
    <option value="metro-blue">metro-blue</option>
    <option value="metro-gray">metro-gray</option>
    <option value="metro-green">metro-green</option>
    <option value="metro-orange">metro-orange</option>
    <option value="metro-red">metro-red</option>
    <option value="ui-cupertino">ui-cupertino</option>
    <option value="ui-dark-hive">ui-dark-hive</option>
    <option value="ui-pepper-grinder">ui-pepper-grinder</option>
    <option value="ui-sunny">ui-sunny</option>
  </select><br><br>
  <input id="datebox1" type="text" class="easyui-datebox">
  <script>
    $(document).ready(function() {
      $("#theme_sel").combobox({
        onSelect:function(rec){
          var css="http://www.jeasyui.com/easyui/themes/" +
                  rec.value + "/easyui.css";
          $("#theme").attr("href", css);
          }
        });
      });
  </script>
{% endblock%}

這裡使用了 EasyUI 的 Combobox 元件當作下拉式選單, 透過其 onSelect 事件傳回的 rec 物件取得所選之主題, 然後更改 easyui.htm 模板中 id=theme 的 link 元素的 href 即可變更主題布景. 以上參考 :

用 jQuery EasyUI 打造輕量級 CMS (五)

最後是資料表格 Datagrid 測試模板 :

<!--ezui_test_datagrids.htm-->
{% extends "easyui.htm" %}
{% block style %}
    body{
      font-family:verdana,helvetica,arial,sans-serif;
      padding:20px;
      font-size:12px;
      margin:0;
      }
{% endblock%}
{% block body %}
  <table class="easyui-datagrid" title="股票列表" style="width:500px;"
    data-options="url:'/get_stocks_list_ezui',method:'get'">
    <thead>
      <tr>
        <th data-options="field:'stock_name',width:100">股票名稱</th>
        <th data-options="field:'stock_id',width:100">股票代號</th>
        <th data-options="field:'close',width:100">收盤價 (元)</th>
        <th data-options="field:'volumn',width:100">成交量</th>
      </tr>
    </thead>
  </table>
{% endblock%}

只要用 data-options 並指定 href 屬性即可以 Ajax 方式從後端程式 get_stocks_list_ezui 取得資料, 但必須在表格標題 thead 的 th 元素中指定 field 欄位名稱才行. EasyUI 的 Datagrids 要求後端程式回應一個具有 total (總筆數) 與 rows (紀錄陣列) 屬性之 JSON 格式資料, 例如 :

{"totals": "6", "rows": [{"stock_name": "\u53f0\u7a4d\u96fb", "stock_id": "2330", "close": 111.5, "volumn": 19268}, {"stock_name": "\u4e2d\u83ef\u96fb", "stock_id": "2412", "close": 95.1, "volumn": 7096}, {"stock_name": "\u4e2d\u78b3", "stock_id": "1723", "close": 145.0, "volumn": 317}, {"stock_name": "\u5275\u898b", "stock_id": "2451", "close": 104.0, "volumn": 459}, {"stock_name": "\u83ef\u64ce", "stock_id": "3515", "close": 104.0, "volumn": 95}, {"stock_name": "\u8a0a\u9023", "stock_id": "5203", "close": 98.5, "volumn": 326}]}

模板中 th 的 field 屬性必須對照此 JSON 資料中 rows 陣列每一個字典之 鍵 (key).


(3). Bootstrap 測試的模板網頁 : 

Bootstrap 應用模板全部都繼承 bootstrap.htm, 首先是頁籤面板測試的模板, 第一個是將頁籤內容直接寫在網頁內 :

<!--bootstrap_test_table_html.htm-->
{% extends "bootstrap.htm" %}
{% block style %}
{% endblock%}
{% block body %}
  <div class="container" style="width:600px;">
    <table class="table table-responsive table-striped table-condensed table-bordered table-hover">
      <tr>
        <th>股票名稱</th>
        <th>股票代號</th>
        <th>收盤價 (元)</th>
        <th>成交量
      </tr>
      <tr>
        <td>台積電</td>
        <td>2330</td>
        <td>111.5</td>
        <td>19268</td>
      </tr>
      <tr>
        <td>中華電</td>
        <td>2412</td>
        <td>95.1</td>
        <td>7096</td>
      </tr>
      <tr>
        <td>中碳</td>
        <td>1723</td>
        <td>145.0</td>
        <td>317</td>
      </tr>
      <tr>
        <td>創見</td>
        <td>2451</td>
        <td>104.0</td>
        <td>459</td>
      </tr>
      <tr>
        <td>華擎</td>
        <td>3515</td>
        <td>108.5</td>
        <td>95</td>
      </tr>
      <tr>
        <td>訊連</td>
        <td>5203</td>
        <td>98.5</td>
        <td>326</td>
      </tr>
    </table>
  </div>
{% endblock%}

第二個是以 data-url 屬性透過 Ajax 從後端程式 get_stocks_list 取得資料來源 :

<!--bootstrap_test_table_ajax.htm-->
{% extends "bootstrap.htm" %}
{% block style %}
{% endblock%}
{% block body %}
  <div class="container" style="width:600px;">
    <table id="stocks" class="table table-responsive table-striped table-condensed table-bordered table-hover" data-url='/get_stocks_list'>
      <thead>
        <tr>
          <th data-field="stock_name">股票名稱</th>
          <th data-field="stock_id">股票代號</th>
          <th data-field="close">收盤價 (元)</th>
          <th data-field="volumn">成交量
        </tr>
      </thead>
    </table>
  </div>
  <script>
    $(document).ready(function(){
      $.ajax({
        url: '/get_stocks_list',
        dataType: 'json',
        success: function(data) {
          //alert(JSON.stringify(data.aaData));
          $('#stocks').bootstrapTable({
            data: data.aaData
            });
          },
        error: function(e) {
          console.log(e.responseText);
          }
        });
      });
  </script>
{% endblock%}

注意, , 後端程式 get_stocks_list 是上面 jQuery UI 測試時使用的, Bootstrap 的表格需要的遠端資料格式為一個物件陣列 :

[{"stock_name": "\u53f0\u7a4d\u96fb", "stock_id": "2330", "close": 111.5, "volumn": 19268}, {"stock_name": "\u4e2d\u83ef\u96fb", "stock_id": "2412", "close": 95.1, "volumn": 7096}, {"stock_name": "\u4e2d\u78b3", "stock_id": "1723", "close": 145.0, "volumn": 317}, {"stock_name": "\u5275\u898b", "stock_id": "2451", "close": 104.0, "volumn": 459}, {"stock_name": "\u83ef\u64ce", "stock_id": "3515", "close": 104.0, "volumn": 95}, {"stock_name": "\u8a0a\u9023", "stock_id": "5203", "close": 98.5, "volumn": 326}]

這好就是 jQuery UI 資料表格遠端內容中的 aaData 屬性, 因此上面 bootstrapTable() 的 data 屬性就要取 data.aaData 值給它, 不是 get_stocks_list 的整個傳回值 data.

最後一個 Bootstrap 模板是測試 Jumbotro 主題網頁, 這是參考書上範例改寫標題而得 :

<!--bootstrap_test_jumbotron.htm-->
{% extends "bootstrap.htm" %}
{% block style %}
{% endblock%}
{% block body %}
  <div class="jumbotron">
    <div class="container">
      <h1>埃及法老王木乃伊展</h1>
      <p>即日起開放訂票</p>
      <p>
        <a class="btn btn-primary btn-lg" href="#" role="button">
          購票 »
        </a>
      </p>
    </div>
  </div>
{% endblock%}

只要指定 div 的 class 為 Jumbotron 即可馬上幫網頁套上美麗外衣, 這就是 Bootstrap 受到缺乏美感工程師熱愛的原因.


2. URL 處理函數 urls.py : 

路由處理程式 urls.py 用來將可能接收到的 URL 對映到視圖處理程式 views.py 中的函數, 由函數決定如何回應客戶端 (包含隱性的 URL 要求例如 Ajax) :

#urls.py
from django.contrib import admin
from django.urls import path
import mysite.views as views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jqui_test_tabs/', views.jqui_test_tabs),
    path('jqui_test_tab2/', views.jqui_test_tab2),
    path('jqui_test_tab3/', views.jqui_test_tab3),
    path('jqui_test_themes/', views.jqui_test_themes),
    path('jqui_test_datatables_array/', views.jqui_test_datatables_array),
    path('jqui_test_datatables_ajax/', views.jqui_test_datatables_ajax),
    path('get_stocks_list/', views.get_stocks_list),
    path('ezui_test_tabs/', views.ezui_test_tabs),
    path('ezui_test_tab2/', views.ezui_test_tab2),
    path('ezui_test_tab3/', views.ezui_test_tab3),
    path('ezui_test_themes/', views.ezui_test_themes),
    path('ezui_test_datagrid_ajax/', views.ezui_test_datagrid_ajax),
    path('get_stocks_list_ezui/', views.get_stocks_list_ezui),
    path('bootstrap_test_table_html/', views.bootstrap_test_table_html),
    path('bootstrap_test_table_ajax/', views.bootstrap_test_table_ajax),
    path('bootstrap_test_jumbotron/', views.bootstrap_test_jumbotron),
    path('', views.home),
]

上面凸顯的 4 個 URL 都屬於隱性 URL, 直接在 views.py 中做出回應, 故不在首頁 home.htm 中, 也沒有模板網頁.


3. 視圖處理函數 views.py :

視圖處理函數 views.py 負責處理 urls.py 丟過來的 URL 請求 :

#views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.http import JsonResponse
from datetime import datetime

def jqui_test_tabs(request):
    return render(request, 'jqui_test_tabs.htm', {})

def jqui_test_tab2(request):
    tab=2
    return render(request, 'jqui_test_tab2.htm', locals())

def jqui_test_tab3(request):
    return HttpResponse("<h3>這是頁籤 3 (直接回應)</h3>")

def jqui_test_themes(request):
    return render(request, 'jqui_test_themes.htm', {})

def jqui_test_datatables_array(request):
    return render(request, 'jqui_test_datatables_array.htm', {})

def jqui_test_datatables_ajax(request):
    return render(request, 'jqui_test_datatables_ajax.htm', {})

def get_stocks_list(request):
    data={"aaData":[{"stock_name":"台積電",
                    "stock_id":"2330",
                    "close":111.5,
                    "volumn":19268},
                   {"stock_name":"中華電",
                    "stock_id":"2412",
                    "close":95.1,
                    "volumn":7096},
                   {"stock_name":"中碳",
                    "stock_id":"1723",
                    "close":145.0,
                    "volumn":317},
                   {"stock_name":"創見",
                    "stock_id":"2451",
                    "close":104.0,
                    "volumn":459},
                   {"stock_name":"華擎",
                    "stock_id":"3515",
                    "close":104.0,
                    "volumn":95},
                   {"stock_name":"訊連",
                    "stock_id":"5203",
                    "close":98.5,
                    "volumn":326}]}
    return JsonResponse(data)

def ezui_test_tabs(request):
    return render(request, 'ezui_test_tabs.htm', {})

def ezui_test_tab2(request):
    tab=2
    return render(request, 'ezui_test_tab2.htm', locals())

def ezui_test_tab3(request):
    return HttpResponse("<h3>這是頁籤 3 (直接回應)</h3>")

def ezui_test_themes(request):
    return render(request, 'ezui_test_themes.htm', {})

def ezui_test_datagrid_ajax(request):
    return render(request, 'ezui_test_datagrid_ajax.htm', {})

def get_stocks_list_ezui(request):
    data={"totals":"6",
          "rows":[{"stock_name":"台積電",
                    "stock_id":"2330",
                    "close":111.5,
                    "volumn":19268},
                   {"stock_name":"中華電",
                    "stock_id":"2412",
                    "close":95.1,
                    "volumn":7096},
                   {"stock_name":"中碳",
                    "stock_id":"1723",
                    "close":145.0,
                    "volumn":317},
                   {"stock_name":"創見",
                    "stock_id":"2451",
                    "close":104.0,
                    "volumn":459},
                   {"stock_name":"華擎",
                    "stock_id":"3515",
                    "close":104.0,
                    "volumn":95},
                   {"stock_name":"訊連",
                    "stock_id":"5203",
                    "close":98.5,
                    "volumn":326}]}
    return JsonResponse(data)

def bootstrap_test_table_html(request):
    return render(request, 'bootstrap_test_table_html.htm', {})

def bootstrap_test_table_ajax(request):
    return render(request, 'bootstrap_test_table_ajax.htm', {})

def bootstrap_test_jumbotron(request):
    return render(request, 'bootstrap_test_jumbotron.htm', {})

def home(request):
    return render(request, 'home.htm', {})

這裡比較特別之處是回應 Ajax 要求需使用 django.http.JsonResponse() 函數, DataTables 元件要求回應一個以 aaData 為鍵的 JSON 資料. 參考 :

jQuery 套件 DataTables 的測試


4. 測試結果 :

以上檔案準備好後以 python manage.py runserver 指令啟動開發伺服器, 首先是瀏覽 127.0.0.1:8000 顯示首頁 :




jQuery UI 測試結果如下 :






可見當資料長度超過每頁筆數時就會出現換頁按鈕. DataTables 雖然昇版後有些選項功能已被廢棄, 但大部分都還是被保留.

EasyUI 測試結果如下 :






注意, EasyUI 新版的 datagrid 元件在表格標頭部分似乎有 bug, 長度比表格內容本身稍短, 但在舊版卻不會有這種情形.

最後是 Bootstrap 的測試結果 :




參考 :

jQuery UI 學習筆記 (一) : 主題布景 (Themes)
jQuery UI 學習筆記索引
https://datatables.net/upgrade/1.10-convert#Options
https://datatables.net/reference/option/ajax
[JQuery] $.ajax 存取 Json 簡單範例
TypeError: $ (…).bootstrapTable is not a function

2019-10-10 補充 :

修改了近一周終於把三個主要框架的 Django 模板搞定了, 順便把 PHP 測試網站移到 000a.biz 去. 框架久沒有用真的會生疏, 還好當時有留下完整測試紀錄, 要回復記憶不難, 倒是版本變更較麻煩, CDN 雖然很方便, 但可以的話還是保留一份測試 ok 的框架版本在本地主機上比較好. 另外我覺得 Bootstrap 有空值得學一學.

2021-03-09 補充 :

Django 3 對模板路徑有新做法, 參考 :


沒有留言 :