2023年11月3日 星期五

Python 學習筆記 : Django 4 用法整理 (五) 資料表之 CRUD 操作 (1)

由於篇幅太長, 資料表之 CRUD 操作測試記錄放在此篇, 本系列之前的文章參考 :



三. 資料表的 CRUD 操作 : 

所謂 CRUD 是指對資料表內之紀錄的新增 (Create), 讀取 (Retrieve), 更新 (Update), 與刪除 (Delete) 等四個主要存取動作, 在其他的後端框架 (例如 Flask 或 PHP) 必須學習 SQL 語法才能存取關聯式資料庫, 但在 Django 由於 ORM 的中介抽象化機制, 只要呼叫 Python 物件的方法即可進行 CRUD 操作, 毋須學習 SQL 語法. 

透過 ORM 進行資料表的 CRUD 操作主要是透過模型類別的 objects 屬性取得物件, 然後呼叫其 get(), all(), create() 方法進行查詢與新增操作, 如下表所示 :  


 模型物件的方法 說明
 模型.objects.get(var=val) 傳回模型資料表中符合條件 (例如 '欄名'=值) 之紀錄 (模型物件)
 模型.objects.all() 傳回模型資料表的全部紀錄, 類型為類似串列的 QuerySet
 模型.objects.create(紀錄) 建立記錄 (field1=value1, field2=value2 ...), 呼叫 save() 方法寫入


get() 方法會傳回一個紀錄物件 (db object), 可以利用點運算子存取其欄位值, 例如 :

record=Members.objects.get(name='劉一刀')
record.email='one_knife@gmail.com'        # 更改紀錄之指定欄位內容

刪除與更新操作則是要先呼叫 get() 取得紀錄之模型物件 (db object) 後, 再呼叫其 delete() 與 save() 方法分別進行記錄之刪除與更新 : 

 紀錄物件的方法 說明
 紀錄.delete(id) 刪除指定 id 之紀錄
 紀錄.save() 儲存紀錄內容至資料表


all() 方法會傳回資料表全部紀錄, 其型別為類似串列之可迭代紀錄集物件 QuerrySet, 其元素即為資料表中的每一筆元素, 可傳入模板網頁後用模板的 for 迴圈迭代讀取每一筆紀錄. 此紀錄集物件有一個 order() 方法可以傳入欄位名稱控制紀錄的排序, 預設為升冪, 前面冠 '-' 為降冪, 例如 :

records=Members.objects.all().order('id')      # 紀錄集按 id 欄位升冪排序  
records=Members.objects.all().order('-id')     # 紀錄集按 id 欄位降冪排序


1. 查詢 (Retrieve) :

查詢操作是從資料表讀取符合指定欄位條件之一筆或多筆紀錄, 如果是呼叫 get() 其傳回值為一筆紀錄; 若呼叫 all() 方法則傳回值為多筆紀錄, 可用模板引擎之迴圈指令迭代顯示結果. 

在上面的範例基礎上 (下載前一篇的最後範例), 我們已經用內建之後台管理程式 admin 在 Members 資料表中新增了四筆紀錄 :




此測試目標是要從資料表中查詢指定 (姓名, 編號) 之紀錄顯示於模板網頁上. 

首先在 myapp1 下建立一個模板資料夾 templates, 並準備一個模板網頁 list_record.htm, 視圖模組 views.py 會把查詢資料表所得之紀錄物件 record 傳進來讓模板顯示 (record 物件之屬性就是各欄位之值) :

<!-- get_record.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>讀取資料表</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
</head>
<body>
  <h2>顯示 Members 資料表的一筆紀錄</h2>
  <h3>{{type(record)}}</h3>
  編號 : {{record.id}}<br>
  姓名 : {{record.name}}<br>
  性別 : {{record.gender}}<br>
  生日 : {{record.birthday}}<br>
  信箱 : {{record.email}}<br>
  電話 : {{record.phone}}<br>
  地址 : {{record.address}}<br>
  <h3>{{message}}</h3>
</body>
</html>

接下來撰寫視圖模組 views.py 讀取資料表並將結果傳送給至上面的模板網頁 get_record.htm 輸出結果, 此處提供兩種查詢方法, 一是輸入姓名 (name) 查詢, 另一則是輸入編號 (id) 查詢 :

# views.py of App=myapp1
from django.shortcuts import render
from myapp1.models import Members

def get_record_by_name(request, name):
    try:
        record=Members.objects.get(name=name)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def get_record_by_id(request, id):
    try:
        record=Members.objects.get(id=id)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

這兩個路由處理函式會分別依據 urls.py 傳入之參數查詢符合指定姓名或編號之紀錄, 然後傳給模板網頁輸出結果. 注意, 查詢資料庫跟開啟檔案一樣都是可能出現例外之操作, 因此必須用 try except 捕捉. 

接著編輯 App 的路由模組 urls.py, 注意, 此檔案不會在建立 App 時自動建立, 但可以從下層專案目錄下複製專案的路由模組 urls.py 來修改為 App 的路由模組 : 

# urls.py of App=myapp1
from django.contrib import admin
from . import views

urlpatterns = [
    path('get_record_by_name/<str:name>/', views.get_record_by_name),
    path('get_record_by_id/<int:id>/', views.get_record_by_id),
]

此 App 路由模組目前只處理兩個 URL, 一個是帶 name 變數的, 另一個是帶 id 變數的, 分別委派給視圖模組的 get_record_by_name() 與 get_record_by_id() 處理, 其 URL 例如 : 

127.0.0.1:5000/myapp1/get_record_by_name/劉一刀
127.0.0.1:5000/myapp1/get_record_by_id/3

最後編輯專案的路由模組 urls.py, 將 myapp1 的 urls.py 含括到專案的 urls.py 裡面 : 

# urls.py of project=mysite
from django.contrib import admin
from django.urls import path, include    

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

這樣就會將 myapp1 底下的路由模組 urls.py 匯入到專案的 urls.py 中了, 這種作法係出於 App 模組化考量, 參考 : 


這樣就可以用 python manage.py runserver 啟動測試伺服器來瀏覽查詢結果了 :

127.0.0.1:5000/myapp1/get_record_by_name/劉一刀



127.0.0.1:5000/myapp1/get_record_by_id/3




127.0.0.1:5000/myapp1/get_record_by_name/劉二刀



由於 name='劉二刀' 並不存在於 Members 資料表中因此讀取失敗. 

接下來要用 objects.all() 來取得資料表全部記錄, 然後用表格來顯示資料表內容, 編輯 App 的 views.py 添加一個 list_all_records() 函式用來從資料庫取得 Members 資料表內容 :

# views.py of App=myapp1
from django.shortcuts import render
from myapp1.models import Members

def get_record_by_name(request, name):
    try:
        record=Members.objects.get(name=name)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def get_record_by_id(request, id):
    try:
        record=Members.objects.get(id=id)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def list_all_records(request):
    record=Members.objects.all_order_by('id')     # 讀取 Members 資料表全部紀錄
    return render(request, 'list_all_records.htm', locals())

函式 list_all_records() 會將取得之全部紀錄 (QuerySet 物件) 傳給模板網頁 list_all_records.htm 輸出, 所以我們要在 myapp1 的 templates 目錄下新增 list_all_records.htm :

<!-- list_all_records.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>讀取資料表</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <style>
    table {background-color:blue;}   
    td, th {background-color:white;}    
  </style>
</head>
<body>
  <h2>顯示 Members 資料表的全部紀錄</h2>
  <table border="0" cellspacing="1" cellpadding="5">
    <caption>Members</caption>
    <tr>
      <th>編號</th>
      <th>姓名</th>
      <th>性別</th>
      <th>生日</th>
      <th>信箱</th>
      <th>電話</th>
      <th>地址</th>
    </tr>
    {% for record in records %}
    <tr>
      <td>{{ record.id }}</td>
      <td>{{ record.name }}</td>
      <td>{{ record.gender }}</td>
      <td>{{ record.birthday }}</td>
      <td>{{ record.email }}</td>
      <td>{{ record.phone }}</td>
      <td>{{ record.address }}</td>
    </tr>
    {% empty %}
    <tr>
      <td colspan="7">資料表內無紀錄</td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

此處用模板語言的 for 迴圈迭代 views.py 傳來的 records 變數, 每一次迭代都從 QuerySet 物件中取出一筆紀錄, 透過點運算子取得紀錄的欄位值顯示在表格的儲存格內. 當資料表無資料時會執行 empty 下至 endfor 的內容. 注意, head 中添加了 table, th, td 的樣式以美化表格效果. 

最後修改 App 的 urls.py 添加對 myapp1/list_all_records 這個 URL 的委派 : 

# urls.py of App=myapp1
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('get_record_by_name/<str:name>/', views.get_record_by_name),
    path('get_record_by_id/<int:id>/', views.get_record_by_id),
    path('list_all_records/', views.list_all_records),
]

這樣就完成設定了, 瀏覽 127.0.0.1/myapp1/list_all_records 結果如下 : 




可見紀錄是按 id 升冪排序, 如果將 views.py 中 list_all_records() 函式的 order_by('id') 改成 order_by('id'), 則會按照 id 降冪排列 :




此測試之網站資料放在 GitHub :



2. 新增 (Create) :

新增紀錄到資料表中需要先呼叫資料表模型類別的 objects.create() 方法建立一個 db object 紀錄物件, 傳入參數為各欄位之 key=value 對, 然後呼叫傳回的紀錄物件之 save() 方法將記錄儲存至資料表中, 語法如下 :

record=模型.objects.create(field1=value1, field2=value2, ....)    # 建立記錄物件
record.save()     # 將紀錄儲存至資料表

由於資料表有許多欄位, 要將這些欄位以 Django 的 RESTful 網址傳遞太長, 且有些機敏資料如帳號密碼不應在 URL 中以明碼傳送, 必須在表單中使用 HTTP 的 POST 方法, 也就是在 HTML 的 form 表單中設定 method='post', 例如 : 

<form action='網址' method='post'>
  {% csrf_token %}
  姓名 : <input type='text' name='name'>
  性別 : <input type='text' name='gender'>
  .....
  <input type='submit' value='送出'>
</form>

當按下送出鈕後, 表單中的各欄位資料 (欄位以 name 屬性值命名) 會放在 HTTP 的 body 中傳送到 action 所指的網址處理, 也就是在視圖模組 views.py 中用 HttpRequest 物件的 POST["欄名"] 取得 body 中傳遞的變數, 再填入 objects.create() 的相對欄位建立紀錄物件, 然後呼叫其 save() 方法存入資料表. 

注意, 使用 POST, PUT, PATCH, 與 DELETE 等 HTTP 方法傳送表單資料必須在 form 標籤內嵌入 Django 的模板變數 {% csrf_token %} 啟動 CSRF (Cross Site Request Forgery, 跨網站請求偽造) 保護, 這會在伺服器上為登入者之 Session 產生一個隨機的隱藏 token 字串, 表單提交時伺服器會檢查此 token 是否為自己發出, 用來驗證是否為已登入之使用者在向應用程式發出請求 (假網站無法取得此 token), 沒有嵌入此 CSRF 變數的表單在提交時會出現 "CSRF 驗證錯誤" 訊息, 關於網站的 CSRF 安全防護參考 :


首先在應用程式 myapp1 的 templates 目錄下新增一個模板網頁 add_record.htm :

<!-- add_record.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>新增紀錄表單</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <style>
  table {background-color:blue;} 
  td, th {background-color:white;} 
  </style>
</head>
<body>
  <h2>新增一筆紀錄到 Members 資料表</h2>
  <form method="post" action="." name="add_record_form">
    {% csrf_token %} 
    <table border="0" cellspacing="1" cellpadding="5">
      <caption>Members</caption>
      <tr>
        <th>姓名</td>
        <td><input type='text' name='name' id='name'></td>
      </tr>
      <tr>
        <th>性別</td>
        <td>
          <input type='radio' name='gender' id='male' value='男'>男
          <input type='radio' name='gender' id='female' value='女'>女
        </td>
      </tr>
      <tr>
        <th>生日</td>
        <td><input type='text' name='birthday' id='birthday'></td>
      </tr>
      <tr>
        <th>信箱</td>
        <td><input type='text' name='email' id='email'></td>
      </tr>
      <tr>
        <th>電話</td>
        <td><input type='text' name='phone' id='phone'></td>
      </tr>
      <tr>
        <th>地址</td>
        <td><input type='text' name='address' id='address'></td>
      </tr>
      <tr>
        <td colspan="2">
          <input type="submit" value="送出">
          <input type="reset" value="重設">
        </td>
      </tr>
    </table>
    <span style='color:red;'>{{ message }}</span>
  </form>
</body>
</html>

注意這表單 form 裡面嵌入 csrf_token 變數, 底下有兩個按鈕, 按 "重設" 會把每個欄位資料清除, 按 "送出" 則會以 POST 方法向伺服器提交此表單, 將所填寫的欄位資料以 name 屬性為變數名稱傳遞給後端視圖模組處理, action="." 表示向此網頁的 URL=add_record.htm 提交. 

其次修改上面範例中的模板網頁 list_all_records.htm, 在表格的 caption 內添加一個超連結, 用來連結到輸入表單模板 add_record.htm 以便新增紀錄 : 

<!-- list_all_records.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>讀取資料表</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <style>
  table {background-color:blue;} 
  td, th {background-color:white;} 
  </style>
</head>
<body>
  <h2>顯示 Members 資料表的全部紀錄</h2>
  <table border="0" cellspacing="1" cellpadding="5">
    <caption>Members <a href="../add_record/">+ 新增紀錄</a></caption>
    <tr>
      <th>編號</th>
      <th>姓名</th>
      <th>性別</th>
      <th>生日</th>
      <th>信箱</th>
      <th>電話</th>
      <th>地址</th>
    </tr>
    {% for record in records %}
    <tr>
      <td>{{ record.id }}</td>
      <td>{{ record.name }}</td>
      <td>{{ record.gender }}</td>
      <td>{{ record.birthday }}</td>
      <td>{{ record.email }}</td>
      <td>{{ record.phone }}</td>
      <td>{{ record.address }}</td>
    </tr>
    {% empty %}
    <tr>
      <td colspan="7">資料表內無紀錄</td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

黃色高亮部分為所添加的超連結, 注意 href 屬性值前面需冠上 "../" 往上跳一層後再前往 "add_record/", 因為 Django 使用階層式路徑, 當網址列輸入 127.0.0.1:5000/myasp1/list_all_records/ 顯示全部紀錄時, 此時瀏覽位置是在 myasp1/list_all_records/, 如果此網頁中新增紀錄超連結的 href="add_record/", 則點擊後會前往 myasp1/list_all_records/add_record/ (錯誤) 而非 myasp1/add_record/ (正確). 這是相對於目前位置的寫法, 也可以用相對於網站根目錄的寫法  href="/myapp1/add_record/". 

接著修改視圖模組 views.py, 讀取 POST 方法傳遞的變數來存入資料表, 注意, 此處另外匯入了 django.shortcuts.redirect() 在完成資料表新增作業後將網頁重導到顯示全部紀錄之網頁 :

# views.py of App=myapp1
from django.shortcuts import render, redirect 
from myapp1.models import Members

def get_record_by_name(request, name):
    try:
        record=Members.objects.get(name=name)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def get_record_by_id(request, id):
    try:
        record=Members.objects.get(id=id)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def list_all_records(request):
    records=Members.objects.all().order_by('-id')
    return render(request, 'list_all_records.htm', locals())

def add_record(request):
    if request.method=='POST':     # 來自表單提交
        name=request.POST['name']
        gender=request.POST['gender']
        birthday=request.POST['birthday']
        email=request.POST['email']
        phone=request.POST['phone']
        address=request.POST['address']
        record=Members.objects.create(name=name,
                                     gender=gender,
                                     birthday=birthday,
                                     email=email,
                                     phone=phone,
                                     address=address)    # 建立紀錄物件
        record.save()   # 將紀錄存入資料表
        rreturn redirect('/myapp1/list_all_records/')       # 重導網址
    else:   # 來自 list_all_records.htm 的新增紀錄超連結
        message='請輸入資料'
        return render(request, 'add_record.htm', locals())
        
此處利用 request.method 屬性值來判斷請求是否來自 add_record.htm 模板網頁中的表單所提交, 是則其值為 "POST", 就從 request.POST 字典中取得所傳遞的表單各欄位變數, 將其傳入 Models.objects.create() 建立紀錄物件後, 呼叫其 save() 方法存入資料表中, 然後用 redirect('/myapp1/list_all_records/') 函式將網頁重導至 list_all_records.htm 顯示全部記錄, 注意, redirect() 的參數網址必須參考根目錄. 

最後修改 urls.py, 添加新增紀錄表單模板網頁的 URL 路徑委派 :

# urls.py of App=myapp1
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('get_record_by_name/<str:name>/', views.get_record_by_name),
    path('get_record_by_id/<int:id>/', views.get_record_by_id),
    path('list_all_records/', views.list_all_records),
    path('add_record/', views.add_record),    
]

瀏覽 27.0.0.1:5000/myasp1/list_all_records/ 結果如下 :




點 "+ 新增紀錄" 超連結前往 /myapp1/add_record/ 顯示輸入新增紀錄網頁 : 




按送出儲存至資料表後重導至 list_all_records.htm :




可見新增紀錄已寫入資料表中. 此測試之網站資料放在 GitHub : 



3. 更新 (Update) 與刪除 (Delete) :

更新操作即編輯資料表中的現有紀錄, 更改其中若干欄位之值後回存資料表. 首先改寫上面範例中顯示全部記錄的模板網頁 list_all_records.htm, 在表格最後面新增一個 "動作" 欄, 裡面放兩個超連結 : 編輯與刪除, 前者用來前往紀錄編輯網頁; 後者用來刪除該筆紀錄, 其 URL 如下 :
  • 編輯 :
    /mypp1/edit_record/id (顯示編輯網頁)
    /mypp1/edit_record/id/update (更新資料表)
  • 刪除 :
    /mypp1/delete_record/id
此處 URL 所帶變數 id 是要傳遞給視圖模組 views.py 用來找出要操作的紀錄. 注意, 此處路徑要參考首頁根目錄 (從 /myapp1 開始或用 ../ 往上跳一層), 因為目前網頁位置是在 /myapp1/list_all_records/, 如果用 edit_record/id 與 delete_record/id, 則超連結會變成 /myapp1/list_all_records/edit_record/id 與 /myapp1/list_all_records/delete_record/id, 這樣就錯了, 因為會在 urls.py 中找不到路徑. 

其次, 編輯動作分成兩步驟, 首先是顯示編輯網頁, 更改欄位資料按送出後更新資料表, 兩個步驟都由視圖模組中的一個函式 edit_record() 處理, 靠表單傳出的 mode 變數區別. 下面為修改過的 list_all_records.htm, 黃底高亮度部分為添加或修改的部分 (注意 colspan 也要從 6 改成 8) :

<!-- list_all_records.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>讀取資料表</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <style>
  table {background-color:blue;} 
  td, th {background-color:white;} 
  </style>
</head>
<body>
  <h2>顯示 Members 資料表的全部紀錄</h2>
  <table border="0" cellspacing="1" cellpadding="5">
    <caption>Members <a href="/myapp1/add_record/">+ 新增紀錄</a></caption>
    <tr>
      <th>編號</th>
      <th>姓名</th>
      <th>性別</th>
      <th>生日</th>
      <th>信箱</th>
      <th>電話</th>
      <th>地址</th>
      <th>動作</th>
    </tr>
    {% for record in records %}
    <tr>
      <td>{{ record.id }}</td>
      <td>{{ record.name }}</td>
      <td>{{ record.gender }}</td>
      <td>{{ record.birthday }}</td>
      <td>{{ record.email }}</td>
      <td>{{ record.phone }}</td>
      <td>{{ record.address }}</td>
      <td>
        <a href="../edit_record/{{ record.id }}">編輯</a>  
        <a href="/myapp1/delete_record/{{ record.id }}">刪除</a>  
      </td>
    </tr>
    {% empty %}
    <tr>
      <td colspan="8">資料表內無紀錄</td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

注意, 此處編輯與刪除超連結分別使用了往上跳一層與直接參考根目錄兩種做法, 效果是一樣的. 改完後重新整理 myapp1/list_all_records.httm 網頁就會看到多出一個動作欄, 裡面的編輯與刪除超連結網址是 ../edit_record/id 與 myapp1/delete_record/id :




但這兩個超連結現在按會出現錯誤, 因為 views.py 與 urls.py 還沒修改. 

首先來修改路由模組 urls.py, 添加三個 URL 路徑委派 : 

# urls.py of App=myapp1
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('get_record_by_name/<str:name>/', views.get_record_by_name),
    path('get_record_by_id/<int:id>/', views.get_record_by_id),
    path('list_all_records/', views.list_all_records),
    path('add_record/', views.add_record),
    path('edit_record/<int:id>/', views.edit_record),   
    path('edit_record/<int:id>/<str:mode>/', views.edit_record),   
    path('delete_record/<int:id>/', views.delete_record),   
]

其中編輯紀錄的操作需要兩個 URL, 第一個只攜帶 id 變數用來顯示編輯表單, 第二個則是攜帶 id 與 mode 變數用來將記錄儲存到資料表 (mode 可攜帶 'update' 字串用來判別); 刪除紀錄則只需要一個攜帶 id 變數的 URL 即可.  

接著改寫視圖模組 views.py, 添加 edit_record() 與 delete_record() 這兩個 URL 處理函式 : 

# views.py of App=myapp1
from django.shortcuts import render, redirect
from myapp1.models import Members

def get_record_by_name(request, name):
    try:
        record=Members.objects.get(name=name)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def get_record_by_id(request, id):
    try:
        record=Members.objects.get(id=id)
        type_of_record=type(record)
    except:
        message='讀取失敗'
    return render(request, 'get_record.htm', locals())

def list_all_records(request):
    try: 
        records=Members.objects.all().order_by('-id')
    except:
        pass
    return render(request, 'list_all_records.htm', locals())

def add_record(request):
    if request.method=='POST':   # 來自表單提交
        name=request.POST['name']
        gender=request.POST['gender']
        birthday=request.POST['birthday']
        email=request.POST['email']
        phone=request.POST['phone']
        address=request.POST['address']
        record=Members.objects.create(name=name,
                                     gender=gender,
                                     birthday=birthday,
                                     email=email,
                                     phone=phone,
                                     address=address)
        record.save()
        return redirect('/myapp1/list_all_records/')
    else:   # 來自 list_all_records.htm 的新增紀錄超連結
        message='請輸入資料'
        return render(request, 'add_record.htm', locals())
        
def edit_record(request, id=None, mode=None):
    if mode=='update':  # 來自在 edit_record.htm 按送出
        record=Members.objects.get(id=id)  # id 一定有不須捕捉例外
        record.name=request.POST['name']
        record.gender=request.POST['gender']
        record.birthday=request.POST['birthday']
        record.email=request.POST['email']
        record.phone=request.POST['phone']
        record.address=request.POST['address']
        record.save()
        return redirect('/myapp1/list_all_records/')
    else: # 來自按 list_all_records.htm 中的編輯超連結:顯示編輯頁面
        try: # 防止可能來自網址列的 id 不存在
            record=Members.objects.get(id=id)
            # 轉換 birthday 的格式 (因資料表欄位為 date 型態)
            birthday=str(record.birthday)
            birthday=birthday.replace('年', '-')
            birthday=birthday.replace('月', '-')
            birthday=birthday.replace('日', '-')
            record.birthday=birthday
        except:
            message='id 不存在'
        return render(request, 'edit_record.htm', locals())
   
def delete_record(request, id=None):
    if id:  # 有傳入 id 才刪除
        try: # 防止可能來自網址列的 id 不存在
            record=Members.objects.get(id=id)  # 取得紀錄物件
            record.delete()  # 刪除紀錄
        except: # 不處理
            pass
    return redirect('/myapp1/list_all_records/')   
  
