案例:線上投票
第 4 至 6 課已經建立了一個簡單的線上投票網站,看起來有點陽春,接下來試著將輸出的頁面稍加美化一下。
如果對於 CSS 已經很熟悉了,可以直接自己撰寫樣式表。在這個案例中,我們直接引入市面上很多人使用的 CSS 框架 – Bootstrap
來美化網站的外觀。
Bootstrap 官網:https://getbootstrap.com/
引用 Bootstrap 框架
在專案中加入 Bootstrap 框架有好幾種方式,可以將至官網下載後,將其當成專案靜態檔案的一部份。最簡便的方式則是透過網路上的 CDN 服務,直接引用所需要的檔案。以下兩種方式請選擇一種套用即可。
1. 透過 CDN 服務引用
參考 Bootstrap 官網上的說明,將 poll/default/templates/base.html
修改如下:
<!doctype html>
<html lang="zh-hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<title>線上投票</title>
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
</body>
</html>
2. 下載為專案的靜態檔案
指定靜態檔案存放路徑
先修改專案設定檔 poll/poll/settings.py
,指定靜態檔案存放的資料夾,新增第 123 - 125 行:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
接著在專案資料夾下建立 static
資料夾,建立完後,整個專案的目錄結構如下:
poll/
├── default/
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── templates/
│ │ ├── default/
│ │ │ ├── poll_detail.html
│ │ │ └── poll_list.html
│ │ ├── base.html
│ │ ├── confirm_delete.html
│ │ └── general_form.html
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── poll/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── static/
├── db.sqlite3
├── manage.py*
└── README.md
將 Bootstrap 相關檔案放到靜態檔案存放路徑之下
-
到 Bootstrap 官網:https://getbootstrap.com/
點選「Download
」按鈕進入下載頁面。
-
點按下載頁面的 Compiled CSS and JS 下方的「Download
」按鈕
-
將下載的壓縮檔解開後,把得到的 css
與 js
兩個資料夾複製到專案的靜態檔案存放路徑(static
)下,完成後整個專案的目錄結構如下:
poll/
├── default/
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── templates/
│ │ ├── default/
│ │ │ ├── poll_detail.html
│ │ │ └── poll_list.html
│ │ ├── base.html
│ │ ├── confirm_delete.html
│ │ └── general_form.html
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── poll/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── static/
│ ├── css/
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css.map
│ │ ├── bootstrap-grid.css
│ │ ├── bootstrap-grid.css.map
│ │ ├── bootstrap-grid.min.css
│ │ ├── bootstrap-grid.min.css.map
│ │ ├── bootstrap.min.css
│ │ ├── bootstrap.min.css.map
│ │ ├── bootstrap-reboot.css
│ │ ├── bootstrap-reboot.css.map
│ │ ├── bootstrap-reboot.min.css
│ │ └── bootstrap-reboot.min.css.map
│ └── js/
│ ├── bootstrap.bundle.js
│ ├── bootstrap.bundle.js.map
│ ├── bootstrap.bundle.min.js
│ ├── bootstrap.bundle.min.js.map
│ ├── bootstrap.js
│ ├── bootstrap.js.map
│ ├── bootstrap.min.js
│ └── bootstrap.min.js.map
├── db.sqlite3
├── manage.py*
└── README.md
修改網站基底頁面範本
將網站基底頁面範本 poll/default/templates/base.html
修改如下:
<!doctype html>
<html lang="zh-hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<title>線上投票</title>
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script>
</body>
</html>
|
引用 Bootstrap 框架前
|
|
引用 Bootstrap 框架後
|
上面兩張擷圖顯示了引用 Bootstrap 框架前後的差異:
<H1></H1>
的字體大小
- 連結文字的顏色不一樣了,另外,連結的底線也消失了
- 顯示的內容左邊多了留白的空間,讓版面看起來比較不那麼擁擠
美化頁面上的元件
使用 CSS 框架的好處是,這些框架已將事先定義好一整套的樣式規則,我們僅需要將網頁上的元件套用欲使用的 CSS 類別,就能輕鬆地增進網頁的美觀,並維持網站頁面外觀的一致性。
若想把問題列表頁面上的「新增投票主題」、「修改」、「刪除」等連結改得像是按鈕的外觀的話,可以將這些連結套用 btn
以及與其語意相符的按鈕樣式類別即可。預先定義的按鈕樣式的類別如下表:
按鈕樣式 |
外觀範例 |
按鈕樣式 |
外觀範例 |
按鈕樣式 |
外觀範例 |
btn-primary |
|
btn-secondary |
|
btn-success |
|
btn-danger |
|
btn-warning |
|
btn-info |
|
btn-light |
|
btn-dark |
|
btn-link |
|
另外,除了預設尺寸外,也可以額外套用 btn-lg
或 btn-sm
來指定按鈕的尺寸為「大」或「小」。
依上述說明修改 poll/default/templates/default/poll_list.html
,將「新增投票主題」、「修改」、「刪除」等連結套用 CSS 類別:
{% extends "base.html" %}
{% block content %}
<h1>投票主題</h1>
<p><a href="create/" class="btn btn-primary">新增投票主題</a></p>
<ul>
{% for poll in poll_list %}
<li>
{{ poll.date_created }}
<a href="{{ poll.id }}/">{{ poll.subject }}</a> |
<a href="{{ poll.id }}/update/" class="btn btn-sm btn-secondary">修改</a> |
<a href="{{ poll.id }}/delete/" class="btn btn-sm btn-danger">刪除</a>
</li>
{% endfor %}
</ul>
{% endblock %}
- 修改第 5, 9, 10 行,為
<a>
標籤新增 class
屬性
修改後頁面外觀如下圖:
列表、清單(List group)
接下來美化一下問題列表的外觀。將 <ul>
套用 list-group
類別,並將其內的 <li>
元素皆套用 list-group-item
類別:
{% extends "base.html" %}
{% block content %}
<h1>投票主題</h1>
<p><a href="create/" class="btn btn-primary">新增投票主題</a></p>
<ul class="list-group">
{% for poll in poll_list %}
<li class="list-group-item">
{{ poll.date_created }}
<a href="{{ poll.id }}/">{{ poll.subject }}</a>
<a href="{{ poll.id }}/update/" class="btn btn-sm btn-secondary">修改</a>
<a href="{{ poll.id }}/delete/" class="btn btn-sm btn-danger">刪除</a>
</li>
{% endfor %}
</ul>
{% endblock %}
資料卡片(Card)
接下來試著美化檢視問題的頁面,在此我們選用另一種方式-資料卡片(Card)-來呈現問題的詳細內容。
資料卡片的結構大致如下:
<div class="card">
<div class="card-header">卡片標題</div>
<div class="card-body">
卡片內容
</div>
<div class="card-footer">卡片頁腳</div>
</div>
每張卡片可包含 card-header
、 card-body
以及 card-footer
3 個部份,前面兩個比較容易理解, card-footer
通常是用來放說明或註解用的。
修改 poll/default/templates/default/poll_detail.html
,將投票主題當成資料卡片的標題,而附屬於這個主題的投票選項就當成資料卡片的內容,而新增選項的連結則當成頁腳。
{% extends "base.html" %}
{% block content %}
<p><a href="/" class="btn btn-primary">回首頁</a></p>
<div class="card">
<div class="card-header"><H1>{{ poll.subject }}</H1></div>
<div class="card-body">
<p>小提示!直接按選項文字就可以投票囉!</p>
<ul class="list-group">
{% for option in option_list %}
<li class="list-group-item">
<a href="/option/{{ option.id }}/update/" class="btn btn-sm btn-secondary">修改</a>
<a href="/option/{{ option.id }}/delete/" class="btn btn-sm btn-danger">刪除</a>
<a href="/option/{{ option.id }}/">{{ option.title }}</a> : {{ option.count }} 票
</li>
{% endfor %}
</table>
</div>
<div class="card-footer">
<a href="/option/create/{{ poll.id }}" class="btn btn-sm btn-primary">新增選項</a>
</div>
</div>
{% endblock %}
修改其它頁面範本
接著美化 poll/default/templates/general_form.html
:
{% extends "base.html" %}
{% block content %}
{% if backpath %}
<a href="{{ backpath }}" class="btn btn-primary">返回前一頁</a><BR>
{% endif %}
<h1>{{ title }}</h1>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="送出" class="btn btn-sm btn-primary">
</form>
{% endblock %}
因為在「新增投票主題」、「修改投票主題」、「新增投票選項」以及「修改投票選項」等 4 個頁面都會共用這個範本,為了讓使用者更容易識別目前正在進行哪一種操作,在這裡增加了第 7 行的程式碼:
另外,為了方便使用者返回前一個頁面,範本中也增加了第 4 - 6 行的內容:
{% if backpath %}
<a href="{{ backpath }}" class="btn btn-primary">返回前一頁</a><BR>
{% endif %}
由於上述的需求,需要修改 poll/default/views.py
中用來處理「新增投票主題」、「修改投票主題」、「新增投票選項」以及「修改投票選項」的頁面視圖,以下僅顯示相對應的 QuestionCreate
、 QuestionUpdate
、 AnswerCreate
與 AnswerUpdate
等 4 個 class,主要是為這 4 個類別分別加上 get_context_data
成員函式,將 title
與 backpath
加入要傳給頁面範本的資料清單內。
class PollCreate(CreateView):
model = Poll
fields = ['subject']
success_url = '/poll/'
template_name = 'general_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '新增投票主題'
context['backpath'] = '/'
return context
class PollUpdate(UpdateView):
model = Poll
fields = ['subject']
success_url = '/poll/'
template_name = 'general_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '修改投票主題'
context['backpath'] = '/'
return context
class OptionCreate(CreateView):
model = Option
fields = ['title']
template_name = 'general_form.html'
def get_success_url(self):
return '/poll/'+str(self.kwargs['pid'])+'/'
def form_valid(self, form):
form.instance.poll_id = self.kwargs['pid']
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '新增投票選項'
context['backpath'] = reverse('poll_view', kwargs={'pk': self.kwargs['pid']})
return context
class OptionUpdate(UpdateView):
model = Option
fields = ['title']
template_name = 'general_form.html'
def get_success_url(self):
return '/poll/'+str(self.object.poll_id)+'/'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '修改投票選項'
context['backpath'] = reverse('poll_view', kwargs={'pk': self.object.poll_id})
return context
要特別注意的是,「新增答案」或「修改答案」後,應該要導向到答案所屬的問題檢視頁面,所以 OptionCreate
與 OptionUpdate
這兩個 view 還需要再分別新增 get_success_url
成員函式來回傳操作完成後的導向頁面路徑。
頁面範本剩下 poll/default/templates/confirm_delete.html
還沒處理,修改如下:
{% extends "base.html" %}
{% block content %}
{% if backpath %}
<a href="{{ backpath }}" class="btn btn-primary">返回前一頁</a><BR>
{% endif %}
<h1>{{ title }}</h1>
<div class="list-group-item">{{object}}</div>
<p>您確定要刪除這筆記錄嗎?</p>
<form action="" method="POST">
{% csrf_token %}
<input type="submit" action="" value="是的,我要刪除" class="btn btn-sm btn-danger">
</form>
{% endblock %}
因為上述的修改,需要 views 額外傳遞 backpath
以及 title
這 2 個參數,所以需要修改 poll/default/views.py
裡的 PollDelete
以及 OptionDelete
這 2 個 View,分別為其加上 get_context_data
成員函式,以傳遞 title
與 backpath
給頁面範本,以下僅顯示 PollDelete
及 OptionDelete
:
class PollDelete(DeleteView):
model = Poll
success_url = '/poll/'
template_name = "confirm_delete.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '刪除投票主題'
context['backpath'] = '/poll/'
return context
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})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '刪除投票選項'
context['backpath'] = reverse('poll_view', kwargs={'pk': self.object.poll_id})
return context
案例:線上投票
第 4 至 6 課已經建立了一個簡單的線上投票網站,看起來有點陽春,接下來試著將輸出的頁面稍加美化一下。
如果對於 CSS 已經很熟悉了,可以直接自己撰寫樣式表。在這個案例中,我們直接引入市面上很多人使用的 CSS 框架 –
Bootstrap
來美化網站的外觀。Bootstrap 官網:https://getbootstrap.com/
引用 Bootstrap 框架
在專案中加入 Bootstrap 框架有好幾種方式,可以將至官網下載後,將其當成專案靜態檔案的一部份。最簡便的方式則是透過網路上的 CDN[1] 服務,直接引用所需要的檔案。以下兩種方式請選擇一種套用即可。
1. 透過 CDN 服務引用
參考 Bootstrap 官網上的說明,將
poll/default/templates/base.html
修改如下:2. 下載為專案的靜態檔案
指定靜態檔案存放路徑
先修改專案設定檔
poll/poll/settings.py
,指定靜態檔案存放的資料夾,新增第 123 - 125 行:接著在專案資料夾下建立
static
資料夾,建立完後,整個專案的目錄結構如下:將 Bootstrap 相關檔案放到靜態檔案存放路徑之下
到 Bootstrap 官網:https://getbootstrap.com/
點選「
Download
」按鈕進入下載頁面。點按下載頁面的 Compiled CSS and JS 下方的「
Download
」按鈕將下載的壓縮檔解開後,把得到的
css
與js
兩個資料夾複製到專案的靜態檔案存放路徑(static
)下,完成後整個專案的目錄結構如下:修改網站基底頁面範本
將網站基底頁面範本
poll/default/templates/base.html
修改如下:上面兩張擷圖顯示了引用 Bootstrap 框架前後的差異:
<H1></H1>
的字體大小美化頁面上的元件
使用 CSS 框架的好處是,這些框架已將事先定義好一整套的樣式規則,我們僅需要將網頁上的元件套用欲使用的 CSS 類別,就能輕鬆地增進網頁的美觀,並維持網站頁面外觀的一致性。
按鈕(Button)
若想把問題列表頁面上的「新增投票主題」、「修改」、「刪除」等連結改得像是按鈕的外觀的話,可以將這些連結套用
btn
以及與其語意相符的按鈕樣式類別即可。預先定義的按鈕樣式的類別如下表:另外,除了預設尺寸外,也可以額外套用
btn-lg
或btn-sm
來指定按鈕的尺寸為「大」或「小」。依上述說明修改
poll/default/templates/default/poll_list.html
,將「新增投票主題」、「修改」、「刪除」等連結套用 CSS 類別:<a>
標籤新增class
屬性修改後頁面外觀如下圖:
列表、清單(List group)
接下來美化一下問題列表的外觀。將
<ul>
套用list-group
類別,並將其內的<li>
元素皆套用list-group-item
類別:資料卡片(Card)
接下來試著美化檢視問題的頁面,在此我們選用另一種方式-資料卡片(Card)-來呈現問題的詳細內容。
資料卡片的結構大致如下:
每張卡片可包含
card-header
、card-body
以及card-footer
3 個部份,前面兩個比較容易理解,card-footer
通常是用來放說明或註解用的。修改
poll/default/templates/default/poll_detail.html
,將投票主題當成資料卡片的標題,而附屬於這個主題的投票選項就當成資料卡片的內容,而新增選項的連結則當成頁腳。修改其它頁面範本
接著美化
poll/default/templates/general_form.html
:因為在「新增投票主題」、「修改投票主題」、「新增投票選項」以及「修改投票選項」等 4 個頁面都會共用這個範本,為了讓使用者更容易識別目前正在進行哪一種操作,在這裡增加了第 7 行的程式碼:
另外,為了方便使用者返回前一個頁面,範本中也增加了第 4 - 6 行的內容:
由於上述的需求,需要修改
poll/default/views.py
中用來處理「新增投票主題」、「修改投票主題」、「新增投票選項」以及「修改投票選項」的頁面視圖,以下僅顯示相對應的QuestionCreate
、QuestionUpdate
、AnswerCreate
與AnswerUpdate
等 4 個 class,主要是為這 4 個類別分別加上get_context_data
成員函式,將title
與backpath
加入要傳給頁面範本的資料清單內。要特別注意的是,「新增答案」或「修改答案」後,應該要導向到答案所屬的問題檢視頁面,所以
OptionCreate
與OptionUpdate
這兩個 view 還需要再分別新增get_success_url
成員函式來回傳操作完成後的導向頁面路徑。頁面範本剩下
poll/default/templates/confirm_delete.html
還沒處理,修改如下:因為上述的修改,需要 views 額外傳遞
backpath
以及title
這 2 個參數,所以需要修改poll/default/views.py
裡的PollDelete
以及OptionDelete
這 2 個 View,分別為其加上get_context_data
成員函式,以傳遞title
與backpath
給頁面範本,以下僅顯示PollDelete
及OptionDelete
:內容傳遞網路(英語:Content delivery network或Content distribution network,縮寫:CDN)是指一種透過網際網路互相連接的電腦網路系統,利用最靠近每位使用者的伺服器,更快、更可靠地將音樂、圖片、影片、應用程式及其他檔案傳送給使用者,來提供高效能、可擴展性及低成本的網路內容傳遞給使用者。
來源:https://zh.wikipedia.org/wiki/內容傳遞網路 ↩︎