回上方

使用者表單

在前面的章節中使用的 Django 內建的後臺管理界面對自訂的資料模型 Poll 以及 Option 進行操作。但後臺管理界面較偏向一般化的操作,可能無法滿足我們對操作流程的要求,此時可以透過使用者表單在頁面上收集使用者的輸入值,藉此對資料模型所對應的資料表進行新增、刪除、修改等操作。

舉例來說,想在檢視投票主題的頁面對該主題的投票選項進行操作,例如新增一個選項時,只需要填寫選項文字就好,其 poll_id 欄位就由程式自動判斷填入,這樣的需求就沒法直接透過建建的管理後臺達成。

投票主題

由於這個範例的資料比較單純,我們可以透過 Django 的通用編輯視圖(Generic editing views)類別來進行資料的「增(Create)」、「刪(Delete)」、「修(Update)」的動作。

修改資料模型

開啟 poll/default/models.py,修改 Poll 資料模型:

class Poll(models.Model): # 投票主題文字,至多 200 字 subject = models.CharField(max_length=200, verbose_name='主題') # 投票建立日期,在建立時若未指定,則自動填入建立時的時間 date_created = models.DateField(auto_now_add=True) def __str__(self): return str(self.id) + ")" + self.subject

新增資料(Create)

路徑

修改 poll/default/urls.py,增加路徑規則:

path('option/<int:pk>/', views.PollVote.as_view()), path('poll/create/', views.PollCreate.as_view()),
  • 新增第 8 行,讓 poll/create/ 對應到新增投票主題的視圖。

視圖

開啟 poll/default/views.py,增加以下程式碼:

from django.views.generic.edit import CreateView, UpdateView, DeleteView from .models import *
  • 新增第 3 行,從 django.views.generic.edit 引用 CreateView, UpdateView 以及 DeleteView
# 新增投票主題 class PollCreate(CreateView): model = Poll fields = ['subject'] # 指定要顯示的欄位 success_url = '/poll/' # 成功新增後要導向的路徑 template_name = 'general_form.html' # 要使用的頁面範本

範本

新增通用表單範本檔案 poll/default/templates/general_form.html

