使用 ModelForm 優化 Django 表單與資料庫操作

更新日期: 2024 年 12 月 2 日

在 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()

改進建議:

  1. 關鍵字引數(Keyword Arguments)
    使用關鍵字引數優化代碼:
Resume(title="xxx", skill="xxx", location="xxx", content="xxx").save()
  1. 解包操作
    使用 **request.POSTrequest.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 請求,則默認不處理(此處保留視圖擴展的可能性)

詳細解析:

  1. if request.POST:
    判斷是否收到 POST 請求,只有在表單提交時,這部分邏輯才會執行。
  2. form = ResumeForm(request.POST)
    利用 Django 的 ModelForm,將 POST 請求中的數據自動綁定到表單對象,簡化資料綁定流程。
  3. form.save()
    驗證表單後直接保存數據,等效於逐一為模型屬性賦值並執行 .save()
  4. return redirect("resumes:list")
    使用 redirect 函數將用戶轉址到資料列表頁,避免表單重複提交的問題。

更新資料:new 函數

def new(request):
    form = ResumeForm()
    return render(request, 'resumes/new.html',{'form': form})
  1. form = ResumeForm()
    • 初始化一個空的 ResumeForm 表單實例,準備在模板中顯示。
    • 該表單尚未綁定任何數據,用於首次打開新增履歷頁面時,讓用戶輸入內容。
  1. 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)}
    )

詳細解析:

  1. resume = get_object_or_404(Resume, id=id)
    根據 id 獲取指定的 Resume 資料。如果找不到,直接返回 404 錯誤。
  2. if request.POST:
    如果是表單提交,則進行更新操作。
  3. form = ResumeForm(request.POST, instance=resume)
    使用 instance 關鍵字,將現有資料與表單提交的數據綁定,實現對數據的更新。
  4. form.save()
    保存更新後的數據到資料庫,等效於逐一為模型屬性賦值並執行 .save()
  5. return redirect("resumes:list")
    保存成功後,使用轉址將用戶帶回資料列表頁。
  6. 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),其行為與模型的交互會根據初始化參數進一步擴展:

  1. 初始化空白表單
    • 如果沒有傳參數,則創建一個沒有數據的表單,但表單字段是基於模型的字段自動生成的。
  2. 綁定數據的表單
    • 當傳入 request.POST 或其他類似數據時,表單會嘗試將傳入的數據與模型字段對應起來,並檢查輸入是否有效(如數據格式是否正確、是否符合字段的約束條件)。
  3. 綁定現有實例的表單
    • 如果傳入 instance,表單將使用該實例的數據預填字段,並在用戶提交數據後,更新這個實例。
  4. 保存數據到模型
    • 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 結構,包含以下內容:

  1. <label> 標籤
    • 用於顯示字段的名稱(友好的標籤文字)。
    • 文本內容默認來自模型字段的名稱,並轉為標準的標題格式(例如,nameNamecreated_atCreated at)。
    • for 屬性與對應的表單字段的 id 保持一致。
  2. 輸入標籤(例如 <input><textarea>
    • id 屬性:自動設置為 id_<字段名稱>,例如字段名稱是 name,則 idid_name
    • name 屬性:設置為模型字段的名稱,例如字段名稱是 name,則 name 也為 name
    • 其他屬性(例如 requiredmaxlength 等):根據模型字段的屬性自動設置。

標籤的選擇邏輯

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>

標籤屬性的詳細說明

  1. <label> 標籤
    • for 屬性:指向對應輸入字段的 id(例如 for="id_name")。
    • 顯示文字:默認取自模型字段名稱(以首字母大寫的形式顯示)。
    • 您可以通過表單的 label 選項自定義顯示文字:
class ResumeForm(forms.ModelForm):
    class Meta:
        model = Resume
        fields = '__all__'
        labels = {
            'name': '姓名',
            'skill': '技能',
        }
  1. <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': '輸入描述'}),
        }
  1. 標籤類型選擇(如 <input><textarea>
    • 基於字段類型:例如,CharField 使用 <input type="text">TextField 使用 <textarea>
    • 可自定義:通過 widgets,可以更改默認標籤的類型和屬性。

小結

使用 ModelForm 的優勢:

  1. 簡化代碼
    • 無需手動綁定和驗證資料,減少重複性代碼。
  2. 數據驗證
    • 自帶數據驗證功能,確保資料完整性。
  3. 易於維護
    • 表單欄位定義集中在 ModelForm 中,修改方便。
  4. 模板渲染簡化
    • 使用 as_p 等方法快速渲染表單。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *