在前兩篇筆記中分別使用一般 HTML 與 Bootstrap 網頁框架作為前端, 測試了網頁表單與後端資料表的 CRUD 操作, 但都必須於模板網頁中自行手刻 HTML 表單, 在開發與維護上要麻煩. Django 提供了能自動生成表單的表單模型化做法, 可解決表單生成問題.
本系列之前的文章參考 :
所謂表單模型化是指以類似資料表模型化方式使用 Python 物件來建立網頁表單, 透過繼承 Django 內建的 django.forms.Form 類別可定義一個與資料表模型類別相對應的表單類別, 實體化後的表單物件即可傳入模板網頁中利用該物件的 as_p(), as_ul(), 或 as_table() 等方法自動生成網頁表單.
一. 建立表單模型 :
本篇接續前一篇測試 "Python 學習筆記 : Django 4 用法整理 (六) CRUD 操作 (2)" 中所使用的 myapp1 應用程式, 將這個以 Bootstrap 框架為基礎的網站用表單模型來改寫基本的資料表 CRUD 操作, 不同之處是使用表單模型的網站具有欄位驗證功能, 這是前一篇測試沒有的 (須自行手工添加). 前一次測試的專案下載網址 :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/django-review-database-5.zip
在改寫前先在上層專案目錄下用 tree /f 指令檢視前一篇結束時的網站結構 :
D:.
│ manage.py
│ db.sqlite3
│
├─mysite
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └─__pycache__
│ __init__.cpython-310.pyc
│ settings.cpython-310.pyc
│ wsgi.cpython-310.pyc
│ __init__.cpython-311.pyc
│ settings.cpython-311.pyc
│ urls.cpython-311.pyc
│ wsgi.cpython-311.pyc
│ urls.cpython-310.pyc
│
└─myapp1
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│ urls.py
│
├─migrations
│ │ __init__.py
│ │ 0001_initial.py
│ │ 0002_alter_members_email.py
│ │
│ └─__pycache__
│ __init__.cpython-310.pyc
│ 0001_initial.cpython-310.pyc
│ 0002_alter_members_email.cpython-310.pyc
│ __init__.cpython-311.pyc
│ 0002_alter_members_email.cpython-311.pyc
│ 0001_initial.cpython-311.pyc
│
├─__pycache__
│ __init__.cpython-310.pyc
│ apps.cpython-310.pyc
│ models.cpython-310.pyc
│ __init__.cpython-311.pyc
│ apps.cpython-311.pyc
│ models.cpython-311.pyc
│ admin.cpython-311.pyc
│ admin.cpython-310.pyc
│ urls.cpython-310.pyc
│ views.cpython-310.pyc
│
└─templates
get_record.htm
list_all_records.htm
add_record.htm
edit_record.htm
base.htm
bootstrap.htm
其中的模型層 models.py 裡面定義了一個包含六個欄位的資料表模型類別 Members :
from django.db import models
class Members(models.Model):
name=models.CharField(max_length=20, null=False)
gender=models.CharField(max_length=2, default='男', null=False)
birthday=models.DateField(null=False)
email=models.CharField(max_length=120, blank=True, default='')
phone=models.CharField(max_length=50, blank=True, default='')
address=models.CharField(max_length=255, blank=True, default='')
def __str__(self):
return self.name
在前一篇測試中, 我們是在模板網頁 add_record.htm 與 edit_record.htm 裡依據 Members 模型中的欄位手刻 HTML 網頁表單來新增或編輯紀錄; 其實 Django 提供了更省力的表單模型化作法, 它模仿 ORM 機制以資料模型物件來映射實體資料表做法, 內建了 Form 與 ModelForm 表單類別, 利用表單物件來映射實體網頁表單, 本篇先測試 Form 類別用法.
首先在應用程式目錄 myapp1 底下建立一個 forms.py 模組 (這名稱是固定的), 從 django 匯入 forms 模組, 然後定義一個繼承 forms.Form 類別的子類別, 於其中呼叫 forms.xxxField() 欄位定義函式來建立表單欄位變數, 格式與 models.py 類似, 語法如下 :
# forms.py
from django import forms
class table_nameForm(forms.Form):
field_name_1=forms.xxxField(**kwargs)
field_name_2=forms.xxxField(**kwargs)
......
其中類別名稱可自訂, 但為了與 models.py 中的資料表相對應, 通常會用資料模型類別名稱後面串上 'Form' 為表單類別名稱. 常用欄位定義函式如下表 :
常用的表單欄位定義函式 | 說明 |
CharField(**kwargs) | 單行文字欄位或多行文字區域欄位, 常用參數 : label (標籤), max_length (長度), nitial (初始值), widget (控件), 設為 forms.Textarea 時為多行文字區域 (Textarea) |
ChoiceField(**kwargs) | 下拉式選單, 常用參數 label, choices 選項 (二維串列 [[值, 顯示]]) |
BooleanField(**kwargs) | 核取方塊 (checkbox), 常用參數 label (標籤) |
EmailField(**kwargs) | Email 欄位, 常用參數 label (標籤), required (必填) |
URLField(**kwargs) | URL 欄位, 常用參數 label (標籤), |
IntegerField(**kwargs) | 整數欄位, 常用參數 label (標籤) |
DateField(**kwargs) | 日期欄位, 常用參數 label (標籤) |
DateTimeField(**kwargs) | 日期時間欄位, 常用參數 label (標籤) |
欄位定義函式可傳入關鍵字參數, 常用參數如下表 :
常用的欄位參數 | 說明 |
label | 欄位標籤 (字串), 用來標示這是甚麼欄位 |
requred | 必填欄位 (布林), 欄位驗證用 |
initial | 欄位初始值 (任何值), 即元素的 value 屬性值 |
widget | 指定欄位的輸入控件, 例如 Textarea, RadioSelect, CheckBoxSelect |
max_length | 最大字元長度 (整數), 用於 CharField 欄位驗證 |
min_length | 最小字元長度 (整數), 用於 CharField 欄位驗證 |
label_suffix | 添加在欄位標籤後面的尾綴 (字串), 也可放在 Form 類別參數 (全部欄位加尾綴) |
help_text | 顯示在欄位下方的說明 (字串), 會放在一個 span 元素中. |
error_messages | 驗證失敗時用來覆蓋預設錯誤訊息 (字典), 例如 {'required' : '必填欄位'}) |
help_text | 顯示在欄位下方的說明 (字串), 會放在一個 span 元素中. |
disabled | 是否將此欄位禁能 (布林), disabled=True 會在該元素中添加 disabled 屬性 |
其中 label 參數幾乎是每個欄位的必要參數, 用來顯示欄位標籤, 事實上就是渲染成 HTML 的 label 標籤. 更多表單欄位定義函式參考 :
依據上面的語法仿照 models.py 於 myapp1 底下建立 forms.py 群組 :
# forms.py of App=myapp1
from django import forms
class MembersForm(forms.Form):
name=forms.CharField(label='姓名', max_length=20, required=True)
gender=forms.CharField(label='性別', max_length=2, initial='男')
birthday=forms.DateField(label='生日')
email=forms.CharField(label='信箱', required=True)
phone=forms.CharField(label='電話', required=True)
address=forms.CharField(label='地址', max_length=255)
存檔後開啟命令提示字元視窗 (如果已開啟要關掉重開才會抓到 forms.py 模組, 這很重要, 否則會出現模組不存在錯誤), 切換到上層專案目錄 mysite, 用 python manage.py shell 指令進入 Python 環境, 匯入上面在 forms.py 中定義的表單類別 MembersForm 後建立表單實例 :
python manage.py shell
E:\django\test4\mysite>python manage.py shell
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.15.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from myapp1.forms import MembersForm # 匯入表單類別
In [2]: f=MembersForm() # 建立表單物件
In [3]: f
Out[3]: <MembersForm bound=False, valid=Unknown, fields=(name;gender;birthday;email;phone;address)>
此處表單物件屬性 bound=False 表示此表單尚未綁定 (即尚未填入資料), fields 欄位則列舉此表單之欄位名稱. 建立表單物件後即可呼叫下列 4 個方法來生成表單 :
表單物件的方法 | 說明 |
as_p() | 生成以 <P> 標籤裝載之表單欄位 HTML 碼 |
as_div() | 生成以 <DIV> 標籤裝載之表單欄位 HTML 碼 |
as_ul() | 生成以 <UL> 標籤裝載之表單欄位 HTML 碼 (不含 <UL>, 須自行添加) |
as_table() | 生成以 <TABLE> 標籤裝載之表單欄位 HTML 碼 (不含 <TABLE>, 須自行添加) |
注意, as_ul() 與 as_table() 均不含外圍的 <UL> 與 <TABLE> 標籤, 其傳回值從視圖模組傳遞至模板網頁時必須自行添加 <UL> 與 <TABLE> 標籤, 否則 HTML 碼將不完整. 這種設計有個好處, 我們可在 <UL> 與 <TABLE> 套上 id 與樣式類別以控制表格的外觀或互動特性.
In [4]: f.as_p()
Out[4]: '<p>\n <label for="id_name">姓名:</label>\n <input type="text" name="name" maxlength="20" required id="id_name">\n \n \n </p>\n\n \n <p>\n <label for="id_gender">性別:</label>\n <input type="text" name="gender" value="男" maxlength="2" required id="id_gender">\n \n \n </p>\n\n \n <p>\n <label for="id_birthday">生日:</label>\n <input type="text" name="birthday" required id="id_birthday">\n \n \n </p>\n\n \n <p>\n <label for="id_email">信箱:</label>\n <input type="text" name="email" required id="id_email">\n \n \n </p>\n\n \n <p>\n <label for="id_phone">電話:</label>\n <input type="text" name="phone" required id="id_phone">\n \n \n </p>\n\n \n <p>\n <label for="id_address">地址:</label>\n <input type="text" name="address" maxlength="255" required id="id_address">\n \n \n \n \n </p>'
可見每個欄位都被包在各自的 <P> 元素中.
In [5]: f.as_ul()
Out[5]: '<li>\n \n <label for="id_name">姓名:</label>\n <input type="text" name="name" maxlength="20" required id="id_name">\n \n \n </li>\n\n <li>\n \n <label for="id_gender">性別:</label>\n <input type="text" name="gender" value="男" maxlength="2" required id="id_gender">\n \n \n </li>\n\n <li>\n \n <label for="id_birthday">生日:</label>\n <input type="text" name="birthday" required id="id_birthday">\n \n \n </li>\n\n <li>\n \n <label for="id_email">信箱:</label>\n <input type="text" name="email" required id="id_email">\n \n \n </li>\n\n <li>\n \n <label for="id_phone">電 話:</label>\n <input type="text" name="phone" required id="id_phone">\n \n \n </li>\n\n <li>\n \n <label for="id_address">地址:</label>\n <input type="text" name="address" maxlength="255" required id="id_address">\n \n \n \n \n </li>'
可見每個欄位都被包在各自的 <LI> 元素中.
In [6]: f.as_div()
Out[6]: '<div>\n \n <label for="id_name">姓名:</label>\n \n \n \n <input type="text" name="name" maxlength="20" required id="id_name">\n \n \n</div>\n\n <div>\n \n <label for="id_gender">性別:</label>\n \n \n \n <input type="text" name="gender" value="男" maxlength="2" required id="id_gender">\n \n \n</div>\n\n <div>\n \n <label for="id_birthday">生日:</label>\n \n \n \n <input type="text" name="birthday" required id="id_birthday">\n \n \n</div>\n\n <div>\n \n <label for="id_email">信箱:</label>\n \n \n \n <input type="text" name="email" required id="id_email">\n \n \n</div>\n\n <div>\n \n <label for="id_phone">電話:</label>\n \n \n \n <input type="text" name="phone" required id="id_phone">\n \n \n</div>\n\n <div>\n \n <label for="id_address">地址:</label>\n \n \n \n <input type="text" name="address" maxlength="255" required id="id_address">\n \n \n \n \n</div>'
可見每個欄位都被包在各自的 <DIV> 元素中.
In [7]: f.as_table()
Out[7]: '<tr>\n <th><label for="id_name">姓名:</label></th>\n <td>\n \n <input type="text" name="name" maxlength="20" required id="id_name">\n \n \n </td>\n </tr>\n\n <tr>\n <th><label for="id_gender">性別:</label></th>\n <td>\n \n <input type="text" name="gender" value="男" maxlength="2" required id="id_gender">\n \n \n </td>\n </tr>\n\n <tr>\n <th><label for="id_birthday">生日:</label></th>\n <td>\n \n <input type="text" name="birthday" required id="id_birthday">\n \n \n </td>\n </tr>\n\n <tr>\n <th><label for="id_email">信箱:</label></th>\n <td>\n \n <input type="text" name="email" required id="id_email">\n \n \n </td>\n </tr>\n\n <tr>\n <th><label for="id_phone">電話:</label></th>\n <td>\n \n <input type="text" name="phone" required id="id_phone">\n \n \n </td>\n </tr>\n\n <tr>\n <th><label for="id_address">地址:</label></th>\n <td>\n \n <input type="text" name="address" maxlength="255" required id="id_address">\n \n \n \n \n </td>\n </tr>'
可見每個欄位都被包在各自的 <TD> 元素中, 而欄位標籤 (label) 則是被包在 <TH> 元素中. 注意, 以上 as_p(), as_div(), as_ul(), 與 as_table() 四個方法均只生成表單欄位, 並不含 SUBMIT 與 RESET 按鈕, 必須自行添加.
二. 修改視圖模組 views.py 與模板網頁 :
接下來要修改視圖模組 views.py, 於其中建立表單物件, 然後將其傳遞給模板網頁輸出, 修改的部分僅在於 add_record() 與 edit_record() 這兩個函式 (藍色字體部分) :
# views.py of App=myapp1
from django.shortcuts import render, redirect
from myapp1.models import Members
from myapp1.forms import MembersForm
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())
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):
f=MembersForm() # 建立空表單物件 (未綁定)
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('日', '-')
# 建立表單物件 (已綁定)
f=MembersForm({'name': record.name,
'gender': record.gender,
'birthday': birthday,
'email': record.email,
'phone': record.phone,
'address': record.address})
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/')
在 add_record() 中我們只是在開頭添加 f=MembersForm() 建立一個空的表單物件 f 而已, 它會被 locals() 打包到字典中傳遞給模板網頁 add_record.htm, 即可用 f.as_table/f.as_p/f.as_ul/f_as_div 等 自動生成表單欄位.
在 edit_record() 中也是用 f=MembersForm() 建立一個表單物件, 但傳入從資料表查詢到的紀錄欄位組成的字典作為參數, 這個填入動作稱為綁定 (bound), 用 python manage.py shell 指令進入 Python 命令列查詢 f.is_bound 屬性會傳回 True; 反之, 空的表單物件會傳回 False.
接下來修改此模板網頁 add_record.htm, 刪除原先手刻放在 table 元素中的表單欄位, 改為模板指令 {{ f.as_table }}, 如上所述, as_table 方法只輸出 TABLE 的 TH, TD 等欄位部分, 不包含 TABLE 本身與 SUBMIT, RESET 按鈕 :
<!-- add_record.htm -->
{% extends "bootstrap.htm" %}
{% block title %}新增紀錄{% endblock %}
{% block body %}
<h4 class="m-4">新增會員資料</h4>
<div class="container ml-3">
<form method="post" action="." name="add_record">
{% csrf_token %}
<table class="table-bordered">
{{ f.as_table() }}
<tr>
<td colspan="2" class="p-2">
<input type="submit" value="送出">
<input type="reset" value="重設">
</td>
</tr>
</table>
<span style='color:red;'>{{ message }}</span>
</form>
</div>
{% endblock %}
注意, 在模板中呼叫物件的方法不可以加括號 (只要用 f.as_table 即可), 否則會出現如下錯誤 :
Could not parse the remainder: '()' from 'f.as_table()'
這時用 python manage.py runerver 指令開啟開發伺服器, 瀏覽 http://127.0.0.1:8000/myapp1/add_record/ 網頁就可以看到 f.as_table 所生成的表單欄位 :
由於是自動生成, 無法在 th 與 td 元素上添加 Bootstrap 樣式類別, 渲染效果與前一篇測試之結果有些差異, 不過 padding/magin 可以利用 style 區塊另行設定. 輸入欄位資料後送出即能存入資料表, 功能與前一篇相同.
最後修改 edit_record.htm 模板, 與 add_record.htm 類似, 也是用模板指令 {{ f.as_table }} 取代手刻欄位, 由於表單物件 f 已在 views.py 的 edit_record() 函式中綁定 (填入)一筆紀錄, 因此用 f.as_table 生成欄位時會在欄位中自動填入記錄中相對應的欄位值 :
<!-- edit_record.htm -->
{% extends "bootstrap.htm" %}
{% block title %}編輯紀錄{% endblock %}
{% block body %}
<h4 class="m-4">編輯會員資料</h4>
<div class="container ml-3">
<form method="post" action="/myapp1/edit_record/{{ record.id }}/update/" name="add_record">
{% csrf_token %}
<table class="table-bordered">
{{ f.as_table }}
<tr>
<td colspan="2" class="p-2">
<input type="submit" value="送出">
<input type="reset" value="重設">
</td>
</tr>
</table>
<span style='color:red;'>{{ message }}</span>
</form>
</div>
{% endblock %}
瀏覽 http://127.0.0.1:8000/myapp1/list_all_rcords/ 網頁, 並點擊 "編輯" 超連結就會前往 /myapp1/edit_rcord/id 紀錄編輯網頁, 看到 f.as_table 所生成的表單欄位 :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/django-review-database-6.zip
參考 :
三. 欄位驗證功能 :
上面的測試中在定義表單欄位時有傳入 required 這個參數, 若設為 True 表示此欄位必須輸入才能提交; 另外 min_length 與 max_length 參數則是用來限制輸入欄位的字元長度, 這些參數都是用來做欄位驗證用的, required 會在元素中添加 required 屬性; min_length 與 max_length 則會分別添加 minlength 與 maxlength 屬性 .
但是, 在上面範例的 MembersForm 表單模型中雖然只在 name, email, form 三個欄位的定義中有指定 required, 但實際測試發現, 其實最後生成的表單欄位都有 required 屬性, 例如在新增紀錄時若所有欄位都填了, 只有生日未填就按送出, 則該欄位下方會出現 "請填寫這個欄位" 提示, 表單並未被提交 :
這是因為 Django 表單物件在生成欄位時預設會替每個欄位添加 required 屬性之故, 檢視新增記錄的網頁原始碼, 可見 {{ f.as_table }} 生成的每個欄位都有 required 屬性 :
<form method="post" action="." name="add_record">
<input type="hidden" name="csrfmiddlewaretoken" value="5ZJ7iENDjzjxWkxkLNXzIYATzAN8ecv1Qs2oIKpKVqMnsl5GHJEI1jeEWNC2PI6P">
<table class="table-bordered">
<tr>
<th><label for="id_name">姓名:</label></th>
<td>
<input type="text" name="name" maxlength="20" required id="id_name">
</td>
</tr>
<tr>
<th><label for="id_gender">性別:</label></th>
<td>
<input type="text" name="gender" value="男" maxlength="2" required id="id_gender">
</td>
</tr>
<tr>
<th><label for="id_birthday">生日:</label></th>
<td>
<input type="text" name="birthday" required id="id_birthday">
</td>
</tr>
<tr>
<th><label for="id_email">信箱:</label></th>
<td>
<input type="text" name="email" required id="id_email">
</td>
</tr>
<tr>
<th><label for="id_phone">電話:</label></th>
<td>
<input type="text" name="phone" required id="id_phone">
</td>
</tr>
<tr>
<th><label for="id_address">地址:</label></th>
<td>
<input type="text" name="address" maxlength="255" required id="id_address">
</td>
</tr>
<tr>
<td colspan="2" class="p-2">
<input type="submit" value="送出">
<input type="reset" value="重設">
</td>
</tr>
</table>
<span style='color:red;'>請輸入資料</span>
</form>
可見不論在表單模型定義中有沒有傳入 required 參數, 每一個欄位都有 required 屬性. 但 minlength 與 maxlength 則必須在表單模型定義中有傳入 min_length 與 max_length 參數才會有.
如果要做進一步的欄位驗證, 例如 email 欄位格式是否正確 (上面的 email 欄沒有 @ 亂填也是可以順利提交的), 必須使用字串處理或正規表示法, 字串處理只能簡單判別 email 欄位的輸入值是否含有 @; 正規表示法則可以做 email 格式驗證, 例如 :
reg = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
修改視圖模組 views.py 中的 add_record() 與 edit_record() 函式如下 :
# views.py of App=myapp1
from django.shortcuts import render, redirect
from myapp1.models import Members
from myapp1.forms import MembersForm
import re
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())
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):
f=MembersForm() # 建立空表單物件 (未綁定)
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']
reg=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(reg, email): # email 格式正確
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: # email 格式不正確, 綁定原輸入值傳回去
f=MembersForm({'name': name,
'gender': gender,
'birthday': birthday,
'email': email,
'phone': phone,
'address': address})
message='E-mail 格式不正確'
return render(request, 'add_record.htm', locals())
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']
reg=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(reg, record.email):
record.save()
return redirect('/myapp1/list_all_records/')
else: # email 格式不正確, 綁定原輸入值傳回去
f=MembersForm({'name': record.name,
'gender': record.gender,
'birthday': record.birthday,
'email': record.email,
'phone': record.phone,
'address': record.address})
message='E-mail 格式不正確'
return render(request, 'edit_record.htm', locals())
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('日', '-')
# 建立表單物件 (已綁定)
f=MembersForm({'name': record.name,
'gender': record.gender,
'birthday': birthday,
'email': record.email,
'phone': record.phone,
'address': record.address})
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/')
因為要用到正規式, 所以此處的 views.py 需匯入 re 模組, 在 add_record() 中, 如果 request.method 是 'POST' 表示係請求來自於新增表單的提交, 這時需用正規式驗證傳來的 email 欄位格式是否符合要求, 若符合就新增紀錄至資料表, 否則就把接收到的所有參數打包成字典, 傳給 MembersForm() 建立表單物件 (綁定), 傳回給模板網頁 add_record.htm, 這樣使用者才不會因為 email 欄不合格而必須重新輸入所有欄位資料. edit_record() 的改法也是類似.
新增紀錄時若 email 格式不符, 按提交後會在表單底下顯示 "E-mail 格式不正確" :
注意, 此例修改了 forms.py 裡面表單模型定義中的 birthday 欄位, 添加了 help_text 參數, 這樣會在該欄位底下多出一個 span 元素, 說明生日的格式是 'YYMM-DD'.
# forms.py of App=myapp1
from django import forms
class MembersForm(forms.Form):
name=forms.CharField(label='姓名', max_length=20, required=True)
gender=forms.CharField(label='性別', max_length=2, initial='男')
birthday=forms.DateField(label='生日', help_text='格式: YY-MM-DD')
email=forms.CharField(label='信箱', required=True)
phone=forms.CharField(label='電話', required=True)
address=forms.CharField(label='地址', max_length=255)
同樣地, 編輯紀錄時也是如此 :
如果 email 格式正確, 就能順利新增或更新了 :
以上測試的欄位驗證都屬於後後端驗證, 當驗證失敗時都會重新載入頁面, 雖然驗證也可以在前端以 Javascript 來做, 但若用戶端關掉 Javascript 則驗證就無法執行, 所以還是用後端驗證較保險.
此測試之專案壓縮檔存在 GitHub :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/dango-review-database-7.zip
1 則留言 :
I appreciate the practical tips and strategies you share.
張貼留言