2020年11月30日 星期一

Arduino Nano 33 IoT 開發板

今天在 iThome 看到一篇介紹 Arduino 新板子 Nano 33 IoT 的文章, 才知道去年五月就已經推出, 真是後知後覺. 這兩年我都轉向到 ESP8266/ESP32, 對 Arduino 失去關注的動力, 沒想到 Arduino 繼續推陳出新, 特別是針對 Nano 這款我以前最喜歡的板子 : 

深入淺出看ARDUINO NANO 33 IOT

這四款板子係針對不同需求而設計 :
  • Arduino Nano Every :
    這是以前的 Nano 進化版 (MPU 改用 ATMega4809), 5V 工作電壓, 20 MHz 時脈, 48KB Flash, 6KB SRAM, 8 個 ADC 腳, 5 個 PWM 腳, USB 晶片改用 ATSAMD11D14A, 適合一般專案用.
  • Arduino Nano 33 IoT
    MPU 採用 SAMD21 Cortex®-M0+ 32bit ARM 架構, 3.3V 工作電壓, 48 MHz 時脈, 256KB Flash, 32KB SRAM, 14 個 DIO 腳, 8 個 ADC 腳, 1 個 DAC 腳, 11 個 PWM 腳, USB 晶片內建於 MPU 中, 內建 WiFi 與藍芽能力與安全 IoT 連線, 適合 WiFi/BLE 物聯網專案用.
  • Arduino Nano 33 BLE
    MPU 採用 ARM® Cortex™-M4 架構之 nRF52840, 3.3V 工作電壓, 64 MHz 時脈, 1MB Flash, 256KB SRAM, 14 個 DIO 腳 (全部具備 PWM), 8 個 ADC 腳, 無 DAC 腳, USB 晶片內建於 MPU 中, 內建 9 軸感測器與藍芽安全 IoT 連線, 適合穿戴式或短距離無線訊專案用.
  • Arduino Nano BLE Sense
    MPU 採用 ARM® Cortex™-M4 架構之 nRF52840, 3.3V 工作電壓, 64 MHz 時脈, 1MB Flash, 256KB SRAM, 14 個 DIO 腳 (全部具備 PWM), 8 個 ADC 腳, 無 DAC 腳, USB 晶片內建於 MPU 中, 內建麥克風, 溫溼度感測器, 氣壓計, 亮度與色彩感測器, 9 軸感測器, 以及藍芽安全 IoT 連線功能等等, 適合各種物聯網專案用.

這四款板子最便宜的是 Nano Every, 其次是 Nano 33 IoT, 然後是 Nano BLE, 最貴的是 Namo BLE Sense, 參考露天 : 


2020 年第 48 周記事

本周為了還書都在學習 Django, 暫停了 jQuery Mobile 與 PHP. 希望在年底前可以把 Django 與 jQuery Mobile 都學完, 結束網頁技術的學研進度, 因為我想回應羅兄的建議, 繼續 Raspberry Pi 物聯網的研究, 想看的書多到看不完, 能定下心來好好看完一本書真是一種享受. 最近常聽陳依琳的二胡演奏, 雖然好想去學, 但眼前還有更重要的事要做, 藝術這種上層饗宴, 我現在還無福消受. 

上週五阿蘭又感染發燒住院, 這回同樣暫時沒有一對多看護, 請了木棉花的鍾大姊幫忙到周三中午, 差三小時就五天費用 10700 元, 昨日去探望阿蘭時順便交付看護費, 她只休息一天又接新的看護工作. 與鍾大姊聊了一下, 方知應該只大我兩三歲的她已當了阿嬤, 因為之前在腦麻協會的行政工作待遇不高, 所以轉職當看護, 想必即使當了阿嬤生活壓力還是有的, 真是家家有本難念的經, 但我引我在荷馨看到的孩子為例, 不論人生境遇如何, 我們通常是身在福中不知福而自怨自艾, 你以為的不幸在某些人眼裡那算什麼. 能健康地活著就是一種恩賜. 

昨日傍晚本想去獅形頂跑山, 卻剛好下起冬雨, 只好到頂樓繼續雨水儲存桶的接管, 上週進度完成底部鑽孔工作, 早上去市集買菜時經過小漢又再買一個出水口接頭, 但裝上去才發現上周打孔時打得太低, 因底部有環狀突起, 出水口接頭從裡面穿出後突出桶外的部分太短根本無法鎖, 所以又再用電鑽磨平一些, 塑膠墊片放內側鎖緊應該可以防止漏水, 不須打 Silicon :




三周前種下的大陸妹與白菜本周已可採收, 白菜差點被樹螺與蝸牛吃個乾淨, 還好每晚飯後爸去撿拾, 才能保住一些. 兩年來無農藥種菜的經驗, 發現大陸妹與菠菜最好種, 比較不會被蟲與蝸牛吃, 而白菜, 高麗菜, 絲瓜, 花椰菜這些沒農藥都很慘, 除了絲瓜有用網做初期保護尚可外, 其餘均被吃個精光, 所以今年主要種大陸妹. 另外小舅在我菜園種的橙蜜也可採收了 :





11 月底了, 再過 4 周就 2021 年了, 真的是韶光易逝啊. 原本看完 "非武裝救援" 想專心學研, 但水某找到 JTBC 的 "重返十八歲", 這種時光劇最吸引我的目光了, 但這部不是穿越, 而是在現時中回到過去的形體, 描述在 18 歲讀高中時因為同校女友鄭多貞 (金荷娜飾) 懷孕而奉子成婚的高中籃球隊主將洪大英 (尹相鉉飾), 畢業後就為了扶養龍鳳胎兒女到處打工, 18 年後卻在一場高中同學會因為修理洗衣機的工作遭到同學訕笑, 酒後跟鄭多貞說他後悔了, 鄭多貞傷心之餘提出離婚; 接著因為學歷失去升遷機會與工作, 失望之餘回到母校體育館, 看到當年叱吒一時的球場感慨萬千, 但當球進入籃框的剎那卻突然停電了, 妙的是等燈光恢復時他的身體居然回到 18 歲時的模樣, 然後以轉學生高宇英的身分回到學校與自己的龍鳳胎成了同學 ..... 真是吸引人的故事, 已看到第十集. 劇中演少年洪大英的李到晛怎麼好眼熟, 原來他曾在德魯納酒店裡演出 IU 的前世情人 (公主殿下的護衛隊長), 就是劇中的那隻癡情守護 IU 的蝴蝶. 我看到下面這篇報導後, 覺得他真是很不錯的孩子 :


2020年11月29日 星期日

Python 學習筆記 : Django 3 測試 (七) : 資料庫操作

資料庫操作是動態網頁的核心, 不論是會員登入, 部落格, 留言板, 或者討論區等功能都必須利用資料庫來儲存內容, Django 預設採用一個檔案式的簡易型資料庫管理系統 SQLite (與 MySQL 相容), 但只適用於測試開發或小型網站系統之用, 真正上架營運時會使用 MySQL 或 Postgre 等容量或效能較大之資料庫, 但 Django 使用 ORM 操作資料庫, 因此轉換資料庫非常容易, 只需調整設定, 不需要修改應用程式. 

本系列之前的筆記參考 : 


以下測試參考了如下書籍 : 
  1. Python 架站特訓班 : django 最強實戰 (碁峰, 文淵閣)
  2. Python 新手使用 django 架站技術實作 (何敏煌, 林亮昀, 博碩)
  3. Tango with Django 2 (Leif Azzopardi and David Maxwell, 2020, Learnpub)
以及線上教學網站 : 


互動網站的資料庫操作主要是 CRUD (Create, Retrieve, Update, Delete), 傳統關聯式資料庫使用 SQL 語言來實現 CRUD 的新增, 查詢, 更新與刪除操作, 其對應與範例如下所示 :


 CRUD 操作 SQL 指令 SQL 指令範例
 Create (新增) INSERT SELECT * FROM users WHERE age < 40
 Retrieve (查詢) SELECT INSERT INTO users(name,age) VALUES('Jim',25)
 Update (更新) UPDATE UPDATE users SET country='TWN'
 Delete (刪除) DELETE DELETE FROM users WHERE age > 30


參考 :

最常用的 SQL 指令

有別於其他後端技術直接使用 SQL 語法存取資料表, Django 資料庫操作並不需要直接面對 SQL, 而是使用抽象化的物件關聯對應模型 ORM (Object Relational Model) 將底層 SQL 操作包裝成物件方法, 具體來說就是提供了 django.db.models.Model 類別作為應用程式與資料庫之間的介面 (MTV 架構中的 M 指的就是這個 Model 類別), 只要使用由這個類別所建立之資料庫模型物件就能操作資料庫與資料表. 由於各家資料庫系統所使用之 SQL 語法存在差異, ORM 將操作 SQL 的細節包裝起來最大的好處就是幾乎可以無痛轉換資料庫, 從預設的 SQLite 轉換至 MySQL 或 Postgre 等資料庫時只要改換設定即可, 不需要修改程式或與調整 SQL 語法. 

Django 的資料庫與 App 檔案結構綁在一起, 建立 App 時也會同時建立資料庫相關的檔案與目錄, 例如在前一篇測試中使用 python manage.py startapp helloworld 指令建立第一個應用程式 helloworld 時所建立與資料庫相關之檔案目錄如下圖所示 : 





以下摘要整理與資料庫操作相關之檔案和目錄 : 


1. 預設資料庫設定 :

在專案中建立第一個 App 時會在第一層專案目錄下產生預設的空白 SQLite 資料庫檔案 db.sqlite3, 此資料庫優點是備份方便 (非伺服器型之單一檔案) 且與 MySQL 相容, 適合測試開發或小型專案使用, 但在擴充性與效能上較不足, 實際佈署營運時通常改用 MySQL 或 Postgre 等資料庫 (Django 社群較偏愛 PostgreSQL, 例如免費主機 Heroku 支援 PostgreSQL), 但 Django 採用 ORM 架構, 因此只要修改資料庫設定, 不需修改應用程式. 

Django 的資料庫設定放在第二層專案目錄下的 settings.py 檔案裏, 其中的 DATABASE 變數即資料庫的設定, 預設為 SQLite, 在專案測試階段通常使用預設之 SQLite 資料庫, 不需要修改 : 

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

當系統要上線實際佈署到伺服器時就需要調整變數 DATABASES 裡的預設資料庫設定, 例如改用 MySQL 資料庫時所需的設定語法如下 :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mydatabase',
        'USER': 'root',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

若使用 Postgree 資料庫, 設定語法例如 :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'mydatabase', 
        'USER': 'tony1966', 
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1', 
        'PORT': '5432',
    }
}

主要的差別就是 ENGINE 與 PORT 不同而已. 參考 :



2. 資料模型定義 : 

最重要的是 App 目錄下用來定義資料表結構 (資料模型) 的 models.py 檔, 其預設內容為 :

from django.db import models     

# Create your models here.

預設已匯入 django.db.models 模組, 定義資料表結構 (資料模型) 只要加入下列兩個部份即可 :
  • 繼承 models.Model 類別定義一個資料模型子類別 (即資料表)
  • 此模型類別應定義一個 __str__() 方法傳回某個欄位名稱
注意,  __str__() 方法的作用是在資料庫後台管理程式 admin 中顯示資料表內容時會列出資料表內作為代表之某個欄位內容 (例如標題等), 如果模型的類別內沒有定義此方法, 則在資料庫後台葉面顯示此模型之資料表紀錄時將僅顯示此為物件 (xxx object) 而已, 無法得知這是哪一項紀錄, 可讀性不佳, 定義模型類別之語法如下 : 

class table_name(models.Model):
    field_name_1=models.xxxField(param=value)
    field_name_2=models.xxxField(param=value)
    ...
    def __str__(self):
        return self.field_name_x   #選擇可代表該資料物件之欄位名稱傳回

注意, 類別名稱慣用法為首字母大寫. Django 所有的資料模型都繼承自 django.db.models.Model 類別, 每一個繼承 Model 的子類別相當於是資料表 (table); 其實體 (物件) 對應到一筆紀錄 (row 或 record); 物件的屬性則對應到資料欄位 (fields), 而物件方法則對應到資料庫的 CRUD 操作, 如下表所示 :


 ORM 模型 關聯式資料庫
 類別 (Class) 資料表 (Table)
 物件 (Object) 紀錄 (Row)
 屬性 (Attribute) 欄位 (Field)
 方法 (Method) CRUD 操作


Model 類別會將其實體物件之屬性與方法轉換成 SQL 語言, 所以程式設計者只要呼叫模型物件的方法 (create, get, save, delete 等) 即可存取資料庫, 不須使用 SQL 語法, Django 的 ORM 封裝了這些資料庫的介面細節. 

網站後端開發應先依據需求進行資料庫設計, 規劃需要哪些資料表, 每個資料表有哪些欄位, 以及資料表之間的關聯性等, 然後再進行程式設計. 若資料庫未規劃完全即進行程式設計, 很可能中途要回頭更改資料庫, 連帶影響已設計之程式需配合更改.

Django 的 django.db.models.Model 類別提供許多方法可定義資料表的欄位型態以及與其他資料表的關聯, 常用方法如下表所示 :


 欄位型態方法 說明
 BooleanField() 儲存布林值=True/False, 參數 : 無
 CharField() 儲存單行文字輸入內容, 參數 :
 max_length=最大字元數 (上限 254)
 SlugField 與 CharField() 相同, 但用來儲存 URL 的一部分
 TextField() 儲存多行文字輸入 textarea 內容, 參數 : 無
 IntegerField() 儲存整數 (-2147483648 ~ 2147483647), 參數 : 無
 BigInteger() 儲存長度 64 位元的大整數
 PositiveIntegerField() 儲存正整數 (0 ~ 2147483647), 參數 : 無
 DecimalField() 儲存固定精度之十進位數 (Decimal 物件), 必要參數 :
 max_digits=最大位數
 decimal_places=整數位數
 FloatField() 儲存浮點數, 參數 : 無
 DateField() 儲存日期, 格式 datetime.date, 可選參數 : 
 auto_now=自動儲存今日日期
 auto_now_add=只在建立時儲存今日日期
 DateTimeField() 儲存日期時間, 格式 datetime.datetime, 可選參數 : 
 auto_now=自動儲存今日日期時間
 auto_now_add=只在建立時儲存今日日期時間
 EmailField() 儲存有效之電子郵件, 可選參數 :
 max_length=最大字元數 (上限 254)
 FileField() 檔案上傳欄位, 參數 : 無
 ImageField() 圖檔欄位 (繼承自 FileField, 須配合使用 Pillow 套件)
 URLField() 儲存完整的 URL (繼承自 CharField), 可選參數 :
 max_length=最大字元數 (預設 200)
 AutoField() 自動增量主鍵欄位, 參數 primary_key=True
 ForeignKey() 關聯欄位, 用來指向其他資料表的主鍵 (預設=id) :
 第一參數=所指之資料表類別名稱
 第二參數 : on_delete=models.CASCADE


參考 :


最後的兩個方法 AutoField() 與 ForeignKey() 所定義的欄位比較特殊, 其中 AutoField() 用來設定自動增量主鍵 (primary_key) 欄位, 其實 Django 會自動為每一個資料表添加一個主鍵欄位 id, 但若想自行指定主鍵欄位可以使用 AutoField() 方法. 參考 :


其次, ForeignKey() 欄位則是用來關聯到其他資料表 (稱為外部鍵), 此欄位的第一參數所關聯之資料表類別名稱, 用來指向該資料表的主鍵, Django 預設會自動將其關聯到該資料表之 id 主鍵, 例如上面的 users 資料表中可以添加一個 nationality (國籍) 欄位指向另一個資料表 nations : 

nationality=models.ForeignKey(nations, on_delete=models.CASCADE) 




ForeignKey() 的第二參數用來設定當被參照的外部鍵被刪除時參照者應採取之動作, Django 的 models 套件定義了如下常用之屬性值 :
  • models.CASCADE : 同步執行刪除動作
  • models.PROTECT : 不刪除且拋出一個 ProtectedError 例外
  • models.SET_NULL : 將此外部鍵設為 null (須先以 null=True 參數定義此欄位)
  • models.SET_DEFAULT : 將此外部鍵設為預設值 (須先以 default 參數定義此欄位預設值)
  • models.DO_NOTHING : 不採取動作
呼叫欄位型態函數時除了必要參數外, 還可以傳入選項參數, 常用選項參數如下表 :


 欄位選項參數 說明
 null 欄位值是否可為 null, 值=True/False (預設)
 blank 欄位值是否可為空白, 值=True/False (預設)
 default 欄位預設值 (或可呼叫物件)
 unique 欄位值是否為唯一, 值=True/False (預設)
 primary_key 欄位是否為主鍵, 值=True/False (預設)
 editable 欄位是否顯示於 admin 後台, 值=True (預設) /False 
 choices 設定 select 欄位之選項 (可用 list 或 tuple)
 help_text 顯示於表單元件上的額外資訊
 verbose_name 欄位之人類可讀名稱, 未指定以欄位名稱代替 (底線變空白)


參考 : 



3. 資料模型與資料庫之遷移與同步 : 

App 目錄下還有一個遷移子目錄 migrations, 其用途為記錄資料模型定義與版本變更, 預設只有一個檔案__init__.py 與目錄 __pycache__, 例如上一篇測試中的 helloworld 應用程式 :




這個 migrations 子目錄其實就是用來維持資料模型與資料庫同步的中介目錄, 每次修改資料模型檔 models.py 後, 必須依序使用下列兩個將資料模型之變動情形先遷移或紀錄到這個目錄下 (makemigrations), 然後再據此遷移到資料庫中完成同步 :
  • python manage.py makemigrations AppName   
    此指令會將應用程式 AppName 目錄下的 models.py 資料模型變更紀錄在該 App 的 migrations 子目錄內. 若省略 AppName 則會對此專案下的所有 App 進行資料模型變更紀錄作業.
  • python manage.py migrate AppName
    此指令會根據應用程式 AppName 目錄下的 migrations 子目錄所記錄的變更內容, 進行資料模型與資料庫同步作業. 若省略 AppName 則會對此專案下的所有 App 進行資料庫同步作業. 
因為在上一篇測試的 helloworld 應用程式中沒有使用資料庫, 因此即使沒有執行 migrations 也不妨礙 App 的執行. 


4. 通訊錄 App 資料表測試 : 

以下參考 "Python 架站特訓班 : django 最強實戰" 這本書中第四章的 student 資料表範例加以修改為通訊錄 App, 於本機 localhost 中測試 contact 資料表的 CRUD 操作. 


(1). 建立與註冊應用程式 :   

進入虛擬環境在第一層專案目錄下以下列指令建立 contact 應用程式 : 

python manage.py startapp contact

D:\django\venv>Scripts\activate     
(venv) D:\django\venv>cd mysite    
(venv) D:\django\venv\mysite>python manage.py startapp contact    

在專案目錄上一層用 tree 指令可以顯示此專案目錄結構下新增了 contact 應用程式子目錄 : 

(venv) D:\django\venv>tree mysite  
D:\DJANGO\VENV\MYSITE
├─contact
│  └─migrations
├─helloworld
│  ├─migrations
│  │  └─__pycache__
│  └─__pycache__
├─mysite
│  └─__pycache__
├─static
│  ├─css
│  ├─images
│  └─js
└─templates

建立 App 後須至專案設定檔中註冊此應用程式, 因為後續作資料模型遷移與同步等作業時, Django 管理程式 manage.py 都會檢查 App 是否存在, 開啟第二層專案目錄下的 settings.py 檔, 找到 INSTALLED_APPS 這個串列變數, 在最後面添加新增的應用程式 contact :

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloworld',
    'contact'


(2). 編輯資料模型檔 models.py :    

開啟 App 目錄 contact 下的 models.py, 定義一個繼承自 Model 的 contact 類別 (相當於資料表), 並呼叫 models 模組的欄位定義方法定義資料表內的欄位型態 : 

#models.py
from django.db import models

class contact(models.Model):
    cName=models.CharField(max_length=20, null=False)
    cSex=models.CharField(max_length=2, default='M', null=False)
    cBirthday=models.DateField(null=False)
    cEmail=models.EmailField(max_length=100, blank=True, default='')
    cPhone=models.CharField(max_length=50, blank=True, default='')
    cAddress=models.CharField(max_length=255, blank=True, default='')
    
    def __str__(self):
        return self.cName

此資料模型定義了六個欄位的資料表 contact, 並傳入選項參數約束欄位之性質, 例如設定預設值的 default 以及可容許空白的 blank 與可容許無值的 null 等. 


(3). 製作資料模型遷移紀錄與同步資料庫 :

只要資料模型定義檔 models.py 有修改就要執行 migrations 動作使模型與資料庫能同步, 首先用 makemigrations 指令製作 contact 模型之遷移紀錄, 此處為了單純起見指定僅遷移 contact 應用程式之模型 (若不指定則是遷移所有 App 的資料模型) : 

(venv) D:\django\venv\mysite>python manage.py makemigrations contact   
Migrations for 'contact':
  contact\migrations\0001_initial.py
    - Create model contact

可見完成後在 App 的 migrations 子目錄下產生了第一個遷移紀錄檔 0001_initial.py : 




其內容如下 :

# Generated by Django 3.1.3 on 2020-11-27 08:40
from django.db import migrations, models

class Migration(migrations.Migration):
    initial = True
    dependencies = [
    ]
    operations = [
        migrations.CreateModel(
            name='contact',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('cName', models.CharField(max_length=20)),
                ('cSex', models.CharField(default='M', max_length=2)),
                ('cBirthday', models.DateField()),
                ('cEmail', models.EmailField(blank=True, default='', max_length=100)),
                ('cPhone', models.CharField(blank=True, default='', max_length=50)),
                ('cAddress', models.CharField(blank=True, default='', max_length=255)),
            ],
        ),
    ]

此遷移紀錄就是模型與資料庫同步所需要的中介檔, 它其實是從 models.py 轉換而來的. 注意, Django 自動為此資料表添加了一個名為 id 的自動增量主鍵欄位, 這也是使用 ForeignKey() 外部鍵欄位關聯其他資料表時預設之綁定欄位. 

建立遷移紀錄後即可用 migrate 指令進行模型與資料庫同步動作, 同樣也是指定 contact 應用程式之模型 (若不指定則同步所有 App 之模型), 也就是執行上面的中介檔程式  0001_initial.py  :

(venv) D:\django\venv\mysite>python manage.py migrate contact   
Operations to perform:
  Apply all migrations: contact
Running migrations:
  Applying contact.0001_initial... OK

此 migrate 指令執行 contact.0001_initial.py 中介檔後會在專案預設資料庫 db.sqlite3 中建立資料表, 使用 DB Browser for SQLite 軟體瀏覽 db.sqlite3 可知已根據資料模型建立了三個資料表 : 




可見同步時 ORM 真正建立的資料表名稱是 contact_contact 而非 contact, 點選此資料表切換到 Browse Data 頁籤可看到 Django 自動添加的 id 自動增量主鍵欄位 :




利用此軟體可以執行 SQLite 資料庫的 CRUD 操作, 關於 DB Browser for SQLite 用法參考 :



(4). 用內建應用程式 admin 管理資料庫 : 

雖然可以使用外部軟體例如 DB Browser for SQLite 來管理 SQLite 資料庫, 但其實不假外求,  Django 本身就內建了一個類似 PHP 的 phpMyAdmin 那樣的資料庫後台管理應用程式 admin, 但資料表必須經過註冊程序, 並且設定管理者帳戶後我們才能使用此後台 App 來管理資料庫. 

在建立 App 時會在 App 目錄下產生一個名為 admin.py 的資料模型註冊管理程式 :




開啟 admin.py 可知預設只是匯入了 django.contrib.admin 這個 App 模組而已 :

from django.contrib import admin

# Register your models here.

欲在 admin 後台應用程式中管理此 App 之資料表, 需在此 admin.py 程式中註冊此 App 所用到的的資料表 (亦即在 models.py 中所定義之資料模型名稱), 因為 contact 應用程式只有一個資料表, 因此只要從 contact.models 模組匯入 contact 類別, 並呼叫 admin.site.register() 方法註冊 contact 模型類別即可 :

from django.contrib import admin
from contact.models import contact     #匯入資料模型類別 (即資料表)

admin.site.register(contact)                   #註冊資料表

藍色部分為新增程式碼, 編輯後存檔, 這樣這個資料表即納入 admin 應用程式管理了, 但必須先建立後台管理員帳戶才能登入系統, 先按 CTRL+C 關閉測試伺服器, 執行 createsuperuser 指令設定超級使用者時卻出現錯誤 : 

(venv) D:\django\venv\mysite>python manage.py createsuperuser     

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
......
......
django.db.utils.OperationalError: no such table: auth_user    

原因是在上面使用 migrate 指令同步資料庫時指定了 contact 應用程式, 所以只同步了 contact 這個 App 的模型, 但 admin 這個內建的後台 App 使用了一些內建資料模型, 但卻沒有同步到資料庫中, 因此找不到 auth_user 等資料表, 必須重新同步所有 App 的資料模型才行, 先關閉 DB Browser for SQLite 程式 (否則會 lock 住 db.sqlite3 資料庫檔) : 

(venv) D:\django\venv\mysite>python manage.py migrate     
Operations to perform:
  Apply all migrations: admin, auth, contact, contenttypes, sessions   
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK


可見內建應用程式也許多資料模型, 可用 DB Browser for SQLite 程式開啟 db.sqlite3 瀏覽 : 




這時再次使用 createsuperuser 就可以順利設定後台管理員帳密了 :

(venv) D:\django\venv\mysite>python manage.py createsuperuser    
使用者名稱 (leave blank to use 'user'): tony1966
電子信箱: tony1966@brabra.com.tw
Password:
Password (again):
Superuser created successfully.

然後用 runserver 重新開啟測試伺服器, 在瀏覽器輸入網址 127.0.0.1:8000/admin :




登入成功後即進入資料庫後台管理網頁, 在 "網站管理" 項下會列出各 App 的資料表, 此處 contact 應用程式底下僅有一個資料表 contact :




按右方的 "+新增" 鈕會出現輸入表單可新增紀錄 :




連續新增兩筆後結果下如下 :




注意, 這裡顯示聯絡人之姓名的原因是在 models.py 裡面定義 contact 模型類別時有定義了 __str__() 方法並傳回此類別之 cName 屬性之故 : 


    def __str__(self):
        return self.cName   

將 models.py 中的 __str__() 方法去除, 則預設將傳回物件本身, 因而只顯示 object : 



這樣就無法得知該紀錄到底是哪一筆紀錄了. 

在 __str__() 方法中傳回欄位名稱只能在紀錄列表顯示一個欄位值, 如果要顯示多個欄位資料必須在 admin.py 程式中定義一個繼承 admin.ModelAdmin 類別之子類別, 然後將欲顯示之欄位名稱放在元組中指派給 list_display 變數, 最後在註冊資料模型時將此子類別做為第二參數傳給 admin.site.register() 方法即可, 例如 :

# admin.py
class contactAdmin(admin.ModelAdmin):
    list_display=('id', 'cName', 'cSex', 'cBirthday', 'cEmail', 'cPhone', 'cAddr')
admin.site.register(contanct, contactAdmin)

結果如下 :




此 ModelAdmin 子類別除了提供顯示多欄位功能外, 也可以指定過濾欄位, 或進行搜尋與排序, 這需要在 admin.py 程式中添加 list_filter (過濾欄位元組), search_fields(搜尋欄位元組), 以及 ordering (排序欄位元組) 這三個變數, 修改後的 admin.py 如下所示 : 

#admin.py
from django.contrib import admin
from contact.models import contact

class contactAdmin(admin.ModelAdmin):
    list_display=('id', 'cName', 'cSex', 'cBirthday', 'cEmail', 'cPhone', 'cAddress')
    list_filter=('cName', 'cSex')  
    search_fields=('cName',)  
    ordering=('id',)  

admin.site.register(contact, contactAdmin)

注意, 這四個變數值必須為串列或元組, Python 的串列與元組若只有一個元素後面必須有一個逗號, 否則會出現錯誤. 其次 ordering 預設為遞增排序, 如果要做遞減排序, 則要在欄位前加 "-" 字元, 例如 ordering=("-id",). 重新整理網頁結果如下 :




可見在記錄列表以 id 排序 (遞增), 上方出現了搜尋框, 可搜尋資料表內之紀錄內容; 右方則出現了過濾選項. 

2020年11月28日 星期六

申請 GitBook 帳號

最近找到一個 "Django Girl 學習指南" 這個很不錯的網站, 今天在找 ES6 的資料時又找到 "從ES6開始的JavaScript學習生活" 這個也是左索引右內容簡潔兩欄式架構的網站, 仔細瞧網址都是 gitbook.io 網域, 查了一下原來是一個撰寫網路文件的網站服務, 免費帳號提供 16 個空間 (space), 大概就是 16 個主題吧! 用來寫教學文件還不錯用, 所以就申請了一個帳號玩玩看. 

https://www.gitbook.com/

我原先以為這是 GitHub 的姐妹網站, 但看了 About 沒找到任何與 GitHub 相關資訊, 所以應該沒啥關係, 只是剛好都有 Git 而已. 


高科大還書 2 本 (Django + jQuery)

本周母校圖書館還書兩本 :
Django 這本因為被預約了必須先還, 事實上這本我已看得差不多了, Django 的學習科目大概有專案, App, 模板, 靜態檔案, 資料庫, 以及 Cookie 與 Session 這六大項, 剩下的就是實作. 我只剩下 Cookie 與 Session 測完就大功告成了. 要不是有人預約, 不知要拖到何時哩. 此書雖然用的是較舊的 Django 1.11 版, 但編排很簡約不囉嗦, 是本好書. 

jQuery 這本是第二版, 很久之前受訓時有拿到第一版的書, 但其實沒有甚麼差異. 今天去還書看到架上進了好幾本新書, 幸好有帶這本去還借回兩本, 其他只好等消化完手邊的書再去借, 我的卡已 30 本借滿檔沒辦法.  

2020年11月24日 星期二

程式員的區塊鏈好書

最近注意到一則關於數位貨幣的新聞, 說中國人行正緊鑼密鼓準備讓人民幣數位化, 其長遠戰略目標是擺脫美元束縛, 並企圖建立起人民幣支付的數位金融霸權云云, 參考 : 


這就挑起了我對區塊鏈的興趣, 數位貨幣底層的技術是區塊鏈, 看來這已經是非常成熟且正在運行的金融科技, 我好奇是否有哪些書可讓程式員實際參考學習的區塊鏈實作書籍? 今天花了點時間調查了一下, 發現下面這幾本值得一讀 : 

1. Hands-On Blockchain for Python Developers (2019, Packt)


Source : 天瓏


與傳統使用類似 Javascript 的 Solidity 語言不同, 此書使用與 Python 語法極為類似的 Vyper 語言來撰寫智慧合約, 此語言是以太坊共同創辦人布特林 (Vitalik Buterin) 以 Python 為藍本為智慧合約開發的語言. Vyper 與 Solidity 的差異參考 : 

類 Python 的合約語言 Vyper 開發入門:與 Solidity 差異、用 Truffle 部署、ERC20 賣幣合約實做


2. Learn Blockchain Programming with JavaScript  (2019, Packt) :

這本書是我認為最適合熟悉 Javascript 的網頁程式設計師看的區塊鏈入門書, 介紹如何使用 Javascript 與 Node.js 建構自己的區塊鏈原型與去中心化網路, 在此網路中不僅可挖掘新的區塊, 也可以建立不可變更的交易, 透過書中範例可以徹底了解區塊鏈技術的底層是如何運作的. 


Source : 天瓏

3. Beginning Ethereum Smart Contracts Programming: With Examples in Python, Solidity, and JavaScript (Apress, 2019)

此書作者是新加坡知名區塊鏈技術訓練機構 Developer Learning Solution 創辦人李孟韋, 也是 Oreilly 多本暢銷書作家. 此書使用 Python, Javascript, 以及 Solidity 語言為例介紹如何撰寫乙太幣的智慧契約, 可讓讀者不需閱讀大批文件而能快速上手. 


Source : 天瓏

2020年11月23日 星期一

好書 : Building a websie with Django 3

此書為澳洲出版商 NGW 出版的 Django 3 書籍, 作者以淺顯的範例說明 Django 3 架站的步驟, 我覺得是目前寫得最好的一本 Python Web 入門書, 例如為什麼 Django 會有上下兩層專案目錄 (Ans :下層才是真正的專案目錄, 上層只是容器可以隨意改名)? 為什麼用 startapp 建立 App 時不會在其目錄下自動產生 App 的路由器程式 urls.py (Ans : 不是每個 App 都需要 View) ? 這些問題應該都曾掠過每個學習者心頭, 但答案我在別本書上從來沒讀過, 所以這是非常不錯的一本書 :


Source : Amazon


此書副標題 "Go from zero knowledge to your own website using the easiest to learn programming language on the Internet (讓門外漢以最簡單的方式在互聯網學習程式語言並建立自己的網站)" 確實所言不虛. 

2020年11月22日 星期日

市圖還書 1 本

本周市圖還書一本 (有人預約) : 

  1. 最聽話的人最先被淘汰 : 當學校所教的九成都是洗腦, 「渾然忘我力」才是網路世代的關鍵競爭力! /

但早上去還一本, 卻借回兩本 Python 的書 (GUI + 機器學習), 反而越借越多了. 


2020 年第 47 周記事

今天下午突然接到老張電話, 說剛協助南屏別院童軍活動來鄉下拔蘿蔔, 想來坐一下聊天, 我想那真是太好了, 趕緊切好水果恭候大駕, 昨日本來與大帥老張等約好見面, 卻因為周五接到養護中心電話說吾妹阿蘭發燒送醫, 所以下午就請假回鄉下處理住院事宜, 並通知大帥我無法赴約. 

阿蘭因反覆尿路感染住院此次為今年第五次, 同樣一對多無床位改健保房, 打給上回木棉花的看護潘大姊, 她說在忙就推薦另一位同鄉鍾大姊, 雖然第一次做看護較無經驗, 但人挺好. 

週五晚上上二樓書房找東西, 在抽屜裡找到以前的信件, 坐在地上一封封打開來看, 有許多以前我離開旭青後同事寄來的信, 後來不知為何疏於連絡都斷了音訊, 有些名字都已經忘了, 看了信才回想起那些快要遺忘的過往青澀歲月. 還有那個常寫字條給我的國中女同學年年寄來的賀卡, 唉, 看完突然覺得欠了很多債. 旭青的室友周宗榮給我的信也讓我很感傷, 他是真正可以結交的朋友, 但卻這麼早就走了. 

昨日下午去跑步, 跑完經過南側登山口看到有人上山, 我看時間還早就順路爬上去, 等於是往日兩倍運動量, 結果今天下午剛跑出去就覺得肌肉痠痛, 只好折返. 昨日回程剛好遇到國中同學劉老師剛辦完拔蘿蔔活動, 跟他聊了一下, 原來已從老師崗位退休五年改為務農, 令我羨慕不已, 真後悔當初沒聽外婆的勸告去讀屏師, 要不然我現在也跟他一樣爽退了, 哈哈哈. 

昨天早上起來發現洗衣機似乎沒正常洗完, 正納悶時才聽爸說好像自來水沒進樓上水塔, 趕緊從備用水池抽水, 原來水壓不足, 洗衣機也罷工. 今天早上跟爸去對面張家伙房門口查看自來水表, 發現果然有漏水, 門口的柏油是新鋪的, 據阿清伯母說上周有人來施工鋪新柏油路面, 恐怕是壓路機把水管壓壞了, 連榮彩伯也說他們家自來水水量變少, 要找當水電工姪子來勘查一下了. 

傍晚取消跑步改到樓上鑽水桶, 前年為了儲存雨水在資材行買的 "天x" 牌大儲水桶不中用, 上個月爸發現中間出現一個口字型破口, 應該是塑膠不耐熱裂開所致 :



剛好佃耕的阿正兄載了好幾個中型水桶放在我家後院, 我就跟他要了兩個, 上個月抬上頂樓後一直沒空施工, 今天早上去小漢買了出水口接頭, 趁著天黑前把洞鑽好 : 





我還沒買鑽孔器, 所以只好用電鑽附的小鑽頭鑽很多洞, 再慢慢打穿. 下周再把出水接頭安裝上去, 然後改接管路. 我覺得以後在設計智慧家居時要把水塔水量也納入監控對象, 避免水塔沒水都不知道. 

住家大樓的電梯原訂本周一到五施工更換鋼索主機板等, 上下班, 買東西, 倒垃圾等出入麻煩, 要搭別棟電梯到頂樓, 越過頂樓曬衣場再走樓梯下到我家樓層真的好不方便, 所幸工程提早於周四完成, 下班回到樓下發現已可使用, 全新的控制板有開延長對拿信, 搬東西都很方便. 

本周沒在追韓劇了, 努力學習 Django 中 .... 

好書 : 最聽話的人最先被淘汰

這本書借了很久了, 但都斷斷續續地看, 最近發現有人預約此書, 所以就認真看完並做了簡單的筆記摘要. 真的, 如果沒有人跟我搶書, 還不知要放到何時才真的算看完.



Source : 博客來


此書作者是日本商業怪才崛江貴文, 也是日本知名入口網站活力門 (東京證券交易所上市公司) 的前老闆. 雖然他後來因不實財報事件遭到判刑, 但行事作風總是異於日本社會常軌的他, 其實許多想法還蠻值得參考的. 讀後摘要如下 :
  1. 在網路時代 G (Global) 人才比 L (Local) 人才更具優勢. G 人才以存取的能力來豐富自己的生活, 重視的是資訊判讀; 而 L 人才則是以擁有為基礎, 重視的是具體可見的東西. G 人才最大的特徵是從擁有的思維中解放, 不把價值寄託於擁有之中. 當人們掙脫金錢, 學歷, 地位的束縛時, 就邁向了 G 人才的世界.  
  2. 學校與公司都是洗腦機關, 是讓人變成聽話的笨蛋的場所, 會徹底扼殺人們渾然忘我學習的能力. 學習的可能性其實海闊天空, 不需要學校, 老師, 教科書, 網路的時代每個人都可以更自由, 更渾然忘往地開拓自己的視野, 不要畏懼握住方向盤, 勇敢地踩下油門就對了, 想做的事盡管去做就對了, 不想做的不要勉強去做. 
  3. 希望讀者都能丟掉為了以備不時之需, 應當儲蓄金錢, 學歷, 證照, 技術的錯誤觀念. 以忍耐, 節約為基礎的儲蓄型思考不會為你帶來好處. 儲蓄型思考懶得做價值思考; 而投資型思考則努力於決斷對未來之預測. 投資型思考的人重視的是時間與費用的性價比.
  4. 在公司工作到 65 歲, 然後就靠年金過生活, 這樣的人生設計圖已經太陳舊了. 人生班表應該調整為 "開心持續喜愛的工作, 一直工作到沒幹勁為止" 比較好, 從十歲到九十歲都能工作. 
  5. 不浪費時間與勞力是投資自我的基本. 浪費了金錢還有補救挽回的機會, 浪費了時間則一去不復返, 因為生命有限, 要把勞力與腦袋花在更有益的事情上面. 
  6. 學歷, 職業, 出身, 證照, 這些都是可以拿掉的標籤而已. 如果我現在是十五歲, 根本不會想去讀高中大學, 我應該會利用網路學習, 結交朋友, 早早創業. 
  7. 抱持 "總之先做點甚麼" 想法的人絕對無法勝過 "很想做" 同樣事情的人.
  8. 以 "工作與生活平衡" 的方式來度過人生其實並無法讓人開心, 應該劃分成 "想做" 與 "不想做" 才對, 人生最開心的是把百分之百的人生投注在想做的事情上, 而非去想著如何平衡, 人生過得極端沒關係, 不平衡也沒關係.  
  9. 做喜愛的事是玩樂不是工作, 做不想做的事那才是工作. 應該把遊戲, 工作, 學習視為一體, 最開心的人生是徹底在工作中玩樂的人生. 

2020年11月19日 星期四

Python 學習筆記 : Django 3 測試 (六) : 應用程式

距離年初學習 Django 已近一年, Django 也從 2 版躍升到 3 版, 最近因為要所借的書被預約了必須在還書前看完, 所以暫時重回 Django 學習軌道, 希望這次能一舉搞定 Django. 以下測試改用最新版的 Django 3, 其實新版最大的亮點是支援非同步, 整體架構並無改變, 只有專案設定檔的絕對路徑用法改變而已. 

本系列之前的筆記參考 : 


Django 2 官方文件參考 :

在之前的測試中, 我們使用 django-admin startproject mysite 指令自動建立了一個名為 mysite 的 Django 網站, 其基本結構如下 :

mysite (上層專案目錄)

     |____ manage.py
     |____ mysite  (下層案目錄)
                   |_____ __init__.py
                   |_____ settings.py
                   |_____ urls.py
                   |_____ wsgi.py

接著在上層專案目錄下添加模版目錄 (templates) 與靜態檔案目錄 (static), 這個 templates 目錄就是 Django MTV 架構中的 T (Template) :

(venv) D:\django\venv\mysite>mkdir templates 
(venv) D:\django\venv\mysite>mkdir static   
(venv) D:\django\venv\mysite>cd static   
(venv) D:\django\venv\mysite\static>mkdir images    
(venv) D:\django\venv\mysite\static>mkdir css     
(venv) D:\django\venv\mysite\static>mkdir js     

然後在下層專案目錄下添加處理呼叫與回應處理程式 views.py (對應 Django MTV 架構中的 V), 最後修改路由器程式 urls.py 即可讓網頁應用程式網站運作起來, 完整之檔案結構如下 : 

mysite (上層專案目錄)
     |____ manage.py
     |____ templates                 (模版目錄)
     |____ static                        (靜態檔案)
     |             |____ css              (存放 CSS 樣式檔)
     |             |____ js                (存放 Javascript 檔)
     |             |____ images        (存放圖檔)
     |____ mysite                       (下層專案目錄)
                   |____ __init__.py
                   |____ settings.py
                   |____ urls.py         (專案路由器)
                   |____ wsgi.py
                   |____ views.py      (HTTP 請求處理器)

但是直接將網頁應用程式建在專案目錄下的結構不符合軟體工程的要求, 因為由 urls.py 與 views.py 組成的服務控制邏輯與專案緊密結合, 使得應用程式無法重複使用於其他專案. 完整的 Django 網站架構是由是由上層的專案 (Project) 與下層的多個應用程式 (App) 組成, 專案負責設定, App 負責特定之服務邏輯, 且與抽象化的 ORM 資料庫存取機制結合以建立動態網站. 

App 是 Django 專案中具有獨立功能的可插拔元件, 亦即可將網頁專案的服務邏輯按照功能切割為數個 App 以符合軟體工程中元件可重用 (reusable) 之要求, 每一個 App 可以在最小程度的調整後移植到其他專案中使用. 例如一個購物網站的功能可以由會員登入, 商品列表, 購物車以及結帳等 App 組成. 

建立一個 App 可在第一層專案目錄下輸入如下指令 : 

python manage.py startapp <AppName>

例如 :

(venv) D:\django\venv\mysite>python manage.py startapp helloworld
(venv) D:\django\venv\mysite>dir  
 D:\django\venv\mysite 的目錄

2020/11/18  下午 04:47    <DIR>          .
2020/11/18  下午 04:47    <DIR>          ..
2020/11/14  下午 10:55                 0 db.sqlite3
2020/11/16  下午 03:25    <DIR>          helloworld
2020/11/14  下午 08:52               684 manage.py
2020/11/14  下午 10:55    <DIR>          mysite
2020/11/18  下午 04:48    <DIR>          static
2020/11/18  下午 04:47    <DIR>          templates

注意, App 與 static 與 templates 是第一層專案目錄下平行的子目錄. 此外, 建立 App 會在第一層專案目錄下建立一個 db.sqlite3 檔案與一個 helloworld 目錄, 前者為 Django 預設使用的單一檔案關聯式資料庫; 後者則是 App 的目錄, 其底下檔按結構為 : 




可見建立 App 時會自動在該 App 目錄自動產生 views.py 程式檔, 而以前直接在專案下建立應用程式時則須自行在第二層專案目錄 mysite 底下手動建立 views.py, 使用 App 架構的話就不需要了. App 目錄底下的檔案與子目錄之用途如下 :


 App 檔案目錄 說明
 migrations 紀錄與資料庫版本與資料模型變更相關之檔案 (用來同步模型與資料庫)
 __init__.py 空檔案, 使 App 目錄形式上符合 Python 套件要求
 admin.py 用來註冊資料模型以便能於管理網頁中管理資料表
 apps.py 儲存與此 App 相關之設定
 models.py 儲存 App 資料模型定義以及資料間之關係
 test.py 儲存測試程式碼
 views.py 為 urls.py 處理 HTTP 要求與回應之程式檔


其中有需要動手去修改的只有 admin.py, models.py, 以及 views.py 這三個檔案. 加入 App 後完整的 Django 網站之檔案結構如下 : 

mysite                                      (上層專案目錄)
     |____ manage.py                (管理程式)
     |____ templates                  (模版目錄)
     |____ static                         (靜態檔案)
     |             |____ css               (存放 CSS 樣式檔)
     |             |____ js                 (存放 Javascript 檔)
     |             |____ images         (存放圖檔)
     |____ mysite                        (下層專案目錄)
     |             |____ __init__.py   (形成套件)
     |             |____ settings.py    (專案設定檔)
     |             |____ urls.py           (專案路由器)
     |             |____ wsgi.py         (伺服器佈署設定檔)
     |____ myapp                         (應用程式目錄)
                   |____ migrations     (資料庫同步)
                   |____ __init__.py    (形成套件)
                   |____ admin.py       (註冊資料模型)
                   |____ apps.py         (App 設定檔)
                   |____ models.py     (資料模型定義)
                   |____ test.py           (測試用程式)
                   |____ views.py        (HTTP 請求處理器)

注意, 此結構是所有 App 都使用專案路由器來將 URL 映射到請求處理器, 

建立 App 後須先如下設定與程式修改才能正常運行 :


1. 修改 settings.py 設定檔 :   

建立 App 後須先修改第二層專案目錄下的 settings.py (專案設定檔), 需要改的地方基本上有四個 :
  • INSTALLED_APPS : 註冊所建立之 App 名稱
  • LANGUAGE_CODE : 將預設英文 en-us 改為繁體中文的 zh-Hant
  • TIME_ZONE : 將預設 UTC 改為 Asia/Taipei
  • TEMPLATES : 指定網頁模板路徑
INSTALLED_APPS 是一個串列變數, 其中 django.contrib 開頭的是 Django 內建的應用程式, 自訂的 App 名稱就直接加到最後面即可 : 

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloworld',
]

在 INSTALLED_APPS 中註冊 helloworld 這個 App 後, Django 才會載入這個應用程式. 

其次是修改 LANGUAGE_CODE 與 TIME_ZONE 變數, 修改如下 : 

LANGUAGE_CODE = 'zh-Hant'
TIME_ZONE = 'Asia/Taipei'

修改模版變數 TEMPLATES, 把原先空白的 'DIR' 屬性設為 templates 子目錄之絕對路徑 :

'DIRS': [BASE_DIR / 'templates')],

最後在 STATIC_URL 變數底下添加一行指定靜態目錄之絕對路徑 : 

STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static'),]   

注意, 在 Django 2.x 的 settings.py 中使用 os.path.dirname() 來取得網站根目錄絕對位址, 然後用 os.path.join() 組合出檔案之絕對路徑, 但在 Django 3 已經不需要這麼做了, 直接用 BASE_DIR 串接即可, 參考 :


最後將 settings.py 存檔即完成設定.  


2. 修改路由器 urls.py : 

第二層專案目錄下的 urls.py 負責處理 HTTP 請求的路由 (routing), 將來自客戶端的 URL 請求指配給 App 目錄下的 views.py 處理並做出回應. 建立專案後預設的 urls.py 內容如下 :

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

這個預設的 urls.py 已經幫我們匯入兩個內建 App : 後台管理模組 admin 以及路由指派模組 django.urls 的 path(), 後台管理程式的 URL 在本機為 127.0.0.1/admin, 對應到 path() 的第一參數 'admin/', 它被指派給 admin.site.urls 這個內建 App 處理. 

對於我們新建的應用程式 helloworld 來說, 只要在串列變數 urlpatterns 中為此 App 添加路由指派即可, 例如 :

from django.contrib import admin
from django.urls import path
from helloworld.views import helloworld

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', helloworld),  
]

首先從 helloworld 目錄下的 views.py 匯入其 helloworld() 函數, 然後將 URL 'helloworld/' 指派給 helloworld() 函數去處理 (即回應此請求), 如果在本機要請求此 App 服務需使用 127.0.0.1:8000/helloworld 這個網址. 


3. 修改請求處理程式 views.py : 

開啟 App 目錄底下的 views.py 檔, 其預設內容如下 : 

from django.shortcuts import render

# Create your views here.

可見它已預先從內建的捷徑模組 django.shortcuts 匯入渲染方法 render(), 但此 render() 方法是用來渲染模版的, 如果只是要回應 Hello World 字串, 其實只要用 django.http.HttpResponse() 方法即可, 因此可將 views.py 修改為 : 

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

def helloworld(request): 
    return HttpResponse('<b>Hello World! <i>您好</i></b>')

此處定義一個呼叫 HttpResponse() 方法並傳回其結果的函數 helloworld(), 此函數即專案目錄下的路由器程式 urls.py 中 URL='helloworld' 所對應之處理函數, 當收到這個 URL 請求時就由 helloworld 函數回應. 

這樣就完成 App 的設定了, 將 views.py 以 utf-8 編碼存檔 (因為用到中文) 存檔後用 python manage.py runserver 指令運行測試伺服器, 這時會顯示有 18 個 migration (資料模型變更) 沒有執行, 由於這個簡單的 App 沒有用到資料庫, 所以還不執行變更也沒關係 :

(venv) D:\django\venv\mysite>python manage.py runserver    
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November 18, 2020 - 23:52:23
Django version 3.1.3, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

在瀏覽器網址列輸入 127.0.0.1:8000/helloworld 即可看到網頁輸出結果 :



以上是在 App 架構下使用 django.http.HttpResponse() 方法輸出網頁的基本步驟, 測試專案壓縮檔存放於 GitHub :




不過當網頁內容複雜時用 HttpResponse() 輸出網頁會顯得很雜亂, 實務上 Django 網站使用捷徑方法 django.shortcuts.render() 搭配模版來輸出網頁, 例如可將上面的應用程式 helloworld 輸出的網頁改成如下可傳遞參數的模版網頁 hello.htm :

<!-- hello.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <p class="msg">Hello, {{ name }}, 現在的時間是 {{ now }}</p>
</body>
</html>

將此模版檔案以 utf-8 編碼存檔於第一層專案目錄下的 templates 子目錄下, 然後修改路由器程式 urls.py, 添加 hello 這個附帶參數 name 的 URL :

#urls.py
from django.contrib import admin
from django.urls import path
from helloworld.views import helloworld, hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', helloworld),
    path('hello/<name>/', hello),
]

然後修改請求處理程式 views.py, 添加 hello 函式, 利用 django.shortcuts.render() 方法將名字 name 與現在時間 now 兩個參數以字典物件傳給模版網頁 hello.htm, 模版引擎會將參數插入網頁中 :

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

def helloworld(request): 
    return HttpResponse('<b>Hello World! <i>您好</i></b>')
    
def hello(request, name):
    now=datetime.now()
    dict1={'name': name, 'now': now}
    return render(request, 'hello.htm', dict1)

然後在網址列輸入 127.0.0.1/hello/trump 結果如下 :



修改後的專案壓縮檔存放於 GitHub :


上面的測試雖然把 views.py 放在 App 目錄下, 但路由器卻統一使用專案路由器 (即第二層專案目錄下的 urls.py), 亦即專案中的每個 App 之路由指派全部都集合在專案路由器內, 如果一個專案包含了 App 很多, 則專案路由器就會顯得很雜亂, 而且削弱了 App 的獨立性. 

為了進一步讓 App 更具有可插拔性, 較好的做法是將各 App 的路由指派放在各自的 App 目錄下, 然後在專案路由器中用 django.urls.include() 將其含括進來, 這樣專案路由器內容就很清爽, 專案內有哪些 App 在此檔案中也一目了然, 當 App 要移植到別的專案中使用時也不需要到原始的專案路由器中複製貼上, 只要將 App 目錄複製過去, 然後於新專案的專案路由器中 include 進來即可, 架構如下 : 




可知此架構下會有兩層路由器檔案 urls.py, 即專案路由器與 App 路由器, 前者位於第二層專案目錄下, 後者則位於各 App 之目錄下, 但這個 App 路由器 urls.py 在建立 App 時並不會自動建立, 必須手動從專案路由器 urls.py 複製一份到 App 目錄下修改. 

其次, 對於 URL 的路徑也要加入 App 這一層, 路徑分配如下所示 :

Protocol://Host address/AppName/AppURL/param1/param2/param3...

協定 (Protocol) 為 http 或 https, 主機位址是 IP (例如 127.0.0.1) 或域名 (localhost, www.abc.com 等), 接下來是 App 名稱, App 位址 (在 App 的 urls.py 中映射到 views.py 中的函數), 最後跟著的是可能的參數. 

完整的 URL 會由左至右被一層一層剝掉 (striping), 當 HTTP 伺服器收到請求時, 首先協定與位址會先被伺服器剝除, 剩下 AppName/AppURL/param1/param2/param3... 被送到 Django 的專案路由器處理, 它會在取出 AppName 後將剩下的路徑送給該 App 的路由器, 取出的 AppURL (圖中的 url1) 會被映射到 views.py 中的函數 fun1 處理 (同時將剩下的參數傳入該函數) :




以修改上面的測試 2 為例, 第一步先根據上面的新架構修改 App 的請求處理器 views.y 如下 :

#views.py of App "helloworld"
from django.shortcuts import render
from django.http import HttpResponse
from datetime import datetime

def hello1(request): 
    return HttpResponse('<b>Hello World! <i>您好</i></b>')
    
def hello2(request, name):
    now=datetime.now()
    dict1={'name': name, 'now': now}
    return render(request, 'hello.htm', dict1)

事實上這與上面測試 2 內容一樣, 只是為了讓 URL 路徑更清楚而修改了函數名稱, 原來的 hellowworld() 改為 hello1(); 而 hello() 則改為 hello2() 而已. 

第二步是將第二層專案目錄 mysite 底下的專案路由器程式 urls.py 複製到 App 目錄 helloworld 下, 去除 admin (這屬於專案, App 不需要) 只保留 path 模組即可, 然後定義 App 名稱變數 app_name, 最後在 path() 函數中將 AppURL 映射到 views.py 中的處理函數就完成了, 修改後內容如下 :

#App router : urls.py
from django.urls import path
from helloworld import views

app_name="helloworld"

urlpatterns = [
    path('hello1/', views.hello1),
    path('hello2/<name>/', views.hello2),
]

注意, 此處匯入請求處理器的方式與上面測試 2 不同, 這裡是匯入整個 views 模組而非模組內的函數, 因此映射到函數時前面必須帶上模組名稱. 

最後第三步是修改專案路由器 urls.py, 用 include 把 App 路由器含括進來 :

#Project router: urls.py
from django.contrib import admin
from django.urls import path
from django.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', include('helloworld.urls')),
]

這樣就全部完成了, 完整的檔案結構如下 :

mysite                                      (上層專案目錄)
     |____ manage.py                (管理程式)
     |____ templates                  (模版目錄)
     |____ static                         (靜態檔案)
     |             |____ css               (存放 CSS 樣式檔)
     |             |____ js                 (存放 Javascript 檔)
     |             |____ images         (存放圖檔)
     |____ mysite                        (下層專案目錄)
     |             |____ __init__.py   (形成套件)
     |             |____ settings.py    (專案設定檔)
     |             |____ urls.py           (專案路由器)
     |             |____ wsgi.py         (伺服器佈署設定檔)
     |____ helloworld                  (應用程式目錄)
                   |____ migrations     (資料庫同步)
                   |____ __init__.py    (形成套件)
                   |____ admin.py       (註冊資料模型)
                   |____ apps.py         (App 設定檔)
                   |____ models.py     (資料模型定義)
                   |____ test.py           (測試用程式)
                   |____ urls.py           (應用程式路由器)
                   |____ views.py        (HTTP 請求處理與回應)


於瀏覽器輸入如下網址 127.0.0.1:8000/helloworld/hello1 結果如下 :



輸入網址 127.0.0.1:8000/helloworld/hello2/trump 結果如下 :



修改後的專案壓縮檔可從 GitHub 下載 :


綜合上述測試總結如下 :
  • Django 3 除了新增非同步功能外, 基本用法與第二版並無差異, 只有在專案設定檔 settings.py 的絕對路徑指派方式不同而已. 
  • 使用 App 在架構上只是將 views.py 從第二層專案目錄移到 App 目錄下, 以及增加資料同步動作而已. 
  • 路由器 urls.py 分離為專案路由器與 App 路由器有助於提升 App 可重用性.
如果是沒有用到資料庫的小型專案, 直接把應用程式寫在專案目錄下 (即不用 App, 控制邏輯 urls.py 與 views.py 都在第二層專案目錄下) 是最簡單的實作方式; 如果要用到資料庫, 則必須使用 App 架構, 功能不複雜只有少數 App 可以直接使用專案路由器; 但若 App 很多且考慮 App 可移植需求的話, 每一個 App 的路由器最好獨立出來, 與專案路由器分開. 

由於篇幅太長, 資料庫存取部分留到下一篇測試.