{% extends "base.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="送出"> </form> {% endblock %}

:bulb: CSRF token 是什麼?

CSRF 是一種 Web 上的攻擊手法,全稱是 Cross Site Request Forgery,跨站請求偽造,簡單的說就是在別的網站上偽裝為本網站使用者的編輯操作,造成資料被竄改、刪除…等。網站透過 CSRF Token 的使用,可以檢驗請求的真實性,以確保網站的資料安全。細節可參考以下網站說明:

讓我們來談談 CSRF | TechBridge 技術共筆部落格
https://blog.techbridge.cc/2017/02/25/csrf-introduction/

修改資料(Update)

網址

開啟 poll/default/urls.py,增加以下路徑規則:

path('poll/<int:pk>/update/', views.PollUpdate.as_view()),
  • poll/整數/update/ 的請求交由 PollUpdate 處理,並將路徑中的整數部份擷取出來當成參數,命名為 pk

:bulb: 參數 pk 的作用
Django 的通用視圖,若操作對象為某特定一筆記錄的話,它會透過 pk 這個參數來搜尋該視圖所參考的資料模型,將主鍵(Primary Key,未特別定義的話,Django 會自動建立一個 id 欄位,並將其設為主鍵,用以識別每筆記錄)值與 pk 相等的那筆記錄找出來。

註:pk 就是 Primary Key 的簡寫

視圖

開啟 poll/default/views.py,增加以下程式碼:

# 修改投票主題 class PollUpdate(UpdateView): model = Poll fields = ['subject'] # 指定要顯示的欄位 success_url = '/poll/' # 成功新增後要導向的路徑 template_name = 'general_form.html' # 要使用的頁面範本

範本

與新增投票主題共用頁面範本 general_form.html,不另行設置。

刪除資料(Delete)

網址

開啟 poll/default/urls.py,增加以下路徑規則:

path('poll/<int:pk>/delete/', views.PollDelete.as_view()),

視圖

開啟 poll/default/views.py,增加以下程式碼:

# 刪除投票主題 class PollDelete(DeleteView): model = Poll success_url = '/poll/' template_name = "confirm_delete.html"

範本

新增確認刪除頁面範本檔案 poll/default/templates/confirm_delete.html

{% extends "base.html" %} {% block content %} <h1>刪除紀錄</h1> <form action="" method="post"> <p>確定要刪除 {{ object }} 這筆紀錄嗎?</p> {% csrf_token %} <input type="submit" value="是的,我要刪除"> </form> {% endblock %}

更新投票主題列表範本

開啟 poll/default/templates/default/poll_list.html,修改為以下程式碼。

{% extends "base.html" %} {% block content %} <h1>投票主題</h1> <a href="create/">新增投票主題</a> <ul> {% for poll in poll_list %} <li> {{ poll.date_created }} <a href="{{ poll.id }}/">{{ poll.subject }}</a> | <a href="{{ poll.id }}/update/">修改</a> | <a href="{{ poll.id }}/delete/">刪除</a> </li> {% endfor %} </ul> {% endblock %}

投票選項

新增資料(Create)

路徑

開啟 poll/default/urls.py ,增加以下路徑規則:

path('option/create/<int:pid>/', views.OptionCreate.as_view()),

視圖

開啟 poll/default/views.py,增加以下程式碼:

# 新增投票選項 class OptionCreate(CreateView): model = Option fields = ['title'] template_name = 'general_form.html' # 成功新增選項後要導向其所屬的投票主題檢視頁面 def get_success_url(self): return '/poll/'+str(self.kwargs['pid'])+'/' # 表單驗證,在此填上選項所屬的投票主題 id def form_valid(self, form): form.instance.poll_id = self.kwargs['pid'] return super().form_valid(form)

範本

PollCreate, PollUpdate 共用 general_form.html 即可,不另行定義頁面範本。

因為在 models.py 裡定義 Option 的資料模型時,並未指定 title 這個欄位的標籤文字,所以預設會以欄位名稱當做輸入標籤文字。若想修改的話,可以在定義 title 欄位型態時,如下方程式碼區塊的第 18 行,額外指定 verbose_name 屬性即可,例:

class Option(models.Model): # 此選項屬於哪一個投票 poll_id = models.IntegerField() # 選項文字 title = models.CharField(max_length=200, verbose_name='投票選項') # 此選項被投票數 count = models.IntegerField(default=0)

有另一種指定方式,在欄位型態的第一個參數直接放上標籤文字也可以,如:

title = models.CharField('投票選項', max_length=200)

修改資料(Update)

網址

開啟 poll/default/urls.py,增加以下路徑規則:

path('option/<int:pk>/update/', views.OptionUpdate.as_view()),

視圖

開啟 poll/default/views.py,增加以下程式碼:

# 修改投票選項 class OptionUpdate(UpdateView): model = Option fields = ['title'] template_name = 'general_form.html' # 修改成功後返回其所屬投票主題檢視頁面 def get_success_url(self): return '/poll/'+str(self.object.poll_id)+'/'

範本

共用 general_form.html 即可,不需另行定義。

刪除資料(Delete)

網址

開啟 poll/default/urls.py,增加以下路徑規則:

path('option/<int:pk>/delete/', views.OptionDelete.as_view()),

另外,請修改投票主題檢視的路徑規則如下:

path('poll/<int:pk>/', views.PollDetail.as_view(), name='poll_view'),

視圖

修改 poll/default/views.py

from .models import * from django.urls import reverse

另外,新增以下內容:

# 刪除投票選項 class OptionDelete(DeleteView): model = Option template_name = 'confirm_delete.html' # 刪除成功後返回其所屬投票主題檢視頁面 def get_success_url(self): return reverse('poll_view', kwargs={'pk': self.object.poll_id})

:bulb: 為什麼要用 reverse 來產生路徑?

其實這種反向產生網址路徑的方法才是官方比較建議的做法。

之前所示範的方式是自行組合出目的路徑,這樣比較直覺,但缺點是,日後若需調整路徑規則,就必須將視圖中所有相關的路徑全部修正到才行。

反向產生網址路徑的做法,必須先為路徑規則命名,在視圖中只需指定要透過哪條規則來產生路徑,並提供所需參數即可,剩下的事情 reverse 會處理。例如:要將原本的 poll/<int:pk>/ 改為 poll/view/<int:pk>,僅需修改 urls.py 即可,views.py 就不需要做任何調整。如此一來,跟路徑相關的操作就可以全部集中在 urls.py 處理。

範本

PollDelete 共用 confirm_delete.html 即可,不另行定義頁面範本。

更新投票主題檢視頁面範本

開啟 poll/default/templates/default/poll_detail.html,修改為以下程式碼。

{% extends "base.html" %} {% block content %} <h1>{{ poll.subject }}</h1> <a href="/option/create/{{ poll.id }}">新增選項</a> <div>小提示!直接按選項文字就可以投票囉!</div> <ul> {% for option in options %} <li> <a href="/option/{{ option.id }}/">{{ option.title }}</a> : {{ option.count }} 票 | <a href="/option/{{ option.id }}/update/">修改</a> | <a href="/option/{{ option.id }}/delete/">刪除</a> </li> {% endfor %} </ul> {% endblock %}

根路徑重新導向

先前在測試網站的時候,是使用 http://your-host-address/poll/ 來存取服務。但是若僅以主機名稱 http://your-host-address/ 來存取網站,會看到如下圖 Page not found 的錯誤訊息。

(註:your-host-address 請自行置換為執行專案的主機位址)

錯誤訊息的頁面上列出目前網站已定義的路徑規則,目前已定義的 10 條規則中,並不包含根路徑(也就是網站主機位址後不加其他路徑)的定義,因為找不到相對應的處理函式,所以產生了錯誤訊息。

在這個範例最主要的功能就是投票,因此,我們希望僅以主機名稱來存取網站時,會直接顯示投票主題列表,所以必須將根路徑重新導向到 poll/ 來顯示投票主題列表。

修改 poll/default/urls.py

from . import views from django.views.generic import RedirectView urlpatterns = [ path('', RedirectView.as_view(url='poll/')), path('poll/', views.PollList.as_view()),

完成修改後,接下來僅以主機名稱 http://your-host-address/ 來存取網站時,會被重新導向至 http://your-host-address/poll/,顯示投票主題列表頁面。