這兩個函式都會接收 id 變數, 預設值均為 None, 用來從資料表中讀取指定之紀錄, 如果是從 list_all_records.htm 按超連結而來的請求, id 應該都是存在的, 但為了防止直接從網址列輸入不存在的 id 而導致錯誤, 必須用 try except 予以捕捉. 

刪除操作很簡單, 判斷 id 不是 None 後即從資料表讀取紀錄物件, 然後呼叫其 delete() 方法即可. 編輯操作則分成兩階段, 故 edit_ecord() 函式有兩個用途, 一是從資料表讀取指定 id 之紀錄後顯示在記錄編輯頁面 edit_record.htm 上 (URL 沒有攜帶 mode='update' 變數); 其次是在記錄編輯頁面按 '送出' 鈕後, 根據 id 變數從資料表讀取紀錄物件, 並以所接收之 POST 變數更新紀錄物件內容後呼叫其 save() 方法回存至資料庫.  

記錄編輯頁面 edit_record.htm 是從 add_record.htm 複製過來修改的, 其內容如下 : 

<!-- edit_record.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>編輯紀錄</title>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <style>
  table {background-color:blue;} 
  td, th {background-color:white;} 
  </style>
</head>
<body>
  <h2>編輯會員資料</h2>
  <form method="post" action="/myapp1/edit_record/{{ record.id }}/update/" name="edit_record">
    {% csrf_token %}  
    <table border="0" cellspacing="1" cellpadding="5">
      <caption>Members</caption>
      <tr>
        <th>姓名</td>
        <td><input type='text' name='name' id='name' value={{ record.name }}></td>
      </tr>
      <tr>
        <th>性別</td>
        <td>
        {% if record.gender == '男' %}
          <input type='radio' name='gender' id='male' value='男' checked="checked">男
          <input type='radio' name='gender' id='female' value='女'>女
        {% else %}
          <input type='radio' name='gender' id='male' value='男'>男
          <input type='radio' name='gender' id='female' value='女'checked="checked">女
        {% endif %}
        </td>
      </tr>
      <tr>
        <th>生日</td>
        <td><input type='text' name='birthday' id='birthday' value={{ record.birthday }}></td>
      </tr>
      <tr>
        <th>信箱</td>
        <td><input type='text' name='email' id='email' value={{ record.email }}></td>
      </tr>
      <tr>
        <th>電話</td>
        <td><input type='text' name='phone' id='phone' value={{ record.phone }}></td>
      </tr>
      <tr>
        <th>地址</td>
        <td><input type='text' name='address' id='address' value={{ record.address }}></td>
      </tr>
      <tr>
        <td colspan="2">
          <input type="submit" value="送出">
          <input type="reset" value="重設">
        </td>
      </tr>
    </table>
    <span style='color:red;'>{{ message }}</span>
  </form>
</body>
</html>

此模板網頁最重要的部分是 form 表單的 action 屬性, 同樣是提交給 /myapp1/edit_record/, 但除了攜帶 id 變數外, 還攜帶 update 變數, 用來讓視圖模組 views.py 中的處理函式 edit_record() 判斷這是要更新資料表, 而不是輸出編輯紀錄頁面, 因為 edit_record() 是兩用的. 當然也可以將兩個用途分開, 例如顯示編輯頁面使用 /myapp1/edit_record/id, 而更新資料表使用 /myapp1/update_record/id, 這是另一種作法. 

這樣就可以按上面全部記錄列表網頁中的編輯與刪除超連結了, 按編輯會出現顛及頁面 : 




更改欄位資料後按送出會重導回 list_all_records.htm 網頁 :




 可見資料表 id=5 的內容已經被更新了, 按 id=5 這筆紀錄的刪除鈕則會立即刪除該筆紀錄後重導回 list_all_records.htm 網頁 :




可見 id=5 已經被移除了. 此範例壓縮檔儲存在 GitHub :


經過一周斷斷續續整理, 終於完工啦! 

沒有留言 :