Photo by Daniel Fazio on Unsplash
在現今許多網站中,多數都會有使用到表單的時候,舉例來說,在註冊會員時,就是使用表單的方式來填寫基本資料,進而儲存會員資料至資料庫中。
Django網站框架,提供了許多表單的開發方式,其中最常使用的為ModelForm(資料模型表單),顧名思義,就是利用規劃資料庫時,所建立的資料模型類別(Model),來開發表單,能夠快速建置基本的CRUD(Create-Read-Update-Delete)表單應用程式。
Django網站框架,提供了許多表單的開發方式,其中最常使用的為ModelForm(資料模型表單),顧名思義,就是利用規劃資料庫時,所建立的資料模型類別(Model),來開發表單,能夠快速建置基本的CRUD(Create-Read-Update-Delete)表單應用程式。
本文將實際開發基本的記帳網站,來說明Django ModelForm的開發方式,其中的重點包含:
- Django ModelForm建置
- Django ModelForm初始化
- Django ModelForm新增資料操作
- Django Url Reference網址參考
- Django ModelForm修改資料操作
- Django ModelForm刪除資料操作
一、Django ModelForm建置
在開發Django網站的ModelForm(資料模型表單)前,需事先建立網站的資料模型(Model),其中的建立方式可以參考Django Migration(資料遷移)的重要觀念文章。
而本文所要建置的記帳網站資料模型(models.py),如下範例:
而本文所要建置的記帳網站資料模型(models.py),如下範例:
from django.db import models
class Expense(models.Model):
name = models.CharField(max_length=255) #花費項目
price = models.IntegerField() #金額
Django ModelForm(資料模型表單)就是依據models.py中的資料模型類別,來建立網站表單。首先,在Django的應用程式(APP)下新增一個forms.py檔案,如下圖:
接著,在forms.py檔案中自定類別,繼承自ModelForm類別,並且在其中設定表單所要綁定的資料模型名稱,如下範例:
from django import forms
from .models import Expense
class ExpenseModelForm(forms.ModelForm):
class Meta:
model = Expense
而要指定在網站上所要顯示的表單欄位,可以透過fields屬性來進行設定,如下範例:
from django import forms
from .models import Expense
class ExpenseModelForm(forms.ModelForm):
class Meta:
model = Expense
fields = ('name', 'price')
如果要顯示資料模型(Model)中的所有欄位,fields屬性的設定可以簡寫為__all__,如下範例:
from django import forms
from .models import Expense
class ExpenseModelForm(forms.ModelForm):
class Meta:
model = Expense
fields = '__all__'
另外,Django ModelForm也提供了widgets屬性,用來客製化表單的顯示外觀,這邊套用Bootstrap的表單CSS類別為例:
from django import forms
from .models import Expense
class ExpenseModelForm(forms.ModelForm):
class Meta:
model = Expense
fields = '__all__'
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'price': forms.NumberInput(attrs={'class': 'form-control'})
}
由於在資料模型(Model)中,都是定義英文的屬性(Attribute),所以表單欄位的標題也會顯示英文名稱,如果想要進行修改,可以透過labels屬性來設定,如下範例:
from django import forms
from .models import Expense
class ExpenseModelForm(forms.ModelForm):
class Meta:
model = Expense
fields = '__all__'
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'price': forms.NumberInput(attrs={'class': 'form-control'})
}
labels = {
'name': '花費項目',
'price': '金額'
}
二、Django ModelForm初始化
一般情況下,瀏覽器請求網址的方法(method)有兩種,分別為GET及POST,當沒有特別指定請求的方法為POST時,預設皆為GET方法。所以想要在瀏覽器請求Django應用程式(APP)時,能夠顯示一個空白的表單讓使用者填寫,就可以在views.py中,透過以下的程式碼來達成:
from django.shortcuts import render
from .forms import ExpenseModelForm
def index(request):
form = ExpenseModelForm()
context = {
'form': form
}
return render(request, 'expenses/index.html', context)
由於瀏覽器預設使用GET方法請求網址,所以在檢視函式(View Function)中,會執行初始化Django ModelForm表單,並且傳送至Django Template(樣板)中。
接下來在對應的index.html Django Template(樣板)中,即可透過以下的語法,顯示在forms.py中所設定的表單樣式:
接下來在對應的index.html Django Template(樣板)中,即可透過以下的語法,顯示在forms.py中所設定的表單樣式:
{% endblock %} 執行結果{% extends 'base.html' %} {% block content %}
範例程式碼中,在<form>標籤的地方,action為表單要送往的目標網址,預設為目前的網址。另一個method屬性就是表單資料送出時,所使用的請求方法。而其中的csrf_token為Django的表單安全驗證機制,防止有心人士透過表單,傳送破壞性的資料到網站來進行攻擊,是Django表單的安全防護機制。
除此之外,如果想要彈性的放置欄位標題及輸入框,來修改排版的方式,則可以在Template(樣板)中,透過form物件單獨存取label欄位標題及輸入框,如下範例:
除此之外,如果想要彈性的放置欄位標題及輸入框,來修改排版的方式,則可以在Template(樣板)中,透過form物件單獨存取label欄位標題及輸入框,如下範例:
{% endblock %} 執行結果{% extends 'base.html' %} {% block content %}
三、Django ModelForm新增資料操作
在儲存送出表單資料時,Django會使用POST方法,將表單資料傳送至目前的網址,這時候在views.py中,就可以透過以下的程式碼,儲存表單資料至資料庫中:
from django.shortcuts import render, redirect
from .models import Expense
from .forms import ExpenseModelForm
def index(request):
expenses = Expense.objects.all() # 查詢所有資料
form = ExpenseModelForm()
if request.method == "POST":
form = ExpenseModelForm(request.POST)
if form.is_valid():
form.save()
return redirect("/expenses")
context = {
'expenses': expenses,
'form': form
}
return render(request, 'expenses/index.html', context)
由於為了在畫面展示儲存資料後,能將資料顯示在表單的下方,所以在檢視函式(View Function)中增加所有資料的查詢。
在剛剛的index.html Template(樣板)中,指定了表單資料送出的請求方法(method)為POST,所以在檢視函式(View Function)中,就會進入if區塊,利用請求方法所帶過來的表單資料,建立ModelForm物件(form),這時候form物件擁有了使用者在表單所填寫的資料,接著利用is_valid()方法進行驗證,如果通過,就可以直接儲存,最後,重新導向到目前的網址。
回到index.html Template(樣板),在表單下方增加顯示所有資料的查詢結果,以及修改和刪除按鈕,如下範例:
在剛剛的index.html Template(樣板)中,指定了表單資料送出的請求方法(method)為POST,所以在檢視函式(View Function)中,就會進入if區塊,利用請求方法所帶過來的表單資料,建立ModelForm物件(form),這時候form物件擁有了使用者在表單所填寫的資料,接著利用is_valid()方法進行驗證,如果通過,就可以直接儲存,最後,重新導向到目前的網址。
回到index.html Template(樣板),在表單下方增加顯示所有資料的查詢結果,以及修改和刪除按鈕,如下範例:
{% extends 'base.html' %} {% block content %} <form action="" method="POST"> {% csrf_token %} <div class="form-row"> <div class="form-group col-md-6"> <label>{{ form.name.label }}</label> {{ form.name }} </div> <div class="form-group col-md-4"> <label>{{ form.price.label }}</label> {{ form.price }} </div> <div class="form-group col-md-2"> <input type="submit" class="btn btn-success" style="margin-top:30px;" value="儲存"> </div> </div> </form> <table class="table table-bordered table-striped"> <thead class="thead-dark"> <tr> <th>花費項目</th> <th>金額</th> <th></th> </tr> </thead> <tbody> {% for expense in expenses %} <tr> <td>{{ expense.name }}</td> <td>{{ expense.price }}</td> <td> <a href="#" class="btn btn-info">修改</a> <a href="#" class="btn btn-danger">刪除</a> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}執行結果
四、Django Url Reference網址參考
要修改資料時,一定要知道該筆資料的主鍵(PK),並且透過網址傳送至Django的檢視函式(View Function)中,這時候就需要在應用程式(APP)下的urls.py中增加網址設定,如下範例:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='Index'),
path('update/<str:pk>', views.update, name='Update')
]
在修改的網址的參數,首先需定義它的資料型態,接著在冒號後面,進行參數的命名。
網址設定完成後,回到index.html Template(樣板),在表單下方的顯示資料表格中,將修改按鈕的網址進行修改,如下範例:
網址設定完成後,回到index.html Template(樣板),在表單下方的顯示資料表格中,將修改按鈕的網址進行修改,如下範例:
<table class="table table-bordered table-striped"> <thead class="thead-dark"> <tr> <th>花費項目</th> <th>金額</th> <th></th> </tr> </thead> <tbody> {% for expense in expenses %} <tr> <td>{{ expense.name }}</td> <td>{{ expense.price }}</td> <td> <a href="{% url 'Update' expense.id %}" class="btn btn-info">修改</a> <a href="#" class="btn btn-danger">刪除</a> </td> </tr> {% endfor %} </tbody> </table>
在修改按鈕的網址中,利用Django的url樣板語法,指定網址的名稱及該筆資料的主鍵,這樣Django就會依據此名稱,在應用程式(APP)的urls.py中,產生相對應的連結,並且由對應的檢視函式(View Function)來進行請求的處理。
五、Django ModelForm修改資料操作
修改按鈕的網址參考設定完成後,在views.py中,新增一個update檢視函式(View Function),並且利用剛剛在urls.py中所命名的參數名稱,取得表單資料的主鍵(id),這時候就能夠透過get()方法取得該筆表單資料。
接著指定Django ModelForm的instance關鍵字參數(Keyword Argument)為取得的表單資料物件,來初始化表單,傳送至Template(樣板)中,如下範例:
接著指定Django ModelForm的instance關鍵字參數(Keyword Argument)為取得的表單資料物件,來初始化表單,傳送至Template(樣板)中,如下範例:
def update(request, pk):
expense = Expense.objects.get(id=pk)
form = ExpenseModelForm(instance=expense)
context = {
'form': form
}
return render(request, 'expenses/update.html', context)
最後,建立修改畫面的Django Template(樣板),如下圖:
同樣利用剛剛所介紹的表單語法來建立Django ModelForm表單,如下範例:
{% endblock %} 假設這時候點選index.html Template(樣板)中,雞腿飯的修改按鈕,就可以看到Django所產生的網址及執行結果:{% extends 'base.html' %} {% block content %}
可以看到成功的透過網址傳遞該筆資料的主鍵至檢視函式(View Function),並且在表單顯示資料庫中的資料。
接下來,在點擊更新按鈕時,同樣利用POST請求方法,將修改的資料傳送至目前的網址,所以在相對的 update檢視函式(View Function)中,判斷如果為POST方法,就把取得的修改資料及資料庫中的該筆資料傳入Django ModelForm中,來建立form物件,如果修改的表單資料驗證通過,則進行修改的動作,如下範例:
接下來,在點擊更新按鈕時,同樣利用POST請求方法,將修改的資料傳送至目前的網址,所以在相對的 update檢視函式(View Function)中,判斷如果為POST方法,就把取得的修改資料及資料庫中的該筆資料傳入Django ModelForm中,來建立form物件,如果修改的表單資料驗證通過,則進行修改的動作,如下範例:
def update(request, pk):
expense = Expense.objects.get(id=pk)
form = ExpenseModelForm(instance=expense)
if request.method == 'POST':
form = ExpenseModelForm(request.POST, instance=expense)
if form.is_valid():
form.save()
return redirect('/expenses')
context = {
'form': form
}
return render(request, 'expenses/update.html', context)
六、Django ModelForm刪除資料操作
首先,開啟urls.py檔案,新增刪除按鈕的網址,如下範例:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='Index'),
path('update/<str:pk>', views.update, name='Update'),
path('delete/<str:pk>', views.delete, name='Delete')
]
接著,開啟index.html Template(樣板),設定刪除按鈕的網址,如下範例:
<table class="table table-bordered table-striped"> <thead class="thead-dark"> <tr> <th>花費項目</th> <th>金額</th> <th></th> </tr> </thead> <tbody> {% for expense in expenses %} <tr> <td>{{ expense.name }}</td> <td>{{ expense.price }}</td> <td> <a href="{% url 'Update' expense.id %}" class="btn btn-info">修改</a> <a href="{% url 'Delete' expense.id %}" class="btn btn-danger">刪除</a> </td> </tr> {% endfor %} </tbody> </table>
2020/03/28補充說明
完成設定後,在views.py中,建立刪除的檢視函式(View Function),同樣先利用get()方法取得該筆資料後,傳遞至刪除的Template(樣板),讓使用者確認是否要進行刪除的動作,如下範例:
delete.html Template(樣板)中,首先,利用檢視函式(View Function)所傳過來的expense物件,顯示花費項目的名稱。接著,將確定及取消按鈕放置在表單中,讓「確定」按鈕能夠使用POST方法進行請求,如下範例:
假設想要刪除雞腿飯這筆花費項目,點擊「刪除」按鈕後,則可以看到如下圖的執行結果:
最後,回到刪除的檢視函式(View Function),如果請求的方法是POST,也就是按下了「確定」按鈕,則呼叫delete()方法進行刪除的動作,完成後回到首頁,如下範例:
完成設定後,在views.py中,建立刪除的檢視函式(View Function),同樣先利用get()方法取得該筆資料後,傳遞至刪除的Template(樣板),讓使用者確認是否要進行刪除的動作,如下範例:
def delete(request, pk):
expense = Expense.objects.get(id=pk)
context = {
'expense': expense
}
return render(request, 'expenses/delete.html', context)
在Django應用程式(APP)下,新增刪除的Template(樣板),如下圖:{% endblock %} 範例中的「取消」按鈕,由於它的型態(type)不是送出(submit),所以會使用GET請求方法回到首頁。{% extends 'base.html' %} {% block content %} 是否確定刪除{{ expense.name }}?
假設想要刪除雞腿飯這筆花費項目,點擊「刪除」按鈕後,則可以看到如下圖的執行結果:
最後,回到刪除的檢視函式(View Function),如果請求的方法是POST,也就是按下了「確定」按鈕,則呼叫delete()方法進行刪除的動作,完成後回到首頁,如下範例:
def delete(request, pk):
expense = Expense.objects.get(id=pk)
if request.method == "POST":
expense.delete()
return redirect('/expenses')
context = {
'expense': expense
}
return render(request, 'expenses/delete.html', context)
七、小結
以上就是利用Django ModelForm來快速的建立一個CRUD的應用程式,希望透過本文的實作,可以瞭解Django ModelForm的使用方式。如果在實作的過程中有碰到任何問題,歡迎留言提問,我將盡力為各位解答。
如果您喜歡我的文章,請幫我按五下Like(使用Google或Facebook帳號免費註冊),支持我創作教學文章,回饋由LikeCoin基金會出資,完全不會花到錢,感謝大家。
如果您喜歡我的文章,請幫我按五下Like(使用Google或Facebook帳號免費註冊),支持我創作教學文章,回饋由LikeCoin基金會出資,完全不會花到錢,感謝大家。
GitHub網址:https://github.com/mikeku1116/django-urexpenses
有想要看的教學內容嗎?歡迎利用以下的Google表單讓我知道,將有機會成為教學文章,分享給大家😊
你可能有興趣的文章
請問有git嗎??
回覆刪除已補充在文章最後,GitHub網址為https://github.com/mikeku1116/django-urexpenses
刪除if form.is_valid: 少了( )
回覆刪除:)
pika您好:
刪除已完成修改,感謝您的提醒 :)
您好
回覆刪除在第一次執行解果的地方 按鈕沒有顯示 儲存 index.html 的value那邊可能要補一下
Thank you~
已完成修改,感謝您的提醒 :)
刪除MIKE大您好,我照著文中在實作UPDATE部分時有遇到以下問題
回覆刪除NoReverseMatch at /expenses/
Reverse for 'Update' with arguments '(1,)' not found. 1 pattern(s) tried: ['expenses/update/<str:pk>$']
想說是不是update.html中的問題,或是哪邊我沒有顧慮到?
您好,檢查一下urls.py檔案中是否有加上以下的網址:
刪除urlpatterns = [
path('', views.index, name='Index'),
path('update/<str:pk>', views.update, name='Update')
]
特別注意「/」為半型,以及views.py檔案中的update檢視函式(view function)是否有加上參數,如下範例:
def update(request, pk):
...
希望有解決您的問題 :)
<>
刪除urlpatterns = [
path('', views.index, name='Index'),
path('update/<str:pk>', views.update, name='Update'),
path('update/<str:pk>', views.delete, name='Delete')
]
<>
def update(request, pk):
expense = Expense.objects.get(id=pk)
form = ExpenseModelForm(instance=expense)
context = {
'form': form
}
return render(request, 'expenses/update.html', context)
但這樣還是沒辦法成功..
想問M大,update.html中的
{{ form }}
語法就是這樣嗎? 還是您有省略步驟?
XIAO您好,您的urlpatterns有兩個update的path,如下:
刪除path('update/<str:pk>', views.update, name='Update'),
path('update/<str:pk>', views.delete, name='Delete')
第二個path應該是delete/<str:pk>
剛剛有修改了
刪除但目前還是顯示
NoReverseMatch at /expenses/
Reverse for 'Update' with arguments '(1,)' not found. 1 pattern(s) tried: ['expenses/update/<str:pk>$']
它是回報
return render(request, 'expenses/index.html', context)
處有誤
我猜想會不會是我的相關內容中 Expense 和 expenses有錯用?
刪除@XIAO 我的實作發現這邊的 <str:pk> 大於、小於 用的是全形的,需要改成半形。
刪除-->
其他的部分,按照M大的步驟,齊全了就出現了。
感謝Jiang的回覆,因為部落格編碼的關係,為了正確顯示,才使用了全形,再麻煩XIAO檢查一下,謝謝 :)
刪除謝謝@Jiang和M的回覆,這樣一來就可以了!
刪除作者已經移除這則留言。
回覆刪除您好
回覆刪除我參考你的update功能,練習寫了update的功能,但是在submit送出時,回去驗證model欄位資料時,就報錯了;試了很久還是想不懂為什麼會報錯說我找的資料欄位不存在(DoesNotExist;matching query does not exist)
謝謝
Mike您好
回覆刪除非常謝謝您的教學,若我form想要進階一點用到foreingKey,
那我該朝甚麼關鍵字下手呢?