調借的 "跨領域學Python : 資料科學基礎養成" 已到館, 先拿下面這本去換 :
Bootstrap 估計要明年初才會用到, 索書號 312.1695 4400 (3F東方語文圖書區) 2023年12月11日 星期一
2023年11月18日 星期六
Python 學習筆記 : Django 4 用法整理 (八) 表單模型化 (1)
在前兩篇筆記中分別使用一般 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
2023年11月10日 星期五
好站 : 使用 Bootstrap 快速建立響應式網站
最近在 Hahow 企業版上六角學院的響應式網站設計課程時, 找到該學院架設的 Bootstrp 教學網站, 內容豐富鉅細靡遺, 超棒的 :
去年為了 GitHub 架站教學的需要花了一個月複習 Bootstrap, 以為已練成 Bootstrap 大法, 如今看來還只是幼稚園階段啊! 加油!
2023年11月6日 星期一
Python 學習筆記 : Django 4 用法整理 (六) CRUD 操作 (2)
在前一篇的資料表 CRUD 測試中為了聚焦只使用單純的 HTML 模板, 本篇則是要用 Bootstrap 框架來美化版面, 本系列之前的文章參考 :
由於只是改變前端模板網頁, 因此後端的 views.py 與 urls.py 等控制器都不用改. 首先參考 "Python 學習筆記 : Django 4 用法整理 (二) 網頁模板" 這篇建立一個基礎模板 :
<!--base.htm-->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
{% block link %}{% endblock %}
{% block script %}{% endblock %}
<style>
{% block style %}{% endblock %}
</style>
<body>
{% block body %}{% endblock %}
</body>
</html>
然後繼承 base.htm 建立 Bootstrap 框架的模板網頁 bootstrap.htm :
<!--bootstrap.htm-->
{% extends "base.htm" %}
{% block link %}
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0/dist/css/bootstrap.min.css">
{% endblock %}
{% block script %}
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@4.0.0/dist/js/bootstrap.min.js"></script>
{% endblock %}
這樣就可以修改 templates 下前一個 CRUD 測試中的模板, 讓他們都繼承 bootstrap.htm.
首先修改 list_all_records.htm 為繼承 bootstrap.htm, 並於 table, th, td, h3 等標籤上添加 margin (m 開頭) 與 padding (p 開頭) 的樣式類別以調整內外距離 :
<!-- list_all_records.htm -->
{% extends "bootstrap.htm" %}
{% block title %}讀取資料表{% endblock %}
{% block body %}
<h4 class="m-4">顯示 Members 資料表的全部紀錄
<a href="/myapp1/add_record/">+ 新增紀錄</a>
</h4>
<div class="container ml-3">
<table class="table-bordered">
<thead>
<tr>
<th class="p-2">編號</th>
<th class="p-2">姓名</th>
<th class="p-2">性別</th>
<th class="p-2">生日</th>
<th class="p-2">信箱</th>
<th class="p-2">電話</th>
<th class="p-2">地址</th>
<th class="p-2">動作</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr>
<td class="p-2">
<a href="/myapp1/get_record_by_id/{{ record.id }}/">
{{ record.id }}
</a>
</td>
<td class="p-2">
<a href="/myapp1/get_record_by_name/{{ record.name }}/">
{{ record.name }}
</a>
</td>
<td class="p-2">{{ record.gender }}</td>
<td class="p-2">{{ record.birthday }}</td>
<td class="p-2">{{ record.email }}</td>
<td class="p-2">{{ record.phone }}</td>
<td class="p-2">{{ record.address }}</td>
<td class="p-2">
<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 %}
</tbody>
</table>
</div>
{% endblock %}
與前次測試不同之處是這裡把 id 與 name 欄位都加上超連結分別瀏覽 /myapp1/list_record_by_id/id 與 /myapp1/list_record_by_name/name, 節果如下 :
接著修改其他模板網頁, 均繼承 bootstrap.htm :
<!-- get_record.htm -->
{% extends "bootstrap.htm" %}
{% block title %}讀取資料表{% endblock %}
{% block body %}
<div class="container ml-3">
<h2>顯示 Members 資料表的一筆紀錄</h2>
<h3>{{type_of_record}}</h3>
編號 : {{record.id}}<br>
姓名 : {{record.name}}<br>
性別 : {{record.gender}}<br>
生日 : {{record.birthday}}<br>
信箱 : {{record.email}}<br>
電話 : {{record.phone}}<br>
地址 : {{record.address}}<br>
<h3 style='color:red;'>{{message}}</h3>
</div>
{% endblock %}
點擊 list_all_records.htm 中的 id 或 name 欄位的超連結就會顯示那筆紀錄 :
下面是新增紀錄網頁 :
<!-- 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">
<tr>
<th class="p-2">姓名</td>
<td class="p-2"><input type='text' name='name' id='name'></td>
</tr>
<tr>
<th class="p-2">性別</td>
<td class="p-2">
<input type='radio' name='gender' id='male' value='男' checked="checked">男
<input type='radio' name='gender' id='female' value='女'>女
</td>
</tr>
<tr>
<th class="p-2">生日</td>
<td class="p-2"><input type='text' name='birthday' id='birthday' value='2023-01-01'></td>
</tr>
<tr>
<th class="p-2">信箱</td>
<td class="p-2"><input type='text' name='email' id='email'></td>
</tr>
<tr>
<th class="p-2">電話</td>
<td class="p-2"><input type='text' name='phone' id='phone'></td>
</tr>
<tr>
<th class="p-2">地址</td>
<td class="p-2"><input type='text' name='address' 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;'>{{ message }}</span>
</form>
</div>
{% endblock %}
點擊 list_all_records.htm 中的 + 新增紀錄顯示如下頁面 :
下面是編輯紀錄網頁 edit_record.htm :
<!-- 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">
<tr>
<th class="p-2">姓名</td>
<td class="p-2"><input type='text' name='name' id='name' value={{ record.name }}></td>
</tr>
<tr>
<th class="p-2">性別</td>
<td class="p-2">
{% 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 class="p-2">生日</td>
<td class="p-2"><input type='text' name='birthday' id='birthday' value={{ record.birthday }}></td>
</tr>
<tr>
<th class="p-2">信箱</td>
<td class="p-2"><input type='text' name='email' id='email' value={{ record.email }}></td>
</tr>
<tr>
<th class="p-2">電話</td>
<td class="p-2"><input type='text' name='phone' id='phone' value={{ record.phone }}></td>
</tr>
<tr>
<th class="p-2">地址</td>
<td class="p-2"><input type='text' name='address' id='address' value={{ record.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;'>{{ message }}</span>
</form>
</div>
{% endblock %}
點擊 list_all_records.htm 中的編輯超連結顯示如下頁面 :
用 Bootstrap 簡簡單單就讓醜醜的原生網頁改頭換面, 有空要好好地來把 Bootstrap 再複習一遍. 以上測試範例壓縮檔放在 GitHub :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/django-review-database-5.zip
2023年10月6日 星期五
安裝 Viusal Studio Code
最近網友詢問我關於用 VS code 做 ESP32 開發問題, 我是建議若寫 MicroPython 可用 Thonny, 我平常都用這個小巧的編輯器寫 Python/MicroPython. 若在 ESP32 要寫 C 程式那當然用 Arcuino IDE.
今天上 Hahow 六角學院的 Bootstrap 課程時老師使用 Visual Studio Code 寫網頁, 我發現 VS code 還蠻好用的 (例如按快捷鍵將凌亂的網頁整理好), 所以就下載安裝來玩看看, 下載網址如下 :
安裝檔大小約 92 MB, 對大檔案有恐懼症的我還可接受, 安裝過程只有一個地方可能需要改預設設定 (主要是取消將 VSC 設為所支援檔案類型之預設編輯器, 因我不喜歡它一桶江湖) :
為了避免 VS Code 變成很多檔案的預設編輯器, 將預設勾選改成如下 :
從課程中發現 VS Code 有許多編輯上的便利, 例如將 HTML 碼弄整齊, 同時編輯好幾列程式碼等, 確實是個好工具.
學習 Bootsrap 可安裝下面兩個 VScode 擴充套件 :
- Preview on Web Server :
可直接在 VS code 直接瀏覽網頁, 不須另外開啟瀏覽器, 只要存檔立即更新顯示網頁, 且可與 HTML 編碼左右對照. - Sublime Text Keymap :
鍵盤映射文件 (還不清楚實際用途).
按左邊上面第五個按鈕 (Extensions), 分別輸入上面兩個擴充套件名稱, 按 Install 安裝 :
其中 Preview on Web Server 提供兩個常用快捷鍵, 可以用來瀏覽網頁 :
- Ctrl+Shift+ P : 在 VS code 右邊開啟邊框來顯示網頁
- Ctrl+Shift+ L : 開啟預設瀏覽器來顯示網頁
這樣就可以開始用 VS code 來編寫網頁了. 按右上角第一個按鈕 (Explorer), 再按 Open a folder 按鈕開啟一個工作目錄 (在 VS code 一個目錄就是一個專案) :
這時會彈出一個視窗詢問是否信任此 folder 下檔案之作者, 按 Yes :
這樣便建立了一個在 demo 資料夾下的專案, 按右上方圈起來的按鈕新增網頁 index.htm :
然後在編輯區輸入驚嘆號 ! 再按 Enter 就會自動載入預設網頁模板 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
2022年8月28日 星期日
好站 : 好用工具箱
最近在測試 Python 的 random 模組中的排列函式時, 偶然找到這個好站 :
# 好用工具箱
裡面分門別類提供了時間, 數學, 文字, 財務等線上工具, 例如股票賣多少不吃虧, 我以前也想過這問題, 打算用 Javascript 寫個程式來算, 但有現成的就不用啦 !
觀察原始碼, 原來這網站是用 Bootstrap 設計的, 有空可以回頭觀摩他的做法.
2022年8月14日 星期日
完成教學網站 "用 GitHub 輕鬆架站"
因下周學會上課需要, 這兩周努力將之前部落格文章關於 GitHub 架站的濃縮整理在新申請的 GitHub 帳號上, 今天終於算是完工了. 雖然有將 CSS, Javascriipt, 與 jQuery 列入教學文件項目, 但這三個部分屬於進階要寫程式的領域, 對於在 GitHub 上架站來說, 只要對 HTML 與 Bootstrap 有概念, 會將模板改成自己的網頁就夠了, 所以以後有空再補充吧.
本機專案資料夾 : D:\test\html\github_page
雖然兩周前交出簡報檔就算交差了, 但花兩周時間手刻一個網站卻是十幾年前才有的傻勁, 趁這機會又把 HTML 與 Bootstrap 複習一遍也還不錯.
2022-10-27 補充 :
這兩天新增 Jumbotron 大螢幕廣告範例, 但 Carousel 廣告輪播測試 NG, 可能是版本關係.
2023-01-15 補充 :
今天發現一個不錯的 GitPage 教學網站 :
2022年8月3日 星期三
Bootstrap 4 學習筆記 (十) : 警示, 面板與手風琴
這幾天因為要準備客家語文學會的 GitHub 架站簡報資料, 把 Bootstrap 筆記複習了一遍, 發現還缺了 alert (警示), panel (面板), 以及 accordion (手風琴) 這幾個內容呈現容器元件還沒測試, 所以就趁這機會做個完結唄. 本系列之前的測試文章參考 :
# Bootstrap 4 學習筆記 (一) : 環境配置
# Bootstrap 4 學習筆記 (二) : 網格與容器
# Bootstrap 4 學習筆記 (三) : 表格
# Bootstrap 4 學習筆記 (四) : 圖示與按鈕
# Bootstrap 4 學習筆記 (五) : 文字與圖片效果
# Bootstrap 4 學習筆記 (六) : 表單
# Bootstrap 4 學習筆記 (七) : 按鈕選單
# Bootstrap 4 學習筆記 (八) : 導覽頁籤
# Bootstrap 4 學習筆記 (九) : 導覽列
Bootstrap 教學文件參考 :
# 第 10 堂課 - 初探 bootstrap 網頁製作
# Bootstrap 初學介紹 #靜態篇
一. Alert (警示) :
此元件使用具有醒目背景色的 div 區塊來顯示重要訊息, 主要套用 alert 與下列五個樣式類別, 並指定 role="alert" 屬性 :
這些 div 外面再用 class="container" 的 div 區塊包起來, 基本結構如下 :
範例 2-1 : 各種背景色之面板區塊測試 [看原始碼]
手風琴群組裡面的每個面板都可以各自指定板首的背景色樣式類別, 例如 panel-primary, panel-info 等, 如下列範例所示 :
# Bootstrap 4 學習筆記 (一) : 環境配置
# Bootstrap 4 學習筆記 (二) : 網格與容器
# Bootstrap 4 學習筆記 (三) : 表格
# Bootstrap 4 學習筆記 (四) : 圖示與按鈕
# Bootstrap 4 學習筆記 (五) : 文字與圖片效果
# Bootstrap 4 學習筆記 (六) : 表單
# Bootstrap 4 學習筆記 (七) : 按鈕選單
# Bootstrap 4 學習筆記 (八) : 導覽頁籤
# Bootstrap 4 學習筆記 (九) : 導覽列
Bootstrap 教學文件參考 :
# 第 10 堂課 - 初探 bootstrap 網頁製作
# Bootstrap 初學介紹 #靜態篇
一. Alert (警示) :
此元件使用具有醒目背景色的 div 區塊來顯示重要訊息, 主要套用 alert 與下列五個樣式類別, 並指定 role="alert" 屬性 :
| 樣式類別 | 說明 |
| alert | 必要的警示區塊樣式 |
| alert-success | 背景為咖啡色之區塊 |
| alert-info | 背景為淡藍色之區塊 |
| alert-warning | 背景為黃色之區塊 |
| alert-danger | 背景為黃色之區塊 |
| alert-dismissible | 添加可關閉按鈕 (x) |
這些 div 外面再用 class="container" 的 div 區塊包起來, 基本結構如下 :
<div class="container">
<div class="alert alert-success" role="alert">
這是 alert-success 訊息!
</div>
</div>
例如 :
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="alert alert-success" role="alert">
這是 alert-success 訊息!
</div>
<div class="alert alert-info" role="alert">
這是 alert-info 訊息!
</div>
<div class="alert alert-warning" role="alert">
這是 alert-warning 訊息!
</div>
<div class="alert alert-danger" role="alert">
這是 alert-danger 訊息!
</div>
</div>
</body>
</html>
結果如下 :
如果要建立可以關閉的警示, 需要添加 alert-dismissible 樣式類別, 並搭配一個有 close 樣式的 button 物件, 例如 :
<div class="alert alert-danger alert-dismissible" role="alert">
<button class="close" data-dismiss="alert">
<span>×</span>
</button>
這是 alert-danger 訊息!
</div>
完整範例如下 :
範例 1-2 : 可關閉之警示訊息區塊測試 [看原始碼]
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="alert alert-success" role="alert">
這是 alert-success 訊息!
</div>
<div class="alert alert-info" role="alert">
這是 alert-info 訊息!
</div>
<div class="alert alert-warning" role="alert">
這是 alert-warning 訊息!
</div>
<div class="alert alert-danger" role="alert">
這是 alert-danger 訊息!
</div>
<div class="alert alert-danger alert-dismissible" role="alert">
<button class="close" data-dismiss="alert">
<span>×</span>
</button>
這是 alert-danger 訊息!
</div>
</div>
</body>
</html>
結果如下 :
按後面那個 x 按鈕就可以關閉該條警示訊息了.
二. Panel (面板) :
面板也是像上面 alert 一樣的區塊, 但它除了主體 (body) 外還多出板首 (heading) 板尾 (footer) 兩部分, 不過首尾都是可有可無的. 基本結構由兩層 div 構成, 外層 div 需套用 panel 與下列四種背景色樣式類別 :
面板也是像上面 alert 一樣的區塊, 但它除了主體 (body) 外還多出板首 (heading) 板尾 (footer) 兩部分, 不過首尾都是可有可無的. 基本結構由兩層 div 構成, 外層 div 需套用 panel 與下列四種背景色樣式類別 :
| 面板背景色樣式類別 | 說明 |
| panel-default | 背景為灰色之預設面板 |
| panel-primary | 背景為藍色之面板 |
| panel-success | 背景為咖啡色之區塊 |
| panel-info | 背景為淡藍色之區塊 |
| panel-warning | 背景為黃色之區塊 |
| panel-danger | 背景為紅色之區塊 |
主體要套用 panel-body 樣式類別, 而板尾則套用 panel-footer 樣式類別, 基本結構如下 :
<div class="panel panel-default">
<div class="panel-heading">面板板首</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
例如 :
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">面板板首 (default)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">面板板首 (primary)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">面板板首 (success)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">面板板首 (info)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">面板板首 (warning)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">面板板首 (danger)</div>
<div class="panel-body">面板主體</div>
<div class="panel-body">面板板尾</div>
</div>
</div>
</body>
</html>
結果如下 :
板尾部分若不需要可以去除.
三. Accordion (手風琴) :
所謂手風琴效果 (accordion) 就是將網頁內容上下堆疊, 一次只展開一部份, 其餘部分會自動縮合起來的呈現方式, 這種可縮合 (collapse) 結構是在上面介紹的面板 (panel) 上發展出來的, 主要是添加了 a 標籤的頁內錨點 (anchor) 與 panel-collapse, collapse 等樣式類別來控制面板主體區塊的展開與縮合, 基本結構如下 :
三. Accordion (手風琴) :
所謂手風琴效果 (accordion) 就是將網頁內容上下堆疊, 一次只展開一部份, 其餘部分會自動縮合起來的呈現方式, 這種可縮合 (collapse) 結構是在上面介紹的面板 (panel) 上發展出來的, 主要是添加了 a 標籤的頁內錨點 (anchor) 與 panel-collapse, collapse 等樣式類別來控制面板主體區塊的展開與縮合, 基本結構如下 :
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse1">標題</a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse">
<div class="panel-body">內容</div>
</div>
</div>
參考 :
可見此組件基本上就是一個加工後的面板, 主要是板首標題改用 a 標籤來控制本體 div 區塊中的錨點 (id='collapse1') 以使其展開或縮合, 並為 a 標籤添加 data-toggle="collapse" 屬性, 另外, 本體 div 區塊也要加上 panel-collapse 與 collapse 兩個樣式類別, 完整網頁碼如下例 :
範例 3-1 : 可縮合展開的面板區塊 (預設縮合) [看原始碼]
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse1">標題</a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse">
<div class="panel-body">內容</div>
</div>
</div>
</div>
</body>
</html>
結果如下 :
預設內容是收合起來的, 點標題才會展開.
如果預設要展開, 可在本體的 div 區塊添加 in 樣式類別 :
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse1">標題</a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse in">
<div class="panel-body">內容</div>
</div>
</div>
例如 :
範例 3-2 : 可縮合展開的面板區塊 (預設展開) [看原始碼]
結果如下 :
手風琴 (Accordion) 就是以上面的可收合面板為基礎建構的, 將多個可縮合面板再用一個樣式類別為 panel-group 的 div 區塊包起來並指定一個 id, 每一個可縮合面板添加一個 data-parent 屬性指向此 id 即可, 基本結構 (兩個可收合面板) 如下 :
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse1">標題 1</a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse in">
<div class="panel-body">內容 1</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse2">標題 2</a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse">
<div class="panel-body">內容 2</div>
</div>
</div>
</div>
手風琴群組裡面的每個面板都可以各自指定板首的背景色樣式類別, 例如 panel-primary, panel-info 等, 如下列範例所示 :
範例 3-3 : 可縮合展開的面板區塊 (預設展開) [看原始碼]
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse1">標題 1</a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse in">
<div class="panel-body">內容 1 (default)</div>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse2">標題 2</a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse">
<div class="panel-body">內容 2 (primary)</div>
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse3">標題 3</a>
</h4>
</div>
<div id="collapse3" class="panel-collapse collapse">
<div class="panel-body">內容 3 (success)</div>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse4">標題 4</a>
</h4>
</div>
<div id="collapse4" class="panel-collapse collapse">
<div class="panel-body">內容 4 (info)</div>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse5">標題 5</a>
</h4>
</div>
<div id="collapse5" class="panel-collapse collapse">
<div class="panel-body">內容 5 (warning)</div>
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse6">標題 6</a>
</h4>
</div>
<div id="collapse6" class="panel-collapse collapse">
<div class="panel-body">內容 6 (danger)</div>
</div>
</div>
</div>
</div>
</body>
</html>
注意, 其中面板 1 的主體區塊添加了 in 樣式類別, 所以面板 1 預設是展開的, 結果如下 :
可見每開啟一個面板, 其餘面板就會自動收合. 不過因板首的標題是 a 超連結, 所以會出現底線, 若不想出現底線可於每個板首 a 標籤添加 style="text-decoration:none;" 的樣式屬性, 或者於 head 標籤內的 style 標籤中, 統一規範所有超連結都步出現底線 :
a {text-decoration:none;}
訂閱:
文章
(
Atom
)









































