範例:投票選項

建立資料模型

修改 poll/default/models.py,增加第 14 - 23 行

from django.db import models # Create your models here. 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 class Option(models.Model): # 此選項屬於哪一個投票 poll_id = models.IntegerField() # 選項文字 title = models.CharField(max_length=200) # 此選項被投票數 count = models.IntegerField(default=0) def __str__(self): return str(self.id) + ")" + self.title

執行以下指令套用到資料庫:

python manage.py makemigrations python manage.py migrate

將自訂資料模型也納入內建管理後臺

修改 poll/default/admin.py,新增第 6 行,將 Option 也加入後臺管理的範圍:

from django.contrib import admin from .models import * # Register your models here. admin.site.register(Poll) admin.site.register(Option)

修改完成後重新載入管理介面,應該會看到上面出現 PollOption 的管理界面。

接著為投票主題「趕快來投票決定班遊地點!!!」,新增投票選項,記得要指定 Poll id 的值為 1 (因為該投票主題的 id 值為 1)

視圖、網址、範本

投票主題檢視

網址

修改檔案 poll/default/urls.py,新增第 9 行路徑規則:

urlpatterns = [ path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), ]

視圖

修改 poll/default/views.py

from django.shortcuts import render from django.views.generic import ListView, DetailView from .models import *
# 投票主題檢視 class PollDetail(DetailView): model = Poll # 取得額外資料供頁面範本顯示 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) options = Option.objects.filter(poll_id=self.kwargs['pk']) context['options'] = options return context

範本

新增投票選項檢視頁面範本檔案 poll/default/templates/default/poll_detail.html

{% extends "base.html" %} {% block content %} <h1>{{ poll.subject }}</h1> <ul> {% for option in options %} <li>{{ option.title }}</li> {% endfor %} </ul> {% endblock %}

啟動網站,在網址列輸入 http://localhost/poll/1/ 會出現以下頁面:

投票

這個範例的投票動作很簡單,不記錄投票的其他相關資訊,例:投票者、投票時間…等,僅單純增加票數而已。因此之前在定義資料模型的時候僅透過 Option 中的 count 欄位來記錄選項被投了幾次。

投票的流程簡化如下:在檢視投票主題時,可以直接點選該主題下的投票選項進行投票,投票後自動返回所屬投票主題檢視頁面。

網址

修改 poll/default/urls.py,新增第 10 行

urlpatterns = [ path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), path('option/<int:pk>/', views.PollVote.as_view()), ]

視圖

修改 poll/default/views.py

from django.shortcuts import render from django.views.generic import ListView, DetailView, RedirectView from .models import *
# 投票 class PollVote(RedirectView): def get_redirect_url(self, *args, **kwargs): option = Option.objects.get(id=self.kwargs['pk']) option.count += 1 # 將選項的票數+1 option.save() # 儲存至資料庫 return "/poll/"+str(option.poll_id)+"/"

範本

在增加了票數之後,就直接轉址回所屬的投票主題檢視頁面,所以這個功能不需要定義頁面範本。

修改投票選項檢視頁面範本檔案 poll/default/templates/default/poll_detail.html

{% extends "base.html" %} {% block content %} <h1>{{ poll.subject }}</h1> <ul> {% for option in options %} <li><a href="/option/{{ option.id }}">{{ option.title }}</a>--{{ option.count }}</li> {% endfor %} </ul> {% endblock %}

使用者表單

投票選項

新增資料(Create)

路徑

開啟 poll/default/urls.py ,增加第 11 行路徑規則:

urlpatterns = [ path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), path('option/<int:pk>/', views.PollVote.as_view()), path('option/create/<int:pid>/', views.OptionCreate.as_view()), ]
視圖

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

# 新增投票選項 class OptionCreate(CreateView): model = Option fields = ['title'] template_name = '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 共用 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,增加第 12 行路徑規則:

urlpatterns = [ path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), path('option/<int:pk>/', views.PollVote.as_view()), path('option/create/<int:pid>/', views.OptionCreate.as_view()), path('option/<int:pk>/update/', views.OptionUpdate.as_view()), ]
視圖

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

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

刪除資料(Delete)

網址

開啟 poll/default/urls.py,增加第 13 行路徑規則:

urlpatterns = [ path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), path('option/<int:pk>/', views.PollVote.as_view()), path('option/create/<int:pid>/', views.OptionCreate.as_view()), path('option/<int:pk>/update/', views.OptionUpdate.as_view()), path('option/<int:pk>/delete/', views.OptionDelete.as_view()), ]

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

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

修改 poll/default/views.py,新增第 5 行

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://localhost/poll/ 來存取服務。但是若僅以主機名稱 http://localhost/ 來存取網站,會看到如下圖 Page not found 的錯誤訊息。

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

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

修改 poll/default/urls.py,新增第 3, 6 行

from . import views from django.views.generic import RedirectView urlpatterns = [ path('', RedirectView.as_view(url='poll/')), path('poll/', views.PollList.as_view()), path('poll/create/', views.PollCreate.as_view()), path('poll/<int:pk>/update/', views.PollUpdate.as_view()), path('poll/<int:pk>/delete/', views.PollDelete.as_view()), path('poll/<int:pk>/', views.PollDetail.as_view()), path('option/<int:pk>/', views.PollVote.as_view()), path('option/create/<int:pid>/', views.OptionCreate.as_view()), path('option/<int:pk>/update/', views.OptionUpdate.as_view()), path('option/<int:pk>/delete/', views.OptionDelete.as_view()), ]

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