Skip to content
On this page

中興大學 Python Django網頁App開發基礎班第01期 II

2023/07/21-2023/08/02

07/21 Jinja語法初探

  1. 模板templates操作 以 python 的框架建構前端,大多使用 Jinja 模板語法,當前後分離建構各自框架語法,則不受限於此。

  2. Jinjavue 變數語法中的雙大括弧 衝突。 只有一方要退讓,就是 vue 前端設定 delimiters 改成 雙中括弧 [[]]Vue官方

    js
    app.config.compilerOptions.delimiters=['[[', ']]']
    

    js
    ...
    const app={
       setup(){
        ...
       },
       delimiters:['[[', ']]']
    }   
    

    js
    ...
    template:{
       compilerOptions:{
          delimiters:["[[", "]]"],
       }
    }
    

提醒

通常 templates 資料夾需建立在每個 app 應用資料夾內,且資料夾命名不能更改,如果有特定資料夾名稱或建立在其他地方,需至 settings.py 內進行修改。 官方

  1. Jinja基本語法:
    html
    <!-- 接受變數 -->
    <div>{{ 變數名 }}</div>
    <!-- 繼承區塊 -->
    {% block blockname %}{% endblock %}
    <!-- 繼承模板 -->
    {% extends "繼承.html" %}
    <!-- 判斷區塊 -->
    {% if a<=12 %}{% endif %}
    <!-- 迴圈區塊 -->
    {% for arr in arrdate %}{% endfor %}
    
  2. Bootstrap基本介紹與參考。

07/26 Jinja與DataSet初探

  1. django 傳輸到 templates 的方法:

    py
    def f(request:HttpRequest):
       return render(request, '模板.html', {"傳遞資料":"傳遞值"})
    
  2. 模板存放位置(templates) 預設需存放在app應用資料夾內,否則須到內settings.py註冊位置。

  3. Jinja 標籤制定:

    • <% extend '擴充模板.html' %>
    • <% include %> 插入模板
    • <% block name %> 區塊 <% endblock %>
    • <% if %> 判斷式 <% elif %><% endif %>
    • <% for %> 迴圈 <% endif %>
    • <% wih %> 設定變數 <% endwith %>
  4. template 類型:

    • {% 指令 %}
  5. 前端陣列變數宣告:

    py
    arr=['name':'evan','age':'18']
    
    {% for i in arr %}
       <p>  {{ i.0 }}  </p>
    {% endfor %}
    
  6. Class資料集 DataSet觀念(搭配MySQL):

    class是一個很重要的物件導向類別。一開始會先宣告空字串(columns、results)給後續資料放進去。

    py
    class 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 錯誤時回傳前次資料狀態(進階)

    參考 : 掌握Python連結MySQL資料庫的重要操作

  7. 前端輸入的方法 (GET、POST)

    • client端 : 傳遞資料的方法

      • 直接輸入網址。
      • <form> 表單標籤,需搭配 method方法,負責傳送資料。

      參考 :W3S Form

      • <input> 輸入標籤,需搭配 {% csrf_token %}加密。
      • <textarea> 輸入多行文字標籤。
      • <select> 選擇/下拉選單標籤。
    • request端 : 請求的方法

      • GET : 明碼傳送,透過網址顯示,不適和用在密碼傳送。
      • POST : 非明碼傳送,打包方式傳送。

07/28 <input> 標籤與 Javascript 應用

  1. HTMLJS 應用:

    1. Django限制 : 只要有 <input> 輸入標籤,一律內部加入 {% csrf_token %} 來防止身分偽造,否則會產生錯誤訊息。
    html
    <form>
       {% csrf_token %}
       <input type='password'>
    </form>
    
    1. <input type='password'>一律不設置 vaule 屬性,避免產生資安問題。
    2. <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;
       }
    
    1. <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;
          }
       }
    
  2. client 端傳送到 server 端,在表單中所建立的 action屬性,需與 server 端的 views.pyurls.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")
    ]
    

    提醒:

    1. 表單與實際後端所丟出來的畫面是不同 html
    2. <form>標籤,送出後要開啟新視窗須加上target='_blank'屬性,未加此屬性,則會在原畫面轉跳。

08/02 實作與前置規畫

  1. Insert 實作 insert_view

    1. 實作前資料表(database)欄位與格式規劃:

      1. 創建schema.sql來規劃個欄位與格式:

        1. 資料表名稱(stud)
        2. 欄位 ( PRIMARY KEY、AUTO_INCREMENT )
        欄位typedefaultdescription
        rowidBIGINTAUTO_INCREMENTPRIMARY KEY
        studidCHAR(10)NOT NULL學號
        studnaVARCHAR(50)NOT NULL姓名
        gednerCHAR(1)DEFAULT性別
        bdayDATENOT NULL出生日期
        langchiCHAR(04)DEFAULT 'NNNN'中文
        langengCHAR(04)DEFAULT 'NNNN'英文
        langjpnCHAR(04)DEFAULT 'NNNN'日文
        studkdCHAR(05)NOT NULL學生類別
    2. 模板/表單(form)欄位: 採 W3S BootstrapInput Groups 當表單欄位。

      1. Insert 新增 : studInsert.html
      2. Update 修改 : studUpdate.html
      3. Delete 刪除 : studDelete.html

    建議

    基本上模板僅需要製作1組,後續再取用複製修改即可,縮短建構前端頁面的時間。

    1. 路由(urls): 以 CRUD 方式作為路由。
      1. Insert 新增 : /stud/ins/
      2. Update 修改 : /stud/upd/
      3. Delete 刪除 : /stud/del/
    py
    path('/stud/ins/', views.studInsert),
    path('/stud/upd/', views.studUpdate),
    path('/stud/del/', views.studDelete)
    
    1. 事件處理項目(views): 同樣以 新增、修改、刪除作為宣告。
      1. Insert 新增採取上次課堂建立的ulit.py模組 DataSet來搭配表單欄位。
    py
    def 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

    1. Update 修改,修改資料步驟規劃 :
      1. 取出對應的舊資料。
      2. 將資料放到表單呈現。
      3. <input>中的date屬性呈現需js協助。
      4. 使用者修改後再回存後端。
  2. 總結 :

    1. 表單建構需要注意每個欄位的name屬性命名,否則會導致後端接收錯誤。
      1. 留意checkboxradio兩個屬性,當使用者如果未勾選,會造成資料遺漏,需要增加一個標籤有隱藏hidden屬性與js協助判斷回傳資訊。
    2. views處理的事件中,要多利用 DataSet來串接,避免多寫程式。
    3. 上述方法僅初階,尚未達到量產效益,需再改善。
  3. 參考資訊: