案例:數位助理

前言

第 8 課與第 9 課已經分別實作了數位助理專案的日誌以及記帳的功能,接著來進行版面美化的工作。在這個案例中,同樣直接透過 CDN 來引用 Bootstrap 框架以及 Font Awesome 來美化網站頁面。

修改網站基底範本

建立導覽列

新增 assistant/templates/navbar.html

<!-- Navbar begin //--> <nav class="navbar navbar-expand-sm navbar-dark bg-dark mb-2"> <!-- 網站標誌 --> <div class="navbar-brand"><i class="far fa-address-card"></i> 數位助理</div> <!-- 在小螢幕的設備上顯示可展開/收合導覽選單的按鈕 --> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <!-- 導覽列選單內容(可收合) --> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ml-auto"> {% if user.is_authenticated %} <li class="nav-item"><a href="/" class="nav-link"><i class="fas fa-book"></i> 日記</a></li> <li class="nav-item"><a href="/journal/create" class="nav-link"><i class="fas fa-edit"></i> 寫日誌</a></li> <li class="nav-item"><a href="/expenses/" class="nav-link"><i class="fas fa-money-check"></i> 帳本</a></li> <li class="nav-item"><a href="/expenses/create" class="nav-link"><i class="fas fa-money-check-alt"></i> 記帳</a></li> <li class="nav-item"><a href="/accounts/logout" class="nav-link btn btn-sm btn-primary">{{ user.username }} <i class="fas fa-sign-out-alt"></i> 登出</a></li> {% else %} <li class="nav-item"><a href="{% url 'login' %}" class="nav-link btn btn-sm btn-primary"><i class="fas fa-sign-in-alt"></i> 登入</a></li> {% endif %} </ul> </div> </nav> <!-- Navbar end //-->

引用 Bootstrap、FontAwesome 以及導覽列

將網站基底頁面範本 assistant/templates/base.html 修改如下:

<!doctype html> <html lang="zh-hant"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous"> <title>數位助理</title> </head> <body> <div class="container"> {% include "navbar.html" %} <div id="content">{% block content %}{% endblock %}</div> </div> </body> </html>

修改後,導覽列的顯示效果如下:

美化日誌功能

使用者登入

先前在表單所使用的頁面範本中,要產生每個欄位的輸入元件時,都是使用 {{ form.as_p }}{{ form.as_table }} 來將頁面範本接收到的 form 變數中關於欄位的定義自動展開。這種方式的好處是方便,但是較難針對欄位的外觀做較細部的處理。

另一種方式則是自行撰寫所需欄位的 HTML 碼,只要輸入元件的 name 屬性與其所屬的資料模型或表單定義內的欄位名稱相同,就可以用來在頁面上接收使用者的輸入,在送出表單後,Django 也能正常處理。以下示範不透過 form 變數來自動產生輸入元件的 HTML 碼,而改採使用者自行撰寫的方式來修改使用者登入表單的頁面範本。

修改 assistant/templates/registration/login.html

{% extends "base.html" %} {% block content %} {% if form.errors %} <p class="alert alert-danger">帳號或密碼不符合,請再試一次。</p> {% endif %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <div class="h3 mb5">請輸入您的帳號密碼</div> <div class="form-inline"> <input name="username" autofocus required id="id_username" maxlength="254" type="text" class="form-control mr-2" placeholder="帳號"> <input name="password" required id="id_password" type="password" class="form-control mr-2" placeholder="密碼"> <input type="submit" value="login" class="form-control btn-primary"/> </div> </form> {% endblock %}

使用者登出

修改 assistant/templates/registration/logged_out.html

{% extends "base.html" %} {% block content %} <p class="alert alert-info">您已登出!!</p> <a href="{% url 'login'%}" class="btn btn-primary">請按此處重新<i class="fas fa-sign-in-alt"></i> 登入</a> {% endblock %}

Bootstrap 框架的 alert 類別是套用在包覆警告、通知訊息用的容器上,需額外搭配相對應的類別,以產生不同的前背景配色組合:

搭配類別 外觀示例
alert-primary
alert-secondary
alert-success
alert-danger
alert-warning
alert-info
alert-light
alert-dark

日誌列表

修改 assistant/templates/journal/journal_list.html

{% extends "base.html" %} {% block content %} <h2><i class="fas fa-book"></i> 我的日誌:</h2> <table class="table table-sm"> <tr><td>時間</td><td>項目</td><td>功能</td></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/" class="btn btn-sm btn-danger py-0 px-1"><i class="fas fa-trash-alt"></i> 刪除</a></td> </tr> {% endfor %} </table> {% include 'pagination.html' %} {% endblock %}

修正時間格式

日誌列表的時間格式目前顯示的語言是英文,若要修正為中文的話,需要修改專案設定值。

修改 assistant/assistant/settings.py ,將 LANGUAGE_CODE 以及 TIME_ZONE 的值修正如下:

# Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'zh-hant' TIME_ZONE = 'Asia/Taipei'

美化分頁連結

修改 assistant/templates/pagination.html

{% if is_paginated %} <div> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-sm btn-primary"><i class="fas fa-chevron-circle-left"></i>上一頁</a> {% endif %} {% for page in paginator.page_range %} {% if page == page_obj.number %} <button class="btn btn-sm btn-primary" disabled>{{ page }}</button> {% else %} <a href="?page={{ page }}" class="btn btn-sm btn-primary">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}" class="btn btn-sm btn-primary">下一頁<i class="fas fa-chevron-circle-right"></i></a> {% endif %} </div> {% endif %}

修改項目主要有 2 個:

我的帳本

修改 assistant/templates/expenses/expense_list.html

{% extends "base.html" %} {% block content %} <h2><i class="fas fa-money-check"></i> 我的帳本:</h2> <table class="table table-sm"> <tr> <th>時間</th> <th>項目</th> <th>類別</th> <th>金額</th> <th>功能</th> </tr> {% for expense in expense_list %} <tr> <td>({{ expense.time|date:"l" }}) {{ expense.time }}</td> <td><a href="{{ expense.id }}/update/">{{ expense.item }}</a></td> <td>{{ expense.get_category_display }}</td> <td>{{ expense.amount }}</td> <td><a href="{{ expense.id }}/delete/" class="btn btn-sm btn-danger py-0 px-1"><i class="fas fa-trash-alt"></i> 刪除</a></td> </tr> {% endfor %} </table> {% include 'pagination.html' %} {% endblock content %}

寫日誌與記帳

修改頁面範本

修改 assistant/templates/form.html

{% extends "base.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <table class="table table-sm"> {{ form.as_table }} </table> <input type="submit" value="送出" class="btn btn-sm btn-primary"/> </form> {% endblock %}

美化表單

頁面範本 assistant/templates/form.html 中採用了 {{ form.as_table }} 的方式來產生輸入元件的 HTML 碼,所以才能讓新增、修改日誌以及記帳等功能共用同一個頁面範本。但是這樣一來,也無法直接在頁面範本中指定 <input> 標籤來套用 form-control 類別來修改它們的外觀。

如果想要在頁面範本中保持採用 {{ form.as_table }} 讓多項輸要輸入資料的功能可以共用同一個頁面範本的話,該如何針對自動產生的輸入元件的標籤做相關的調整呢?官方的標準做法是自行撰寫表單類別,在類別中指定每個輸入欄位的相關設定。示範如下:

新增 assistant/journal/forms.py,在其中自行定義表單類別:

from django.forms import ModelForm, Textarea from .models import Journal class JournalForm(ModelForm): # 自訂表單定義 class Meta: # 表單元類別 model = Journal # 指定參考的資料模型 fields = ['content'] # 在表單中要顯示哪些輸入欄位 widgets = { 'content': Textarea(attrs={'class': 'form-control'}), }

自訂了表單定義後,接著修改通用視圖,指定使用自訂的表單定義。

請修改 assistant/journal/views.py,指定 JournalCreateJournalUpdate 等 2 個 class 的 form_class 屬性:

from .forms import JournalForm # 引用自訂表單定義
# 新增日誌 class JournalCreate(LoginRequiredMixin, CreateView): form_class = JournalForm # 指定 form_class 使用 JournalForm success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html' # 修改日誌 class JournalUpdate(LoginRequiredMixin, UpdateView): model = Expense form_class = JournalForm # 指定 form_class 使用 JournalForm success_url = '/journal/' # 操作成功後重新導向日誌列表頁面 template_name = 'form.html'

修改處如下:

同理,要修改記帳功能表單的外觀,也要修改 expenses 應用程式的 forms.py 以及 views.py。新增自訂表單定義 assistant/expenses/forms.py:

from django.forms import ModelForm, TextInput, Select, NumberInput from .models import Expense class ExpenseForm(ModelForm): class Meta: model = Expense # 參考資料模型 Expense fields = '__all__' # 顯示*所有*欄位 widgets = { 'item': TextInput(attrs={'class': 'form-control'}), 'category': Select(attrs={'class': 'form-control'}), 'amount': NumberInput(attrs={'class': 'form-control'}), }

修改 assistant/expenses/view.py,指定 ExpenseCreateExpenseUpdate 這兩個通用視圖使用方才自定義的 ExpenseForm:

from .forms import ExpenseForm
# 新增支出紀錄 class ExpenseCreate(LoginRequiredMixin, CreateView): form_class = ExpenseForm # 指定使用自訂表單定義 ExpenseForm template_name = 'form.html' # 指定使用 form.html 這個頁面範本 def get_success_url(self): return reverse('expense_list') # 新增成功返回支出紀錄列表頁面 # 修改支出紀錄 class ExpenseUpdate(LoginRequiredMixin, UpdateView): model = Expense # 參考資料模型 Expense form_class = ExpenseForm # 指定使用自訂表單定義 ExpenseForm template_name = 'form.html' def get_success_url(self): return reverse('expense_list') # 修改成功返回支出紀錄列表頁面

修改方式請參考前一段關於 JournalCreateJournalUpdate 的修改說明。