2019年10月10日 星期四

Django 學習筆記索引

二哥上大學後終於有時間回頭來學 Django, 邊學邊寫測試紀錄, 等暑假二哥要學 Python Web 時就有現成教材了.

我覺得 Django 的架構很直覺, 只要具備 Python + HTML 基礎, 快的話大概一周就可以學會用 Django 架設網站了, 因為 Django 本來就是為了能化繁為簡快速架站而設計的後端框架啊! 如果再學一種前端框架例如 Bootstrap, 再怎麼缺乏美學細胞, 也能架出美美的專業級網站.

筆記索引如下 :

# Python 學習筆記 : Django 2 測試 (一) : 請求與回應處理
# Python 學習筆記 : Django 2 測試 (二) : 使用模板與靜態檔案
# Python 學習筆記 : Django 2 測試 (三) : 模板的匯入與繼承
# Python 學習筆記 : Django 2 測試 (四) : 本機前端框架環境配置
# Python 學習筆記 : Django 2 測試 (五) : 在虛擬環境中佈署網站
# 用 EasyUI for jQuery 與 Django 建構網站 (二)


Django 2/3 參考書籍 : 
  1. Python 架站特訓班 : django 最強實戰 (碁峰, 文淵閣)
  2. Python 新手使用 django 架站技術實作 (何敏煌, 林亮昀, 博碩)
  3. Tango with Django 2 (Leif Azzopardi and David Maxwell, 2020, Learnpub)
  4. Building a websie with Django 3 (LearnPub, 2019)
Django 2/3 線上教學資源 :

2020-11-13 補充 : 

因 Django 已升到第三版, 故將標題中的 2 去掉, 此索引將包括 Django 2 以後版本的測試學習筆記. 

2019年10月7日 星期一

測試網站搬遷後要做的配套異動

由於決定不再租用 hostinger 虛擬主機, 所以昨天先下載程式檔案與資料庫, 然後將 test 測試目錄分批上傳 (一次傳 5MB 以下的 zip 檔較保險) 到 000a.biz 免費網站 yhhuang1966.000a.biz 上, 同時更改部落格中的超連結, 測試 OK, 例如 :

# http://yhhuang1966.000a.biz/test/easyuitest/easyui-datagrid-6.htm
# http://yhhuang1966.000a.biz/test/extjstest/extjs_grid_28.htm

關於 000a.biz 申請步驟參考 :

# 申請免費 PHP 虛擬主機 000a.biz 與 000webhost.com

檔案上傳完成後還要做三件事 :


1. 建立資料庫與測試資料表 :

進入主機的資料庫管理程式 myPhpAdmin 新增一個以 "_test" 結尾的資料庫, 例如 "a000b_24296xxx_test", 然後用 SQL 功能建立如下資料表 :
這些是 Ajax 測試程式要用到的資料表.


2. 修改 MySQL 連線資訊 :

因為之前測試 Ajax 功能都是直接把 MySQL 連線資料放在 PHP 程式中, username 與 database 名稱都會不同, 要一一去改很麻煩, 所以這次我乾脆將 MySQL 連線資訊獨立出來, 寫成如下 db.php 檔 :

<?php
$host="sql308.000a.biz";
$username="a000b_24296xxx";
$password="123456";
$database="a000b_24296xxx_test";
?>

然後修改每一個提供 Ajax 資料源的 PHP 程式, 用 include("db.php") 匯進來, 以後搬主機只要改 db.php 即可, 例如如下很多範例程式都會用到的 get_stocks.php :

<?php
header('Content-Type: text/html;charset=UTF-8');
include("db.php");
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
..... (餘略)


3. 修改部落格文章中的超連結 :

切到 HTML 格式, 將內容複製到編輯器中搜尋替換網址, 這很費時 (因為累積太多測試文章了), 有用到時再改.

等我有空搞定 Git 後再來將全部測試源碼都放在 GitHub 上寄存分享.

參考 :

# PHP 列出當前資料夾中所有目錄_PHP教程
# [ PHP ] – 列出該路徑下所有的檔案名稱包含子目錄

2019年10月6日 星期日

2019 年第 40 周記事

2019 的第 40 周已過, 剩下 12 周了, 別再浪費時間, 年底前該完成的事項需加緊進行了, 不要當 "推事" (這官職消失很久了), 事情一年推給一年, 這樣何時才能做完呢? 下周待辦事項 :
  1. 小狐狸健保加掛 (已完成)
  2. 浴室滲水勘查估價
  3. 買安麗濾心 
  4. 無人機證照說明會報名 (11/03 早上) (已完成)
  5. 0056 除息準備 (已完成)  
本周韓劇看完 "陽光先生" 後, 接檔的是愛奇藝 "天才醫生車耀漢", 描寫看一眼花 10 秒就能診斷出病因的醫生車耀漢因協助病人安樂死而被判刑入獄, 在獄中結識同樣因為醫療事故被派往監獄當駐獄女醫生姜詩英, 並在出獄後在韓世醫院變成師徒, 是描寫死刑與安樂死的題材. 女主角是從小美到大的李世榮, 上回是在 "成為王的男人" 看到她與呂珍九出演.

菁菁開學後我本來都騎機車載她上學, 但是怕掉妝她都不帶口罩, 加上前陣子下雨只好開車, 後來每天就要求開車, 我那 Serena 大車耗油, 以前兩周加一次油, 現在每周都要去加油. 車齡 12 年還跑不到 10 萬公里, 我看三年後應該可以跑到 12 萬公里. 坦白說開車還真的蠻享受的, 可以吹冷氣聽音樂, 菁菁則是 ~~~ 補眠.

PHP 虛擬主機 Hostinger 到期不續租

我在 2016 年底向英國業者 Hostinger 租用的 PHP 虛擬主機將於年底到期, 查詢最近的續期方案, 三年期每個月 2.95 英鎊, 合計 106.2 英鎊, 約合台幣 4053 元, 平均每年 1351 元, 每月 113 元, 是三年前首租時的四倍價格 (那時是三年 27 英鎊) :




參考 :

# 購買 Hostinger Premium 虛擬主機服務

難道是先用低價養新客戶, 老客戶反正覺得搬家麻煩就會繼續租下去嗎? 如果是 2 倍以內我還可以接受, 首租的四倍價格我覺得續不下去. 坦白說每個月 113 元並不算貴, 只是 Kimoji 有點不太好而已.

其實主要是考慮我現在已較少用 PHP , 主要的計算移到樹莓派上, 況且 Hostinger 租用三年並未達到預期功用, 與其讓它繼續空轉不如停止節省費用. 如果要用到 PHP, 免費主機例如 000a.biz 等也不錯用, 只要不做大量運算其實很夠用, 我頂多就是放測試範例程式而已, 所以決定不再花錢租主機了, xyz 的網域也打算同時放棄.

今天已備份下載 Hostinger 上的所有檔案與資料庫, 其中的 test 資料夾將上傳到 000a.biz 去存放, 哇, 又要花點時間改部落格上的超連結了.

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 對模板路徑有新做法, 參考 :


重讀統計學

前天在櫃子裡找到兩本以前買的舊書, 一本是在自由路舊書攤蘇富比買的教科書 "商用統計學" (Business Statistics), 這本很舊了, 2003 年出版, 逢甲大學吳榮彬教授譯自原文書第五版 (最新為 2008 第七版), 雖然是 16 年前的舊書, 但譯筆很不錯, 且統計學原理不會變, 所以就拿出來讀了 :




很久以前就想重讀工程統計學, 但我現在主要興趣是金融 Fintech, 所以應該是讀商業統計才對味. 此書範例採用 EXCEL, 我想用 R 語言來改寫, 可收一石二鳥之效.

另外一本是 2004 年在秦麗二手書買的 "實用統計學", 翻譯自 David Moore 的 "The Basic Practice of Statistics", 是以前很受歡迎的統計學教科書 (目前最新為第 8 版), Moore 教授也寫過一本科普暢銷書 "統計讓數字說話" :




上一次讀統計學是 17 年前為了寫碩士論文而讀, 那時有時間壓力, 只囫圇吞棗讀完統計檢定就迫不及待用 SPSS 去處理語音學資料. 畢業後偶而看到統計學好書也會買下來 ~~~ 放著, 都沒時間去看. 現在為了讓學 R 語言有動力, 拿統計學來當材料最適合.

2019年10月3日 星期四

2019年10月2日 星期三

看完陽光先生

今天終於看完陽光先生, 果然是 Sad ending, 尤金崔又把最後一顆子彈用在最重要的地方, 讓高愛信 (崔愛信) 與義兵的孩子們安全地前往滿州, 自己卻長眠於漢城外國人墓地.




此劇會獲得高收視率, 自然是因為韓國人的愛國心, 雖說每一個國家都有把國家廉價出賣的國賊, 但韓國人愛國心的熾烈是令人敬佩的.

歷史上真實存在的最著名義兵當屬刺殺日本首相伊藤博文的安重根了, 他的死真是可歌可泣, 他被逮捕後向日本法庭申明是以義兵中將名義為國家執行任務, 不可與一般暗殺相提並論, 而且拒絕上訴. 他母親知道他被判死刑後, 還派遣他弟弟到獄中告訴他, 應遵大義受死, 切不可卑賤求生, 唉唷, 這樣的母親, 難怪安重根能活得像煙火那般燦爛.

下次去韓國時想去位於首爾南山附近的安重根紀念館景仰一下義兵中將的風采.

4G 門號續約兩年

4G 門號於九月底上網租約到期, 直接在中華電信 App 上續約 1399 不限量 2 年. 兩年前忘了續約, 好幾天上網都卡卡的, Youtube 也連不上, 正要客訴查原因, 才想起是租約到期.