中興大學 Python Django網頁App開發基礎班第01期 II
2023/07/21-2023/08/02
07/21 Jinja語法初探
模板
templates
操作 以 python 的框架建構前端,大多使用 Jinja 模板語法,當前後分離建構各自框架語法,則不受限於此。Jinja
與vue
變數語法中的雙大括弧
衝突。 只有一方要退讓,就是vue
前端設定delimiters
改成雙中括弧 [[]]
。Vue官方jsapp.config.compilerOptions.delimiters=['[[', ']]']
或
js... const app={ setup(){ ... }, delimiters:['[[', ']]'] }
或
js... template:{ compilerOptions:{ delimiters:["[[", "]]"], } }
提醒
通常 templates 資料夾需建立在每個 app 應用資料夾內,且資料夾命名不能更改,如果有特定資料夾名稱或建立在其他地方,需至 settings.py 內進行修改。 官方
Jinja
基本語法:html<!-- 接受變數 --> <div>{{ 變數名 }}</div> <!-- 繼承區塊 --> {% block blockname %}{% endblock %} <!-- 繼承模板 --> {% extends "繼承.html" %} <!-- 判斷區塊 --> {% if a<=12 %}{% endif %} <!-- 迴圈區塊 --> {% for arr in arrdate %}{% endfor %}
- Bootstrap基本介紹與參考。
07/26 Jinja與DataSet初探
django 傳輸到 templates 的方法:
pydef f(request:HttpRequest): return render(request, '模板.html', {"傳遞資料":"傳遞值"})
模板存放位置(templates) 預設需存放在app應用資料夾內,否則須到內
settings.py
註冊位置。Jinja
標籤制定:<% extend '擴充模板.html' %>
<% include %> 插入模板
<% block name %> 區塊 <% endblock %>
<% if %> 判斷式 <% elif %><% endif %>
<% for %> 迴圈 <% endif %>
<% wih %> 設定變數 <% endwith %>
template
類型:{% 指令 %}
前端陣列變數宣告:
pyarr=['name':'evan','age':'18'] {% for i in arr %} <p> {{ i.0 }} </p> {% endfor %}
Class資料集
DataSet
觀念(搭配MySQL):class
是一個很重要的物件導向類別。一開始會先宣告空字串(columns、results)給後續資料放進去。pyclass DataSet: # 先預設連結、除錯、欄位、結果都為空值或false dbconn = None debug = False columns = None results = None # 定義父層為資料庫連結、開啟除錯 def __init__(self, debug: bool = False) -> None: self.dbconn = connection self.debug = debug
- Query 選擇 : 在資料庫
sql
連結時,一律先判斷所傳輸來的方法是否正確。通常不會知道收到的參數有多少內容,所以會使用*args
作為變數。
py# 定義提取資料(自身, 資料庫:字串, *資料參數) def Query(self, sql: str, *args) -> None: # 判斷送來的方法開頭(startswith)傳換成大寫(upper)後是否為 SELECT if not sql.strip().upper().startswith("SELECT"): raise Exception("錯誤,非SELECT命令") # 開啟除錯模式 if self.debug: print("除錯中:", sql, args) # 連結資料庫 cursor = self.dbconn.cursor() # 打包資料庫(資料名,參數) cursor.execute(sql, args) # 打包欄位,從第0筆開始,依據上段打包(cursor)的描述(description) self.columns = [col[0] for col in cursor.description] # 打包資料成為dict self.results = [dict(zip(self.columns, row)) for row in cursor.fetchall()]
- Update 更新
py# 更新內值 def Update(self, sql: str, *args) -> int: # 判斷送來的是否為 SELECT 的開頭 if sql.strip().upper().startswith("SELECT"): raise Exception("Insert, Update and Delete support only!") # --- if self.debug: print("Update :", sql, args) # --- cursor = self.dbconn.cursor() cursor.execute(sql, args) # --- return cursor.rowcount
- Row Count 總數統計
py# 資料數統計 def get_Row_Count(self) -> int: # 判斷送來的欄位、結果是否為空值 if self.columns == None or self.results == None: return None else: # 回饋資料總數 return len(self.results)
- Get Value 取得內值
py# 取得內值(查找) def get_Value(self, row: int, colna: str) -> any: # 判斷送來的欄位、結果是否為空值 if self.columns == None or self.results == None: return None # 判斷送來的參數是否不在資料數統計內 if row < 0 or row >= self.get_Row_Count(): raise Exception("超出索引值!") # 判斷送來的參數是否不在資料數統計內 if colna not in self.columns: raise Exception("資料不存在!") # 回饋查詢結果 return self.results[row][colna]
- ColumnSet 欄位打包
- ResultSet 結果打包
- Begin 儲存過程(進階)
- Commit 儲存/交提(mysql進階)
- Rollback 錯誤時回傳前次資料狀態(進階)
- Query 選擇 : 在資料庫
前端輸入的方法 (GET、POST)
client
端 : 傳遞資料的方法- 直接輸入網址。
<form>
表單標籤,需搭配method
方法,負責傳送資料。
參考 :W3S Form
<input>
輸入標籤,需搭配{% csrf_token %}
加密。<textarea>
輸入多行文字標籤。<select>
選擇/下拉選單標籤。
request
端 : 請求的方法- GET : 明碼傳送,透過網址顯示,不適和用在密碼傳送。
- POST : 非明碼傳送,打包方式傳送。
07/28 <input>
標籤與 Javascript 應用
HTML
與JS
應用:- Django限制 : 只要有
<input>
輸入標籤,一律內部加入{% csrf_token %}
來防止身分偽造,否則會產生錯誤訊息。
html<form> {% csrf_token %} <input type='password'> </form>
<input type='password'>
一律不設置vaule
屬性,避免產生資安問題。<input type='checkbox'>
解決未勾選無傳送訊息時,與批次點選傳送訊息的接收方法。
html<div> <!-- 追加隱藏的 checkbox --> <input type='hidden' value='N'> <input type='checkbox' onclick="set_checkbox(this)"> <label> Option1 </label> </div>
js//偵測點選 // 善用 node 節點方式 function set_checkbox(node, chVal='Y', unVal='N'){ // 上一個(previousElementSibling)或下一個(nextElementSibling)物件選擇器方式 var vNode=node.previousElementSibling; // 利用 ? 符號作為判斷,可省去 if 宣告 vNode.value = vNode.value == chVal ? unVal : chVal; node.checked = vNode.value == chVal; }
<input type='radio'>
解決未點選無傳送訊息,與批次點選傳送訊息的接收方法;解決方式與checkbox
類似。
html<div> <!-- 使用 div 包覆後,增加1個隱藏的 radio,並強迫起始value為option--> <input type='hidden' name='radio1' value='option1'> <div> <input type='radio' name='radio1' value='option1' checked onclick='set_radio'> <label for='radio1'>Option1</label> </div> <div> <input type='radio' name='radio1' value='option2' onclick='set_radio'> <label for='radio1'>Option2</label> </div> <div> <input type='radio' name='radio1' value='option3' onclick='set_radio'> <label for='radio1'>Option3</label> </div> </div>
js//善用節點node function set_radio(node){ // 採用2個parentNode父層方式找到最外圍的物件 var topNode=node.parentNode.parentNode; // 建立最上層的物件 var valNode=topNode.firstElementChild; valNode.value=node.value; // 使用迴圈,採最上層的總數為基準 for(var x=1; x<topNode.children.length; x++){ radioNode= topNode.children[x].firstElementChild; radioNode.checked=radioNode==node; } }
- JS 參考 : W3S_HTML DOM Element parentNode
- Django限制 : 只要有
從
client
端傳送到server
端,在表單中所建立的action
屬性,需與server
端的views.py
、urls.py
對應正確。html<!-- from.html --> <form action='/myaction/' method='POST'></form>
py# views.py def myaction(request:HttpRequest): if request.method=='GET': return render (request, 'show_data.html',{'passdata': request.GET}) else: return render (request, 'show_data.html',{'passdata': request.POST})
py# app/urls.py urlpatterns=[ ... path('myaction/', views.myaction, name="action_form") ]
提醒:
表單
與實際後端所丟出來的畫面是不同html
。<form>
標籤,送出後要開啟新視窗須加上target='_blank'
屬性,未加此屬性,則會在原畫面轉跳。
08/02 實作與前置規畫
Insert 實作
實作前資料表(database)欄位與格式規劃:
創建schema.sql來規劃個欄位與格式:
- 資料表名稱(stud)
- 欄位 ( PRIMARY KEY、AUTO_INCREMENT )
欄位 type default description rowid BIGINT AUTO_INCREMENT PRIMARY KEY studid CHAR(10) NOT NULL 學號 studna VARCHAR(50) NOT NULL 姓名 gedner CHAR(1) DEFAULT 性別 bday DATE NOT NULL 出生日期 langchi CHAR(04) DEFAULT 'NNNN' 中文 langeng CHAR(04) DEFAULT 'NNNN' 英文 langjpn CHAR(04) DEFAULT 'NNNN' 日文 studkd CHAR(05) NOT NULL 學生類別
模板/表單(form)欄位: 採 W3S Bootstrap 的
Input Groups
當表單欄位。- Insert 新增 :
studInsert.html
- Update 修改 :
studUpdate.html
- Delete 刪除 :
studDelete.html
- Insert 新增 :
建議
基本上模板僅需要製作1組,後續再取用複製修改即可,縮短建構前端頁面的時間。
- 路由(urls): 以 CRUD 方式作為路由。
- Insert 新增 :
/stud/ins/
- Update 修改 :
/stud/upd/
- Delete 刪除 :
/stud/del/
- Insert 新增 :
pypath('/stud/ins/', views.studInsert), path('/stud/upd/', views.studUpdate), path('/stud/del/', views.studDelete)
- 事件處理項目(views): 同樣以 新增、修改、刪除作為宣告。
Insert 新增
採取上次課堂建立的ulit.py
模組DataSet
來搭配表單欄位。
pydef studInsert(request:HttpRequest): # 先做判斷前端送出來的方法: GET/POST if request.method=='GET': return render(request, 'studInsert.html') else: # 合併綜合欄位(如:中、英、日文的聽說讀寫) wk_langchi = "".join(request.POST.getlist("langchi")) wk_langeng = "".join(request.POST.getlist("langeng")) wk_langjpn = "".join(request.POST.getlist("langjpn"))
啟用 DataSet()
py# 使用 ulit.py 內的 DataSet() applySet=DataSet()
使用
sql
語法,外層再用 if 包圍做欄位的判斷。py# 判斷欄位如果等於0,代表未寫入 if applySet.Update( # 欄位對應sql "INSERT INTO studinfos (studid,studna,gedner,bday,langchi,langeng,langjpn,studkd) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", request.POST["studid"], request.POST["studna"], request.POST["gedner"], request.POST["bday"], wk_langchi, wk_langeng, wk_langjpn, request.POST["studkd"], )== 0 : return HttpResponse("<h1>新增資料有誤!</h1>") else: return HttpResponse("<h1>新增資料完成!</h1>")
提醒
此方法上有1錯誤待解決,也就是缺乏判斷資料表內是否有重複,所以當重整畫面後,如果
studid
沒有更新,會產生錯誤,因為在資料庫上已經有先預設索引值UNIQUE INDEX
。Update 修改
,修改資料步驟規劃 :- 取出
對應
的舊資料。 - 將資料放到表單呈現。
<input>
中的date
屬性呈現需js
協助。- 使用者修改後再回存後端。
- 取出
總結 :
- 表單建構需要注意每個欄位的
name
屬性命名,否則會導致後端接收錯誤。- 留意
checkbox
、radio
兩個屬性,當使用者如果未勾選,會造成資料遺漏,需要增加一個標籤有隱藏hidden
屬性與js
協助判斷回傳資訊。
- 留意
views
處理的事件中,要多利用DataSet
來串接,避免多寫程式。- 上述方法僅初階,尚未達到量產效益,需再改善。
- 表單建構需要注意每個欄位的
參考資訊: