實戰:日誌
接下來我們要建立個人數位助理的網站專案,在這個專案中有 2 個獨立的功能:日誌以及記帳。
建立專案與應用程式
建立新專案
在終端機中下達指令建立一個新的專案
django-admin.py startproject assistant
打完指令後,會產生一個 assistant
的資料夾
assistant/
manage.py
assistant/
__init__.py
settings.py
urls.py
wsgi.py
在專案下建立應用程式
cd assistant
python manage.py startapp journal
修改專案設定檔 assistant/assistant/settings.py
,新增第 34 行,修改第 28, 58, 107, 109 行:
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'journal',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
LANGUAGE_CODE = 'zh-hant'
TIME_ZONE = 'Asia/Taipei'
資料庫
修改 assistant/web/models.py
,新增第 4 - 10 行,定義日誌資料表:
from django.db import models
class Journal(models.Model):
content = models.TextField("內容")
created = models.DateField(auto_now_add=True)
def __str__(self):
return self.content
執行以下指令建立資料庫:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
開啟網站服務
python manage.py runserver 0.0.0.0:80
網址、視圖、範本、表單
網址
開啟 assistant/assistant/urls.py
,修改為以下程式碼:
from django.contrib import admin
from django.urls import path, include
from django.views.generic import RedirectView
urlpatterns = [
path('admin/', admin.site.urls),
path('', RedirectView.as_view(url='journal/')),
path('journal', include('journal.urls')),
]
- 修改第 17 行,從
django.urls
額外引用 include
- 新增第 22, 23 行,加入 2 條路徑規則
新增檔案 assistant/journal/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('', JournalList.as_view(), name="journal_list"),
path('create/', JournalCreate.as_view()),
path('<int:pk>/update/', JournalUpdate.as_view()),
path('<int:pk>/delete/', JournalDelete.as_view()),
]
urls.py
裡的 include()
的作用是什麼?
如上所示,我在們 assistant/journal/urls.py
中定義了應用程式 journal
的 4 條路徑規則:
路徑 |
對應功能 |
|
日誌列表 |
create/ |
新增日誌 |
<int:pk>/update/ |
修改日誌 |
<int:pk>/delete/ |
刪除日誌 |
又在 assistant/assistant/urls.py
定義網站專案的路徑規則中,以第 23 行 程式碼:
path('journal/', include('journal.urls')),
將應用程式 journal
定義的路徑規則以前置 journal/
字串的方式加入網站專案中,等同於在專案的路徑規則中加入以下 4 條規則:
路徑 |
對應功能 |
journal/ |
日誌列表 |
journal/create/ |
新增日誌 |
journal/<int:pk>/update/ |
修改日誌 |
journal/<int:pk>/delete/ |
刪除日誌 |
視圖
開啟 assistant/journal/views.py
,修改為以下程式碼:
from django.shortcuts import render
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from .models import Journal
class JournalList(ListView):
model = Journal
ordering = ['-id']
class JournalCreate(CreateView):
model = Journal
fields = ['content']
success_url = '/journal/'
template_name = 'form.html'
class JournalUpdate(UpdateView):
model = Journal
fields = ['content']
success_url = '/journal/'
template_name = 'form.html'
class JournalDelete(DeleteView):
model = Journal
success_url = '/journal/'
template_name = 'confirm_delete.html'
範本
為了將所有的頁面範本集中管理,前面在專案設定檔中已經指定了頁面範本的搜尋路徑,接下來我們就將所有的頁面範本都放在這個資料夾下。
建立頁面範本資料夾
新增頁面範本資料夾 assistant/templates
建立 journal
應用程式的頁面範本資料夾 assistant/templates/journal
新增頁面範本
新增網站基底頁面範本 assistant/templates/base.html
:
<!DOCTYPE html>
<html lang="zh-hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>數位助理</title>
</head>
<body>
<h1>數位助理</h1>
<div>
<a href="/journal/">日誌列表</a>
<a href="/journal/create/">寫日誌</a>
</div>
<div>{% block content %}{% endblock %}</div>
</body>
</html>
新增日誌列表頁面範本 assistant/templates/journal/journal_list.html
{% extends 'base.html' %}
{% block content %}
<h2>我的日誌:</h2>
<table>
<tr>
<th>時間</th>
<th>項目</th>
<th>操作</th>
</tr>
{% for journal in journal_list %}
<tr>
<td>{{ journal.created|date:"l" }}{{ journal.created }}</td>
<td><a href="{{ journal.id }}/update/">{{ journal.content }}</a></td>
<td><a href="{{ journal.id }}/delete/">刪除</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}
- 第 11 - 17 行,將
journal_list
中的每筆紀錄拿出來處理,每次拿到一筆紀錄就放在 journal
這個變數中
關於 date 過濾器
它是用來處理日期/時間資料的過濾器,依據使用者指定的格式輸出。以下摘列 date 過濾器接受的部份常用格式字元及其代表意義:
格式字元 |
說明 |
範例輸出 |
d |
月份的第幾天,以前置 0 補滿 2 位 |
‘01’ 到 ‘31’ |
j |
月份的第幾天,無前置 0 |
‘1’ 到 ‘31’ |
D |
短文字型式的星期幾 |
Fri |
l |
長文字型式的星期幾 |
Friday |
w |
數字型式的星期幾 |
‘0’(星期天) 到 ‘6’(星期六) |
z |
年份中的第幾天,由 0 起算 |
‘0’ 到 ‘365’ |
W |
ISO-8601標準中的週次,以星期一為一週起始 |
‘1’ 到 ‘53’ |
m |
月份,以前置 0 補滿 2 位 |
‘01’ 到 ‘12’ |
n |
月份,無前置 0 |
‘1’ 到 ‘12’ |
M |
短文字型式的月份名 |
‘Jan’ |
b |
短文字型式的月份名,全小寫 |
‘feb’ |
F |
長文字型式的月份名 |
‘September’ |
t |
當月份的總日數 |
‘28’ 到 ‘31’ |
y |
2位型式的西元年份 |
‘19’ |
Y |
4位型式的西元年份 |
‘2019’ |
L |
當年是否為閏年 |
‘True’ 或 ‘False’ |
g |
12小時制的小時,無前置 0 |
‘1’ 到 ‘12’ |
G |
24小時制的小時,無前置 0 |
‘0’ 到 ‘23’ |
h |
12小時制的小時,以前置 0 補滿 2 位 |
‘01’ 到 ‘12’ |
H |
24小時制的小時,以前置 0 補滿 2 位 |
‘00’ 到 ‘23’ |
i |
分鐘,以前置 0 補滿 2 位 |
‘00’ 到 ‘59’ |
s |
秒數,以前置 0 補滿 2 位 |
‘00’ 到 ‘59’ |
注意,在正體中文語系下:
- ‘D’ 與 ‘l’ 沒有差別,都會以 ‘星期一’ 這樣的格式顯示星期幾
- 月份的 ‘M’, ‘b’, ‘F’ 也無差別,都會以 ‘一月’ 這樣的方式呈現
完整列表請參見官方網站說明文件:
https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#date
新增共用編輯表單頁面範本 assistant/templates/form.html
{% extends 'base.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="送出" />
</form>
{% endblock %}
- 第 5 行,產生 CSRF 驗證資料
- 第 7 行,將
form
變數中的表單欄位以表格的型式輸出
新增共用刪除表單頁面範本 assistant/templates/confirm_delete.html
{% extends 'base.html' %}
{% block content %}
<h2>刪除紀錄</h2>
<p>{{ object }}</p>
<p>確定要刪除這筆記錄嗎?</p>
<form action="" method="post">
{% csrf_token %}
<input type="submit" value="是的,我要刪除" />
</form>
{% endblock %}
使用者登入與登出
新增登入登出路徑規則
開啟 assistant/assistant/urls.py
,新增以下程式碼:
urlpatterns = [
path('admin/', admin.site.urls),
path('', RedirectView.as_view(url='journal/')),
path('journal/', include('journal.urls')),
path('accounts/', include('django.contrib.auth.urls')),
]
直接利用內建的應用程式 django.contrib.auth
來處理使用者登入與登出。
開啟專案設定檔 assistant/assistant/settings.py
,增加以下程式碼:
登入登出頁面範本
新增資料夾 assistant/templates/registration
新增登入頁面範本 assistant/templates/registration/login.html
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p>帳號或密碼不符合,請再試一次。</p>
{% endif %}
<form action="" method="post">
{% csrf_token %}
<div>
<td>帳號:</td>
<td>{{ form.username }}</td>
</div>
<div>
<td>密碼:</td>
<td>{{ form.password }}</td>
</div>
<div>
<input type="submit" value="登入" />
</div>
</form>
{% endblock %}
- 第 4 - 6 行,若
form.errors
不是空的,表示登入有誤,因此顯示錯誤訊息。
- 第 10 - 17 行,除了像前一個留言板的範例用
{{ form.as_p }}
或 {{ form.as_table }}
來自動將表單元件轉成 HTML 碼之外,也可以像這個例子,自己編寫表單元素的 HTML 碼。
新增登出頁面範本 assistant/templates/registration/logged_out.html
{% extends "base.html" %}
{% block content %}
<p>您已登出!!</p>
<a href="{% url 'login'%}">請按此處重新登入</a>
{% endblock %}
修改網站基底頁面範本 assistant/templates/base.htm
,加上登出/登入連結:
<!DOCTYPE html>
<html lang="zh-hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>數位助理</title>
</head>
<body>
<h1>數位助理</h1>
<div>
<a href="/journal/">日誌列表</a>
<a href="/journal/create/">寫日誌</a>
{% if user.is_authenticated %}
{{ user.username }} <a href="{% url 'logout' %}">登出</a>
{% else %}
<a href="{% url 'login' %}">登入</a>
{% endif %}
</div>
<div>{% block content %}{% endblock %}</div>
</body>
</html>
- 新增第 14 - 18 行,若使用者已登入,則顯示登入帳號以及登出連結,否則顯示登入連結
這 2 條名為 login
與 logout
的路徑規則有被定義在 django.contrib.auth
應用程式的路徑規則中,還記得剛才我們有將它們引入專案的路徑規則組嗎?
限制登入後才能執行日記功能
到目前為止,雖然提供了使用者登人與登出的功能,但實際上使用者登入與否並不影響他在這個網站上所能進行的操作,即使在沒登入的狀態也能寫日誌、修改日誌、甚至刪除日誌。
你可能會想到在可以頁面範本中檢查使用者的登入狀況,再決定是否要顯示相關的連結。但是,看不到連結,不代表不能手動在網址列打上這些功能的存取路徑,一旦使用者「猜」到他想執行的操作所對應到的路徑,他可以自己打在網址列上,再讓瀏覽器送出存取請求來執行他想進行的動作。
為了做好網站的權限管控,不能只把不讓使用者操作的連結藏起來就好,而是應該在處理使用者請求執行某項功能的時候,要同時檢核他的權限。
這個範例的需求比較簡單,因為它是個人用的數位助理,所以只要檢查使用者是否已登入就好,不需要更複雜的權限管理。
開啟 assistant/journal/views.py
,修改為以下程式碼:
from django.shortcuts import render
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from .models import Journal
from django.contrib.auth.mixins import LoginRequiredMixin
class JournalList(LoginRequiredMixin, ListView):
model = Journal
ordering = ['-id']
class JournalCreate(LoginRequiredMixin, CreateView):
model = Journal
fields = ['content']
success_url = '/journal/'
template_name = 'form.html'
class JournalUpdate(LoginRequiredMixin, UpdateView):
model = Journal
fields = ['content']
success_url = '/journal/'
template_name = 'form.html'
class JournalDelete(LoginRequiredMixin, DeleteView):
model = Journal
success_url = '/journal/'
template_name = 'confirm_delete.html'
- 新增第 4 行,引用 Django 內建的
LoginRequiredMixin
混成的類別
- 修改第 8, 13, 20, 27 行,將
LoginRequiredMixin
加進 JournalList
、JournalCreate
、JournalUpdate
、JournalDelete
的繼承列表
修改完成後,若在未登入的情況下,不論是點按日誌列表,或是寫日誌,亦或是直接在網址列登打修改某篇日誌或刪除某篇日誌的路徑,都會直接被轉址到要求使用者登入的頁面,待登入成功後才能繼續原先欲操作的功能:
分頁顯示
在日誌列表的頁面上,會將所有的日誌記錄全部列出來,但當日誌經過一段時間的累積之後,在同一個頁面上要顯示所有的日誌,有可能會因為數量太大,而導致頁面產生的速度過慢。因此一般的網站在處理清單式的頁面的時候,通常都會採用分頁的手法,將所有紀錄拆分成多頁顯示,每一項最多僅顯示固定筆數的紀錄。
修改視圖,加入分頁設定
開啟 assistant/journal/views.py
,修改 JournalList
類別,新增第 13 行,通用視圖 ListView
的衍生類別可以透過指定 paginate_by
屬性來指定每頁顯示的最多資料筆數:
class JournalList(LoginRequiredMixin, ListView):
model = Journal
ordering = ['-id']
paginate_by = 3
註:為了便於示範,在不產生過多測試資料的情況下,這裡設定每頁最多只顯示 3 筆日誌。實務上每頁顯示的資料量應該會更多。
處理頁面範本,加入分頁連結
在視圖中指定 paginate_by
屬性值,ListView
會自動依據這個設定取出目前頁面的資料量。因此頁面範本中只能取得最多 paginate_by
筆數的紀錄,但在頁面上顯示這些紀錄後,我們目前的頁面範本並不會自動顯示上一頁、下一頁、或其他分頁頁碼的連結。這部份不是 ListView
的工作,頁面如何呈現,該呈現什麼,這是頁面範本的任務。因此需要修改頁面範本,來規範這些分頁的連結該出現在何處,以及如何呈現這些分頁連結。
建立分頁連結範本
首先新增分頁連結的範本 assistant/templates/pagination.html
:
{% if is_paginated %}
<div>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">上一頁</a>
{% endif %}
{% for page in paginator.page_range %}
{% if page == page_obj.number %}
<a href="#"><font color="red">{{ page }}</font></a>
{% else %}
<a href="?page={{ page }}">{{ page }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一頁</a>
{% endif %}
</div>
{% endif %}
這個分頁連結的範本所產生的 HTML 碼並不是一個完整的頁面,僅產生分頁連結的部份而已。若有其他的頁面範本需要產生分頁連結的時候,可以在頁面範本中透過底下的標籤來引用分頁連結範本:
{% include 'pagination.html' %}
將分頁連結加入日誌列表
修改日誌列表頁面範本 assistant/templates/journal/journal_list.html
,新增第 19 行,引入 pagination.html
分頁連結範本:
{% extends 'base.html' %}
{% block content %}
<h2>我的日誌</h2>
<table>
<tr>
<th>時間</th>
<th>項目</th>
<th>操作</th>
</tr>
{% for journal in journal_list %}
<tr>
<td>{{ journal.created|date:"l" }}{{ journal.created }}</td>
<td><a href="{{ journal.id }}/update/">{{ journal.content }}</a></td>
<td><a href="{{ journal.id }}/delete/">刪除</a></td>
</tr>
{% endfor %}
</table>
{% include 'pagination.html' %}
{% endblock %}
實戰:日誌
接下來我們要建立個人數位助理的網站專案,在這個專案中有 2 個獨立的功能:日誌以及記帳。
建立專案與應用程式
建立新專案
在終端機中下達指令建立一個新的專案
打完指令後,會產生一個
assistant
的資料夾在專案下建立應用程式
修改專案設定檔
assistant/assistant/settings.py
,新增第 34 行,修改第 28, 58, 107, 109 行:資料庫
修改
assistant/web/models.py
,新增第 4 - 10 行,定義日誌資料表:執行以下指令建立資料庫:
開啟網站服務
網址、視圖、範本、表單
網址
開啟
assistant/assistant/urls.py
,修改為以下程式碼:django.urls
額外引用include
新增檔案
assistant/journal/urls.py
urls.py
裡的include()
的作用是什麼?如上所示,我在們
assistant/journal/urls.py
中定義了應用程式journal
的 4 條路徑規則:create/
<int:pk>/update/
<int:pk>/delete/
又在
assistant/assistant/urls.py
定義網站專案的路徑規則中,以第 23 行 程式碼:將應用程式
journal
定義的路徑規則以前置journal/
字串的方式加入網站專案中,等同於在專案的路徑規則中加入以下 4 條規則:journal/
journal/create/
journal/<int:pk>/update/
journal/<int:pk>/delete/
視圖
開啟
assistant/journal/views.py
,修改為以下程式碼:範本
為了將所有的頁面範本集中管理,前面在專案設定檔中已經指定了頁面範本的搜尋路徑,接下來我們就將所有的頁面範本都放在這個資料夾下。
建立頁面範本資料夾
新增頁面範本資料夾
assistant/templates
建立
journal
應用程式的頁面範本資料夾assistant/templates/journal
新增頁面範本
新增網站基底頁面範本
assistant/templates/base.html
:新增日誌列表頁面範本
assistant/templates/journal/journal_list.html
journal_list
中的每筆紀錄拿出來處理,每次拿到一筆紀錄就放在journal
這個變數中關於 date 過濾器
它是用來處理日期/時間資料的過濾器,依據使用者指定的格式輸出。以下摘列 date 過濾器接受的部份常用格式字元及其代表意義:
d
j
D
Fri
l
Friday
w
z
W
m
n
M
b
F
t
y
Y
L
g
G
h
H
i
s
注意,在正體中文語系下:
完整列表請參見官方網站說明文件:
https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#date
新增共用編輯表單頁面範本
assistant/templates/form.html
form
變數中的表單欄位以表格的型式輸出新增共用刪除表單頁面範本
assistant/templates/confirm_delete.html
使用者登入與登出
新增登入登出路徑規則
開啟
assistant/assistant/urls.py
,新增以下程式碼:直接利用內建的應用程式
django.contrib.auth
來處理使用者登入與登出。開啟專案設定檔
assistant/assistant/settings.py
,增加以下程式碼:登入登出頁面範本
新增資料夾
assistant/templates/registration
新增登入頁面範本
assistant/templates/registration/login.html
form.errors
不是空的,表示登入有誤,因此顯示錯誤訊息。{{ form.as_p }}
或{{ form.as_table }}
來自動將表單元件轉成 HTML 碼之外,也可以像這個例子,自己編寫表單元素的 HTML 碼。新增登出頁面範本
assistant/templates/registration/logged_out.html
修改網站基底頁面範本
assistant/templates/base.htm
,加上登出/登入連結:這 2 條名為
login
與logout
的路徑規則有被定義在django.contrib.auth
應用程式的路徑規則中,還記得剛才我們有將它們引入專案的路徑規則組嗎?限制登入後才能執行日記功能
到目前為止,雖然提供了使用者登人與登出的功能,但實際上使用者登入與否並不影響他在這個網站上所能進行的操作,即使在沒登入的狀態也能寫日誌、修改日誌、甚至刪除日誌。
你可能會想到在可以頁面範本中檢查使用者的登入狀況,再決定是否要顯示相關的連結。但是,看不到連結,不代表不能手動在網址列打上這些功能的存取路徑,一旦使用者「猜」到他想執行的操作所對應到的路徑,他可以自己打在網址列上,再讓瀏覽器送出存取請求來執行他想進行的動作。
為了做好網站的權限管控,不能只把不讓使用者操作的連結藏起來就好,而是應該在處理使用者請求執行某項功能的時候,要同時檢核他的權限。
這個範例的需求比較簡單,因為它是個人用的數位助理,所以只要檢查使用者是否已登入就好,不需要更複雜的權限管理。
開啟
assistant/journal/views.py
,修改為以下程式碼:LoginRequiredMixin
混成的類別LoginRequiredMixin
加進JournalList
、JournalCreate
、JournalUpdate
、JournalDelete
的繼承列表修改完成後,若在未登入的情況下,不論是點按日誌列表,或是寫日誌,亦或是直接在網址列登打修改某篇日誌或刪除某篇日誌的路徑,都會直接被轉址到要求使用者登入的頁面,待登入成功後才能繼續原先欲操作的功能:
分頁顯示
在日誌列表的頁面上,會將所有的日誌記錄全部列出來,但當日誌經過一段時間的累積之後,在同一個頁面上要顯示所有的日誌,有可能會因為數量太大,而導致頁面產生的速度過慢。因此一般的網站在處理清單式的頁面的時候,通常都會採用分頁的手法,將所有紀錄拆分成多頁顯示,每一項最多僅顯示固定筆數的紀錄。
修改視圖,加入分頁設定
開啟
assistant/journal/views.py
,修改JournalList
類別,新增第 13 行,通用視圖ListView
的衍生類別可以透過指定paginate_by
屬性來指定每頁顯示的最多資料筆數:註:為了便於示範,在不產生過多測試資料的情況下,這裡設定每頁最多只顯示 3 筆日誌。實務上每頁顯示的資料量應該會更多。
處理頁面範本,加入分頁連結
在視圖中指定
paginate_by
屬性值,ListView
會自動依據這個設定取出目前頁面的資料量。因此頁面範本中只能取得最多paginate_by
筆數的紀錄,但在頁面上顯示這些紀錄後,我們目前的頁面範本並不會自動顯示上一頁、下一頁、或其他分頁頁碼的連結。這部份不是ListView
的工作,頁面如何呈現,該呈現什麼,這是頁面範本的任務。因此需要修改頁面範本,來規範這些分頁的連結該出現在何處,以及如何呈現這些分頁連結。建立分頁連結範本
首先新增分頁連結的範本
assistant/templates/pagination.html
:這個分頁連結的範本所產生的 HTML 碼並不是一個完整的頁面,僅產生分頁連結的部份而已。若有其他的頁面範本需要產生分頁連結的時候,可以在頁面範本中透過底下的標籤來引用分頁連結範本:
{% include 'pagination.html' %}
將分頁連結加入日誌列表
修改日誌列表頁面範本
assistant/templates/journal/journal_list.html
,新增第 19 行,引入pagination.html
分頁連結範本: