使用 ModelForm 優化 Django 表單與資料庫操作
更新日期: 2024 年 12 月 2 日
本文為 Django 高階教學,第 2 篇:
- 使用 Poetry 管理 Django 專案:完整指南
- 使用 ModelForm 優化 Django 表單與資料庫操作 👈 所在位置
- Django 留言功能實作指南:從模型設計到應用完成
- Django 留言功能新增與視圖互動詳解
- 在 Django 中實現留言檢索與排序功能:完整指南
- 在 Django 中實現留言換行與顯示時間功能
- 在 Django 中實現留言刪除功能:完整指南
- 在 Django 中實現軟刪除功能:完整指南
- 在 Django 中實現軟刪除功能:Fat Model, Thin View
建議閱讀本文前,先閱讀完 Django 進階教學 系列文
在 Django 中,使用表單和資料模型進行交互是非常常見的需求。
傳統的手動操作方法雖然可行,但會導致代碼冗長且易出錯。
透過 Django 的 ModelForm,可以簡化表單處理和數據存儲的代碼,並提升開發效率。
本指南將帶你一步步了解如何使用 ModelForm 來實現新增與更新功能。
傳統方法的挑戰
以下是傳統方法直接操作模型存儲數據的範例:
resume = Resume()
resume.title = request.POST["title"]
resume.skill = request.POST["skill"]
resume.location = request.POST["location"]
resume.content = request.POST["content"]
resume.save()
改進建議:
- 關鍵字引數(Keyword Arguments)
使用關鍵字引數優化代碼:
Resume(title="xxx", skill="xxx", location="xxx", content="xxx").save()
- 解包操作
使用**request.POST
將request.POST
的數據直接展開為關鍵字引數:
Resume(**request.POST).save()
問題:
- 直接使用
**request.POST
時,會因包含csrfmiddlewaretoken
等無關鍵字引數而導致錯誤。 - 手動操作模型的重複性代碼不利於維護。
使用 ModelForm 簡化表單處理
什麼是 Django ModelForm?
- ModelForm 的定義:Django 提供了一個
ModelForm
類,能夠基於模型(Model)自動生成對應的表單(Form)。 - 核心作用:
- 減少代碼重複:通過模型字段來生成表單字段,避免手動定義。
- 簡化表單保存邏輯:直接與模型結合,方便進行數據的驗證與保存。
創建 ModelForm
在應用目錄下新建一個 forms.py
文件,並定義以下內容:
from django.forms import ModelForm
from .models import Resume
class ResumeForm(ModelForm):
class Meta:
model = Resume # 綁定的模型
fields = ["title", "skill", "location", "content"] # 欄位列表
表單字段的來源:
fields
指定哪些模型字段應出現在表單中。- 示意圖:模型字段與表單字段的對應關係
模型 (Model) 表單 (Form)
----------------- ------------------
title = models.CharField <input type="text" name="title">
skill = models.TextField <textarea name="skill"></textarea>
location = models.CharField <input type="text" name="location">
content = models.TextField <textarea name="content"></textarea>
在 views.py
中使用 ModelForm
新增資料:home
函數
from django.shortcuts import render, redirect, get_object_or_404
from .models import Resume
from .forms import ResumeForm
# 定義首頁函數,處理新增資料的邏輯
def home(request):
if request.POST: # 如果收到 POST 請求(表示表單被提交)
form = ResumeForm(request.POST) # 將提交的數據綁定到 ResumeForm 表單
form.save() # 驗證通過後,直接保存到資料庫
# 這段代碼是傳統寫法,但使用 ModelForm 可以簡化流程
# resume = Resume()
# resume.title = request.POST["title"]
# resume.skill = request.POST["skill"]
# resume.location = request.POST["location"]
# resume.content = request.POST["content"]
# resume.save()
return redirect("resumes:list") # 提交成功後,轉址到首頁(顯示資料列表)
# 如果是 GET 請求,則默認不處理(此處保留視圖擴展的可能性)
詳細解析:
if request.POST:
判斷是否收到 POST 請求,只有在表單提交時,這部分邏輯才會執行。form = ResumeForm(request.POST)
利用 Django 的 ModelForm,將 POST 請求中的數據自動綁定到表單對象,簡化資料綁定流程。form.save()
驗證表單後直接保存數據,等效於逐一為模型屬性賦值並執行.save()
。return redirect("resumes:list")
使用redirect
函數將用戶轉址到資料列表頁,避免表單重複提交的問題。
更新資料:new
函數
def new(request):
form = ResumeForm()
return render(request, 'resumes/new.html',{'form': form})
form = ResumeForm()
:- 初始化一個空的
ResumeForm
表單實例,準備在模板中顯示。 - 該表單尚未綁定任何數據,用於首次打開新增履歷頁面時,讓用戶輸入內容。
- 初始化一個空的
return render(...)
:- 將表單對象
form
傳遞到模板'resumes/new.html'
中。 {'form': form}
是模板上下文,包含視圖函數要傳遞給模板的數據。
- 將表單對象
更新資料:edit
函數
# 定義編輯函數,處理更新資料的邏輯
def edit(request, id):
# 根據主鍵 ID 獲取對應的 Resume 資料
resume = get_object_or_404(Resume, id=id)
if request.POST: # 如果收到 POST 請求(表示表單被提交)
# 將提交的數據與現有數據綁定,進行更新
form = ResumeForm(request.POST, instance=resume)
form.save() # 保存更新後的數據到資料庫
# 傳統寫法,逐一為模型屬性賦值
# resume.title = request.POST["title"]
# resume.skill = request.POST["skill"]
# resume.location = request.POST["location"]
# resume.content = request.POST["content"]
# resume.save()
return redirect("resumes:list") # 提交成功後,轉址到首頁(顯示資料列表)
# 如果是 GET 請求,返回編輯頁面,並將當前資料預填到表單
return render(
request,
"resumes/edit.html",
{"form": ResumeForm(instance=resume)}
)
詳細解析:
resume = get_object_or_404(Resume, id=id)
根據id
獲取指定的 Resume 資料。如果找不到,直接返回 404 錯誤。if request.POST:
如果是表單提交,則進行更新操作。form = ResumeForm(request.POST, instance=resume)
使用instance
關鍵字,將現有資料與表單提交的數據綁定,實現對數據的更新。form.save()
保存更新後的數據到資料庫,等效於逐一為模型屬性賦值並執行.save()
。return redirect("resumes:list")
保存成功後,使用轉址將用戶帶回資料列表頁。return render(...)
如果是 GET 請求,渲染編輯頁面,並將當前的 Resume 資料作為表單的預設值。
ModelForm 的工作流程示意圖
以下是 ModelForm 的運作過程,用圖表描述如何從模型到表單的生成,以及如何處理提交的數據:
+--------------------------------------------+
| Django Model |
| 定義數據結構與約束,如 title, skill, ... |
+--------------------------------------------+
⬇︎ (透過 Meta 定義)
+--------------------------------------------+
| Django ModelForm |
| - 根據模型字段生成表單字段 |
| - 定義需要的字段,如 fields=["title",...] |
+--------------------------------------------+
⬇︎ (用戶交互)
+--------------------------------------------+
| HTML 表單 (Form) |
| - 用戶輸入數據,例如 Title: "Resume A" |
+--------------------------------------------+
⬇︎ (表單提交)
+--------------------------------------------+
| ModelForm 數據處理流程 |
| 1. 驗證輸入數據 |
| 2. 調用 save 方法保存到模型 |
| 3. 保存到數據庫 |
+--------------------------------------------+
使用 as_p
簡化模板渲染
ModelForm 提供了一種簡單的方式來渲染表單欄位。
使用 {{ form.as_p }}
可以將表單渲染為 HTML <p>
標籤包裹的格式。
修改新增頁面模板
原始模板代碼:
<form action="{% url 'resumes:new' %}" method="POST">
{% csrf_token %}
title: <br />
<input type="text" name="title" /> <br />
skill: <br />
<input type="text" name="skill" /> <br />
location: <br />
<input type="text" name="location" /> <br />
content: <br />
<textarea name="content"></textarea> <br />
<button>提交</button>
</form>
修改後:
<form action="{% url 'resumes:new' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button>提交</button>
</form>
修改編輯頁面模板
原始模板代碼:
<form action="{% url 'resumes:edit' resume.id %}" method="POST">
{% csrf_token %}
title: <br />
<input type="text" name="title" value="{{ resume.title }}" /> <br />
skill: <br />
<input type="text" name="skill" value="{{ resume.skill }}" /> <br />
location: <br />
<input type="text" name="location" value="{{ resume.location }}" /> <br />
content: <br />
<textarea name="content">{{ resume.content }}</textarea> <br />
<button>更新</button>
</form>
修改後:
<form action="{% url 'resumes:edit' resume.id %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button>更新</button>
</form>
補充:ResumeForm
的行為與初始化邏輯
ResumeForm
是基於 Django 的表單類,用於處理表單的顯示、數據驗證以及與模型的交互。其行為取決於初始化時是否有參數。主要有以下三種情境:
1. ResumeForm()
(無參數)
- 行為:
- 初始化一個空白的表單,所有字段都沒有數據,適合在 GET 請求中首次渲染表單。
- 用戶可以通過瀏覽器在這個空白表單中輸入數據。
- 使用場景:
- 第一次展示表單,讓用戶輸入新數據。
- 效果: 此時模板中的
{{ form.as_p }}
將渲染出一個空白的表單供用戶輸入。
2. ResumeForm(request.POST)
(帶參數:綁定用戶提交的數據)
- 行為:
- 初始化一個已綁定數據的表單,第一個參數(如
request.POST
)是用戶提交的數據(例如來自 HTML 表單的輸入)。 - 表單會根據字段名稱,嘗試將傳入的數據綁定到表單字段上,並進行數據驗證。
- 初始化一個已綁定數據的表單,第一個參數(如
- 使用場景:
- 處理用戶提交的數據,驗證輸入是否符合要求,並執行後續邏輯(如保存到數據庫)。
- 效果:
- 當表單數據無效時,重新渲染表單,保留用戶輸入的數據,讓用戶能夠修正輸入錯誤。
3. ResumeForm(request.POST, instance=resume)
(帶第二個參數:綁定現有實例進行更新)
- 行為:
- 初始化一個已綁定數據的表單,第一個參數(如
request.POST
)是用戶提交的新數據,第二個參數instance
用於指定模型的現有實例。 - Django 會使用
instance
提供的數據填充表單,同時將提交的新數據應用到這個實例上。
- 初始化一個已綁定數據的表單,第一個參數(如
- 使用場景:
- 當需要編輯或更新現有數據時,讓表單預填現有數據,並接受用戶的修改。
- 適用於編輯頁面的場景(如更新履歷)。
- 效果:
- 在模板中,表單字段會顯示現有實例的數據(預填數據)。
- 當用戶提交表單時,僅會更新有修改的字段,其他字段保持不變。
結合模型的邏輯
如果 ResumeForm
是一個 模型表單(ModelForm),其行為與模型的交互會根據初始化參數進一步擴展:
- 初始化空白表單:
- 如果沒有傳參數,則創建一個沒有數據的表單,但表單字段是基於模型的字段自動生成的。
- 綁定數據的表單:
- 當傳入
request.POST
或其他類似數據時,表單會嘗試將傳入的數據與模型字段對應起來,並檢查輸入是否有效(如數據格式是否正確、是否符合字段的約束條件)。
- 當傳入
- 綁定現有實例的表單:
- 如果傳入
instance
,表單將使用該實例的數據預填字段,並在用戶提交數據後,更新這個實例。
- 如果傳入
- 保存數據到模型:
form.save()
:- 如果沒有
instance
,會創建新實例。 - 如果有
instance
,會更新該實例。
- 如果沒有
情境 | 用途 | 行為 |
---|---|---|
無參數 (ResumeForm() ) | 初始化空白表單,適合新增數據 | 表單字段無預設數據,用戶可手動輸入 |
單參數 (ResumeForm(request.POST) ) | 處理新數據的提交與驗證,適合新增數據 | 綁定用戶提交數據,驗證並保存 |
雙參數 (ResumeForm(request.POST, instance=resume) ) | 編輯現有實例的數據,適合更新場景 | 表單字段預填現有數據,用戶修改後,更新實例到數據庫 |
as_p
的意涵
as_p
是 Django 表單的內建方法,用於將表單欄位渲染成<p>
標籤包裹的 HTML。- 例如:
<p>
<label for="id_title">Title:</label>
<input type="text" name="title" id="id_title">
</p>
- 除了
as_p
,還有:as_table
:渲染成表格格式。as_ul
:渲染成無序列表格式。
好的,讓我們更清楚地解釋這部分的邏輯,幫助您深入理解 Django 表單如何根據模型字段生成對應的 HTML 標籤。
Django 表單如何生成 HTML?
當您使用 Django 的 ModelForm 類時,Django 根據模型字段的定義,自動生成對應的 HTML 結構,包含以下內容:
<label>
標籤:- 用於顯示字段的名稱(友好的標籤文字)。
- 文本內容默認來自模型字段的名稱,並轉為標準的標題格式(例如,
name
→Name
,created_at
→Created at
)。 for
屬性與對應的表單字段的id
保持一致。
- 輸入標籤(例如
<input>
或<textarea>
):id
屬性:自動設置為id_<字段名稱>
,例如字段名稱是name
,則id
為id_name
。name
屬性:設置為模型字段的名稱,例如字段名稱是name
,則name
也為name
。- 其他屬性(例如
required
、maxlength
等):根據模型字段的屬性自動設置。
標籤的選擇邏輯
Django 自動選擇的標籤類型(如 <input>
或 <textarea>
)取決於模型字段的類型,對應關係如下:
模型字段類型 | 對應的 HTML 標籤 | 附加屬性 |
---|---|---|
CharField | <input type="text"> | maxlength (從字段的 max_length 繼承) |
TextField | <textarea> | cols , rows (默認大小) |
EmailField | <input type="email"> | 瀏覽器會自動驗證 email 格式 |
IntegerField | <input type="number"> | 數字輸入框 |
DateField / DateTimeField | <input type="date"> / <input type="datetime-local"> | 日期選擇器 |
BooleanField | <input type="checkbox"> | 勾選框 |
ChoiceField (枚舉類型) | <select> | 下拉選單 |
FileField | <input type="file"> | 文件上傳選擇框 |
具體生成的 HTML 結構
以以下模型為例:
from django.db import models
class Resume(models.Model):
name = models.CharField(max_length=50)
skill = models.CharField(max_length=200)
location = models.CharField(max_length=200)
description = models.TextField()
對應的 ModelForm 定義:
from django import forms
from .models import Resume
class ResumeForm(forms.ModelForm):
class Meta:
model = Resume
fields = '__all__'
當在模板中渲染 {{ form }}
或 {{ form.as_p }}
時,會生成以下 HTML:
<p>
<label for="id_name">Name:</label>
<input type="text" name="name" maxlength="50" required id="id_name">
</p>
<p>
<label for="id_skill">Skill:</label>
<input type="text" name="skill" maxlength="200" required id="id_skill">
</p>
<p>
<label for="id_location">Location:</label>
<input type="text" name="location" maxlength="200" required id="id_location">
</p>
<p>
<label for="id_description">Description:</label>
<textarea name="description" cols="40" rows="10" required id="id_description"></textarea>
</p>
標籤屬性的詳細說明
<label>
標籤:for
屬性:指向對應輸入字段的id
(例如for="id_name"
)。- 顯示文字:默認取自模型字段名稱(以首字母大寫的形式顯示)。
- 您可以通過表單的
label
選項自定義顯示文字:
class ResumeForm(forms.ModelForm):
class Meta:
model = Resume
fields = '__all__'
labels = {
'name': '姓名',
'skill': '技能',
}
<input>
或<textarea>
標籤:id
屬性:默認為id_<字段名稱>
,例如id_name
。name
屬性:與模型字段名稱相同,例如name
。- 額外屬性:
maxlength
:來自模型字段的max_length
屬性。required
:如果字段在模型中非空(blank=False
),則自動添加。- 您可以通過
widgets
自定義這些屬性:
class ResumeForm(forms.ModelForm):
class Meta:
model = Resume
fields = '__all__'
widgets = {
'description': forms.Textarea(attrs={'rows': 5, 'placeholder': '輸入描述'}),
}
- 標籤類型選擇(如
<input>
或<textarea>
):- 基於字段類型:例如,
CharField
使用<input type="text">
,TextField
使用<textarea>
。 - 可自定義:通過
widgets
,可以更改默認標籤的類型和屬性。
- 基於字段類型:例如,
小結
使用 ModelForm 的優勢:
- 簡化代碼:
- 無需手動綁定和驗證資料,減少重複性代碼。
- 數據驗證:
- 自帶數據驗證功能,確保資料完整性。
- 易於維護:
- 表單欄位定義集中在 ModelForm 中,修改方便。
- 模板渲染簡化:
- 使用
as_p
等方法快速渲染表單。
- 使用