使用者表單
在前面的章節中使用的 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 %}
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
參數 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})
為什麼要用 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/
,顯示投票主題列表頁面。