回上方

實戰:日誌

接下來我們要建立個人數位助理的網站專案,在這個專案中有 2 個獨立的功能:日誌以及記帳。

建立專案與應用程式

建立新專案

(1)在終端機中下達指令建立一個新的專案

django-admin.py startproject assistant

打完指令後,會產生一個 assistant 的資料夾

assistant/
    manage.py
    assistant/
        __init__.py
        settings.py
        urls.py
        wsgi.py

在專案下建立應用程式

cd assistant python manage.py startapp web

修改 assistant/assistant/settings.py

ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'web', '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', ], }, }, ]
# Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ LANGUAGE_CODE = 'zh-hant' TIME_ZONE = 'Asia/Taipei'

資料庫

修改 assistant/web/models.py

from django.db import models # Create your models here. # 日誌 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: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('', include('web.urls')), ]

新增檔案 assistant/web/urls.py

from django.urls import path from django.views.generic import RedirectView from .views import * urlpatterns = [ path('', RedirectView.as_view(url='journal/')), path('journal/', JournalList.as_view()), path('journal/create/', JournalCreate.as_view()), path('journal/<int:pk>/update/', JournalUpdate.as_view()), path('journal/<int:pk>/delete/', JournalDelete.as_view()), ]

視圖

開啟 assistant/web/views.py,修改為以下程式碼:

from django.shortcuts import render from django.views.generic import ListView, CreateView, UpdateView, DeleteView from .models import Journal # Create your views here. # 日誌列表 class JournalList(ListView): model = Journal ordering = ['-id'] # 依 id 欄位反向排序(新的在前面) # 新增日誌 class JournalCreate(CreateView): model = Journal fields = ['content'] # 自動產生表單時僅顯示 content 欄位 success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html' # 修改日誌 class JournalUpdate(UpdateView): model = Journal fields = ['content'] # 自動產生表單時僅顯示 content 欄位 success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html' # 刪除日誌 class JournalDelete(DeleteView): model = Journal success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'confirm_delete.html'

範本

為了將所有的頁面範本集中管理,前面在專案設定檔中已經指定了頁面範本的搜尋路徑,接下來我們就將所有的頁面範本都放在這個資料夾下。

建立頁面範本資料夾

新增頁面範本資料夾 assistant/templates

建立 web 應用程式的頁面範本資料夾 assistant/templates/web

新增頁面範本

新增網站基底頁面範本 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/web/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 %}

新增共用編輯表單頁面範本 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 %}

新增共用刪除表單頁面範本 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('', include('web.urls')), path('accounts/', include('django.contrib.auth.urls')), ]

開啟專案設定檔 assistant/assistant/settings.py,增加以下程式碼:

# Redirect to home URL after login (Default redirects to /accounts/profile/) LOGIN_REDIRECT_URL = '/' # 設定登人後導向的頁面

登入登出頁面範本

新增資料夾 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 %}

新增登出頁面範本 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>


限制登入後才能執行日記功能

到目前為止,雖然提供了使用者登人與登出的功能,但實際上使用者登入與否並不影響他在這個網站上所能進行的操作,即使在沒登入的狀態也能寫日誌、修改日誌、甚至刪除日誌。

你可能會想到在可以頁面範本中檢查使用者的登入狀況,再決定是否要顯示相關的連結。但是,看不到連結,不代表不能手動在網址列打上這些功能的存取路徑,一旦使用者「猜」到他想執行的操作所對應到的路徑,他可以自己打在網址列上,再讓瀏覽器送出存取請求來執行他想進行的動作。

為了做好網站的權限管控,不能只把不讓使用者操作的連結藏起來就好,而是應該在處理使用者請求執行某項功能的時候,要同時檢核他的權限。

這個範例的需求比較簡單,因為它是個人用的數位助理,所以只要檢查使用者是否已登人就好,不需要更複雜的權限管理。

開啟 assistant/web/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 # Create your views here. # 日誌列表 class JournalList(LoginRequiredMixin, ListView): model = Journal ordering = ['-id'] # 依 id 欄位反向排序(新的在前面) # 新增日誌 class JournalCreate(LoginRequiredMixin, CreateView): model = Journal fields = ['content'] # 自動產生表單時僅顯示 content 欄位 success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html' # 修改日誌 class JournalUpdate(LoginRequiredMixin, UpdateView): model = Journal fields = ['content'] # 自動產生表單時僅顯示 content 欄位 success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html' # 刪除日誌 class JournalDelete(LoginRequiredMixin, DeleteView): model = Journal success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'confirm_delete.html'

分頁顯示

在日誌列表的頁面上,會將所有的日誌記錄全部列出來,但當日誌經過一段時間的累積之後,在同一個頁面上要顯示所有的日誌,有可能會因為數量太大,而導致頁面產生的速度過慢。因此一般的網站在處理清單式的頁面的時候,通常都會採用分頁的手法,將所有紀錄拆分成多頁顯示,每一項最多僅顯示固定筆數的紀錄。

修改視圖,加入分頁設定

開啟 assistant/web/views.py,修改 JournalList 類別:

# 日誌列表 class JournalList(LoginRequiredMixin, ListView): model = Journal ordering = ['-id'] # 依 id 欄位反向排序(新的在前面) paginate_by = 3 # 設定每頁最多顯示的資料筆數

處理頁面範本,加入分頁連結

在視圖中指定 paginate_by 屬性值,ListView 會自動依據這個設定取出目前面頁的資料量。因此頁面範本中只能取得最多 paginated_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/web/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> {% include 'pagination.html' %} {% endblock %}