建立資料庫與表單

修改 repair/log/models.py

from django.db import models # Create your models here. class LogItem(models.Model): # 處理進度的選項清單 ST_OPTIONS = [ (0, '待處理'), (1, '處理中'), (2, '已結案'), ] # 報修主旨 subject = models.CharField('報修主旨', max_length=255) # 報修內容 description = models.TextField('報修內容') # 報修人 reporter = models.CharField('報修人', max_length=30) # 聯絡電話 phone = models.CharField('聯絡電話', max_length=30) # 報修時間 ctime = models.DateTimeField('報修時間', auto_now_add=True) # -------------------------------------------------- # 處理人員 handler = models.CharField('處理人員', max_length=30) # 處理進度 status = models.IntegerField( '處理進度', default=0, choices=ST_OPTIONS ) # 處理說明 comment = models.TextField('處理說明') # 更新時間 utime = models.DateTimeField('更新時間', auto_now=True) def __str__(self): return self.subject
python manage.py makemigrations python manage.py migrate

另外,在第一次進行資料庫遷移時,記得以專案管理腳本的 createsuperuser 參數來建立管理員帳號:

python manage.py createsuperuser

接下來就可以將專案網站服務執行起來,以便在開發過程中進行測試:

python manage.py runserver 0.0.0.0:80

決定好路徑與功能的對應關係之後,先修改專案的路徑規則檔 repair/repair/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('log/', include('log.urls')), path('', RedirectView.as_view(url='log/')), ]

接著就將焦點放在應用程式 log 上,先來定義它自己的路徑規則。請新增檔案 repair/log/urls.py

from django.urls import path from .views import * urlpatterns = [ path('', LogList.as_view(), name='log_list'), path('create/', LogCreate.as_view(), name='log_create'), path('<int:pk>/', LogView.as_view(), name='log_view'), path('<int:pk>/reply/', LogReply.as_view(), name='log_reply'), ]

定義處理視圖

接著來撰寫每條路徑規則相對應的處理視圖,請開啟 repair/log/views.py ,修改為以下程式碼:

from django.shortcuts import render from django.views.generic import * from django.urls import reverse from .models import LogItem # Create your views here. # 報修項目列表 class LogList(ListView): model = LogItem ordering = ['-id'] # 檢視報修項目 class LogView(DetailView): model = LogItem # 新增報修項目 class LogCreate(CreateView): model = LogItem # 新增時只顯示需填寫的部份欄位 fields = ['subject', 'description', 'reporter', 'phone'] def get_success_url(self): return reverse('log_list') # 回覆維修進度 class LogReply(UpdateView): model = LogItem # 回覆時僅顯示相關欄位 fields = ['handler', 'status', 'comment'] def get_success_url(self): return reverse('log_view', kwargs={'pk': self.object.id})

頁面範本

網站基底頁面範本

建立放置頁面範本的資料夾 repair/log/templates 以及 repair/log/templates/log

為了維持專案網站外觀的一致性,先新增網站基底範本 repair/log/templates/base.html,並引入 Bootstrap CSS 框架與 FontAwesome 框架:

<!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" %} {% block content %}{% endblock %} </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <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>

新增導覽列範本檔 repair/log/templates/navbar.html

<!-- Navbar begin //--> <nav class="navbar navbar-expand-sm navbar-light bg-light mb-4 shadow-sm"> <!-- 網站標誌 --> <div class="navbar-brand"> <i class="fas fa-wrench"></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 nav-pills"> <li class="nav-item"> <a href="{% url 'log_list' %}" class="nav-link"> <i class="fas fa-list-alt"></i> 報修記錄 </a> </li> <li class="nav-item"> <a href="{% url 'log_create' %}" class="nav-link"> <i class="fas fa-edit"></i> 我要報修 </a> </li> </ul> </div> </nav> <!-- Navbar end //-->

報修記錄列表

建立報修記錄列表範本檔 repair/log/templates/log/logitem_list.html

{% extends 'base.html' %} {% block content %} <table class="table table-sm table-hover"> <thead> <tr> <th>報修時間</th> <th>報修主旨</th> <th>報修人</th> <th>狀態</th> <th>更新時間<th> </tr> </thead> <tbody> {% for item in logitem_list %} <tr> <td>{{ item.ctime }}</td> <td> <a href="{{ item.id }}">{{ item.subject }}</a> </td> <td>{{ item.reporter }}</td> <td>{{ item.get_status_display }}</td> <td>{{ logitem.utime }}</td> </tr> {% endfor %} </tbody> </table> {% endblock %}

新增報修記錄

建立新增報修記錄範本檔 repair/log/templates/log/logitem_form.html

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

</form> <script> // 取得表格內的所有輸入元件(<input>與<textarea>) var inputs = document.querySelectorAll('table input, table textarea'); inputs.forEach(function(item) { // 為每一個輸入元件新增套用 form-control 類別 item.classList.add('form-control'); }); </script> {% endblock %}

檢視報修記錄

新增檔案 repair/log/temapltes/web/logitem_detail.html

{% extends "base.html" %} {% block content %} <div class="card"> <div class="card-header"> <div class="h3">{{ content.subject }}</div> </div> <div class="card-body"> <div class="card-text"> {{ content.description|linebreaks }} </div> </div> <div class="card-footer card-text d-flex justify-content-between text-muted"> <small title="報修人"><i class="fas fa-user"></i> {{ content.reporter }} <i class="fas fa-phone"></i> {{ content.phone }}</small> <small><i class="far fa-clock"></i> {{ content.publication_date }}</small> </div> </div> <hr> <div class="card mt-3"> <div class="card-header d-flex justify-content-between"> <div class="h3">處理狀況說明</div> <div>{{ content.get_status_display }}</div> </div> <div class="card-body">{{ content.comment|linebreaks }}</div> <div class="card-footer card-text d-flex justify-content-between text-muted"> <small title="處理人員"><i class="fas fa-user-md"></i> {{ content.handler }}</small> <small><i class="far fa-clock"></i> {{ content.handle_date }}</small> </div> </div> {% endblock %}

分頁顯示

在報修項目列表的頁面中,當系統上線時間一長,累積的報修事項過多時,一般都會採用分頁顯示的方式來顯示列表,避免在列表頁面上顯示過多資料。

開啟 repair/log/views.py ,修改 LogList

class LogList(ListView): model = LogItem ordering = ['-id'] paginate_by = 15

新增分頁控制項範本 repair/log/templates/pagination.html

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

接下來在報修項目列表的頁面範本中引入剛才建好的分頁控制項範本,這樣在列表的頁面上,若資料筆數超過指定數量,就會出現分頁控制的連結。

請修改 repair/log/templates/log/logitem_list.html

{% extends 'base.html' %} {% block content %} <table class="table table-sm table-hover"> <thead> <tr> <th>報修時間</th> <th>報修主旨</th> <th>報修人</th> <th>狀態</th> <th>最後更新時間</th> </tr> </thead> <tbody> {% for item in logitem_list %} <tr> <td>{{ item.ctime }}</td> <td> <a href="{{ item.id }}">{{ item.subject }}</a> </td> <td>{{ item.reporter }}</td> <td>{{ item.get_status_display }}</td> <td>{{ item.utime }}</td> </tr> {% endfor %} </tbody> </table> {% include 'pagination.html' %} {% endblock %}

突顯報修記錄的狀態

報修記錄列表以及檢視報修記錄的頁面中,報修記錄的處理狀態是以純文字的方式呈現,如果要突顯處理狀態的話,可以考慮使用 Bootstrap 的徽章(Badge)來標示。

徽章(Badge)

要將網頁上的元件以 Bootstrap 的徽章方式呈現的話,該元件需套用 badge badge-{type} 類別,其中 {type} 的種類共有 primarysecondarysuccessdangerwarninginfolight 以及 dark 等,各種類的徽章的外觀大致如下圖所示:

假設要讓「待處理」、「處理中」、「已處理」等 3 種處理狀態分別套用 badge-dangerbadge-warning 以及 badge-success 的話,可以將 repair/log/templates/log/logitem_list.html第 22 行由原本的:

<td>{{ item.get_status_display }}</td>

修改為

<td> {% if item.status == 0 %} <span class="badge badge-danger"> {% elif item.status == 1 %} <span class="badge badge-warning"> {% else %} <span class="badge badge-success"> {% endif %} {{ item.get_status_display }} </span> </td>

統一指定處理狀態要套用的 CSS 類別

既然要套用的類別與 item.status 有對應關係,這邊提供另一種做法,乾脆直接在 LogItem 這個 model 中定義一個一個成員函式,可以根據 status 的值傳回對應的 CSS 類別字串。

修改資料模型的定義檔 repair/log/models.py

from django.db import models # Create your models here. class LogItem(models.Model): # 處理進度的選項清單 ST_OPTIONS = [ (0, '待處理'), (1, '處理中'), (2, '已結案'), ] # 報修主旨 subject = models.CharField('報修主旨', max_length=255) # 報修內容 description = models.TextField('報修內容') # 報修人 reporter = models.CharField('報修人', max_length=30) # 聯絡電話 phone = models.CharField('聯絡電話', max_length=30) # 報修時間 ctime = models.DateTimeField('報修時間', auto_now_add=True) # -------------------------------------------------- # 處理人員 handler = models.CharField('處理人員', max_length=30) # 處理進度 status = models.IntegerField( '處理進度', default=0, choices=ST_OPTIONS ) # 處理說明 comment = models.TextField('處理說明') # 最後更新時間 utime = models.DateTimeField('最後更新時間', auto_now=True) def __str__(self): return self.subject # 根據 status 的值傳回對應的 class 字串, # 0 傳回 'danger', 1 傳回 'warning', 2 傳回 'success' def get_status_class(self): return ['danger', 'warning', 'success'][self.status]

接著修改報修項目列表頁面範本 repair/log/templates/log/logitem_list.html

{% extends 'base.html' %} {% block content %} <table class="table table-sm table-hover"> <thead> <tr> <th>報修時間</th> <th>報修主旨</th> <th>報修人</th> <th>狀態</th> <th>最後更新時間</th> </tr> </thead> <tbody> {% for item in logitem_list %} <tr> <td>{{ item.ctime }}</td> <td> <a href="{{ item.id }}">{{ item.subject }}</a> </td> <td>{{ item.reporter }}</td> <td> <span class="badge badge-{{ item.get_status_class }}"> {{ item.get_status_display }} </span> </td> <td>{{ item.utime }}</td> </tr> {% endfor %} </tbody> </table> {% include 'pagination.html' %} {% endblock %}

套用相同方式對 repair/log/templates/log/logitem_detail.html 進行修改:

{% extends 'base.html' %} {% block content %} <div class="card"> <div class="card-header"> <div class="h3">{{ logitem.subject }}</div> </div> <div class="card-body"> <div class="card-text"> {{ logitem.description|linebreaks }} </div> </div> <div class="card-footer card-text d-flex justify-content-between text-muted"> <small title="報修人"><i class="fas fa-user"></i> {{ logitem.reporter }} <i class="fas fa-phone"></i> {{ logitem.phone }}</small> <small><i class="far fa-clock"></i> {{ logitem.ctime }}</small> </div> </div> <hr> <div class="card mt-3"> <div class="card-header d-flex justify-content-between"> <div class="h3">處理狀況說明</div> <div> <span class="badge badge-{{ logitem.get_status_class }}"> {{ logitem.get_status_display }} </span> </div> </div> <div class="card-body">{{ logitem.comment|linebreaks }}</div> <div class="card-footer card-text d-flex justify-content-between text-muted"> <small title="處理人員"><i class="fas fa-user-md"></i> {{ logitem.handler }}</small> <small><i class="far fa-clock"></i> {{ logitem.utime }}</small> </div> </div> {% endblock %}