借還書登記

資料表定義

修改 library/log/models.py,新增書籍借閱紀錄的資料模型:

from django.db.models import * from book.models import * from reader.models import * # Create your models here. # 借閱紀錄 class Log(Model): reader = ForeignKey(Reader, CASCADE) book = ForeignKey(Book, CASCADE) checkout = DateTimeField('借閱時間', auto_now_add=True) returned = DateTimeField('歸還時間', null=True) # 以 null 代表尚未歸還 def __str__(self): return "{} | {} | {}".format( self.checkout, self.reader.realname, self.book.title )

新增完資料庫表格定義後,在終端機下指令讓 python 依上述定義對資料庫進行變更。

python manage.py makemigrations python manage.py migrate

借還書流程說明

借書的時候,需要登錄讀者以及其所欲借書籍,整個過程可區分為3個階段:

  1. 選擇讀者,可鍵入關鍵字依姓名來篩選讀者
  2. 選擇書籍,可鍵入關鍵字依書名來篩選可外借之書籍
  3. 完成借書登錄,返回第 2 階段,為同一讀者選擇下一本欲借閱的書籍
階段 主要作業 路徑
1 選擇讀者 /log/checkout/
2 選擇書籍 /log/checkout/<reader_id>/
3 借書登錄 /log/checkout/<reader_id>/<book_id>/

還書時,只要從未歸還的借閱紀錄中選擇要還書的紀錄就可以了,分為以下 2 個步驟:

  1. 選擇欲歸還的書籍,這可由借閱紀錄的 returned 欄位為空值(null)來篩選
  2. 完成還書登錄,為所點選的借閱記錄的 returned 欄位填入當下的時間再回存,返回第 1 步驟再選擇下一本欲歸還的書籍
階段 主要作業 路徑
1 選擇書籍 /log/return/
2 還書登錄 /log/return/<log_id>/

新增路徑對應

新增路徑規則檔 library/log/urls.py,內容如下:

from django.urls import path from .views import * urlpatterns = [ path('', LogList.as_view(), name='log_list'), path('checkout/', CheckoutReader.as_view(), name='checkout_reader'), path('checkout/<int:rid>/', CheckoutBook.as_view(), name='checkout_book'), path('checkout/<int:rid>/<int:bid>/', CheckoutLog.as_view(), name='checkout_log'), path('return/', ReturnBook.as_view(), name='return_book'), path('return/<int:lid>/', ReturnLog.as_view(), name='return_log'), ]

定義處理視圖

開啟 library/log/views.py,將內容修改如下,因篇幅較長,以下將程式碼分段解說:

from django.urls import reverse from django.views.generic import * from django.contrib.auth.mixins import LoginRequiredMixin from datetime import datetime from reader.models import Reader from book.models import Book from .models import Log # 借閱記錄列表 class LogList(LoginRequiredMixin, ListView): model = Log ordering = ['-checkout'] paginate_by = 20
# 借書階段1:選擇讀者 class CheckoutReader(LoginRequiredMixin, ListView): model = Reader paginate_by = 20 template_name = 'log/checkout_reader_list.html' def get_queryset(self): query = self.request.GET.get('query') if query: readers = Reader.objects.filter(realname__icontains=query) else: readers = Reader.objects return readers.order_by('realname') def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['query'] = self.request.GET.get('query') or "" return ctx
    ```
    欄位名稱__比對方式=比對值
    ```
    以此例來說,這裡用 `realname` 欄位的內容來進行篩選,比對的方式為 `icontains`,意思是不區分大小寫的包含,只要 `realname` 值裡有包含等號後面比對值指定的內容就會通過篩選
- ++第 27 行++,將結果依 `realname` 排序過再回傳
# 借書階段2:選擇書籍 class CheckoutBook(LoginRequiredMixin, ListView): model = Book paginate_by = 5 template_name = 'log/checkout_book_list.html' def get_queryset(self): query = self.request.GET.get('query') if query: books = Book.objects.filter(title__icontains=query) else: books = Book.objects return books.exclude( log__checkout__isnull=False, log__returned__isnull=True ).order_by('title') def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) curr_reader = Reader.objects.get(id=self.kwargs['rid']) ctx['query'] = self.request.GET.get('query') or "" ctx['reader'] = curr_reader ctx['borrowing'] = curr_reader.log_set.filter( returned__isnull=True ).select_related('book') return ctx
# 借書階段3:借書登錄 class CheckoutLog(LoginRequiredMixin, RedirectView): def get_redirect_url(self, **kwargs): reader = Reader.objects.get(id=self.kwargs['rid']) book = Book.objects.get(id=self.kwargs['bid']) log = Log(reader=reader, book=book) log.save() return reverse('checkout_book', kwargs={'rid': reader.id})
# 還書階段1:借閱中書籍列表 class ReturnBook(LoginRequiredMixin, ListView): model = Log paginate_by = 20 template_name = 'log/return_book_list.html' def get_queryset(self): query = self.request.GET.get('query') if query: logs = Log.objects.filter(book__title__icontains=query) else: logs = Log.objects return logs.exclude( returned__isnull=False ).select_related('book', 'reader') def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['query'] = self.request.GET.get('query') or "" return ctx
# 還書階段2:還書登記 class ReturnLog(LoginRequiredMixin, RedirectView): def get_redirect_url(self, **kwargs): log = Log.objects.get(id=self.kwargs['lid']) log.returned = datetime.now() log.save() return reverse('return_book')

建立借書頁面範本

導覽列新增借書連結

修改 library/templates/navbar.html,新增第 23 - 27 行將借閱紀錄的路徑加到導覽列:

{% if user.is_authenticated %} <li class="nav-item"> <a href="{% url 'book_list' %}" class="nav-link"> <i class="fas fa-book"></i> 圖書列表 </a> </li> <li class="nav-item"> <a href="{% url 'reader_list' %}" class="nav-link"> <i class="fas fa-book-reader"></i> 讀者列表 </a> </li> <li class="nav-item"> <a href="{% url 'log_list' %}" class="nav-link"> <i class="fas fa-list"></i> 借閱紀錄 </a> </li> <li class="nav-item"> <a href="{% url 'logout' %}" class="nav-link"> <i class="fas fa-sign-out-alt"></i> 登出 {{ user.username }} </a> </li> {% else %}

借閱紀錄列表

請先新增 library/templates/log 資料來,用來存放應用程式 Log 所需的頁面範本檔案。接著建立借閱紀錄列表範本檔 library/templates/log/log_list.html

{% extends "base.html" %} {% block content %} <div class="mb-1"> <a href="{% url 'checkout_reader' %}" class="btn btn-sm btn-primary"> <i class="fas fa-address-book"></i> 借書 </a> <a href="{% url 'return_book' %}" class="btn btn-sm btn-primary"> <i class="fas fa-undo"></i> 還書 </a> </div> <div id="log-list"> <table class="table table-sm"> <thead> <tr> <th>借閱時間</th> <th>書籍</th> <th>借閱人</th> <th>歸還時間</th> </tr> </thead> <tbody> {% for log in log_list %} <tr> <td>{{ log.checkout|date:"Y/m/d H:i" }}</td> <td> <a href="{% url 'book_view' log.book.id %}"> {{ log.book.title }} </a> </td> <td> <a href="{% url 'reader_view' log.reader.id %}"> {{ log.reader.realname }} </a> </td> <td>{{ log.returned|date:"Y/m/d H:i" }}</td> </tr> {% endfor %} </tbody> </table> </div> {% include "pagination.html" %} {% endblock %}

借書階段1:選擇讀者

新增頁面範本 library/templates/log/checkout_reader_list.html,用來產生借書選擇讀者的頁面:

{% extends "base.html" %} {% block content %} <div class="card"> <div class="card-header"> <form action="" method="get"> <div class="form-inline form-group"> <label>篩選讀者:</label> <input type="text" name="query" class="form-control" placeholder="請輸入關鍵字..." value="{{ query }}" /> <input type="submit" class="form-control btn btn-primary" value="篩選""/> </div> </form> </div> <div id="reader-list" class="card-body"> {% for reader in reader_list %} <a href="{% url 'checkout_book' reader.id %}" class="btn btn-warning"> {{ reader.realname }} </a> {% endfor %} </div> <div class="card-footer"> {% include "pagination.html" %} </div> </div> {% endblock %}

借書階段2:選擇書籍

新增範本 library/templates/log/checkout_book_list.html,用來產生指定讀者後,選擇欲借書籍的頁面:

{% extends "base.html" %} {% block content %} <div class="card"> <div class="card-header"> <a href="{% url 'reader_view' reader.id %}">{{ reader.realname }}</a> 借閱中的書籍 </div> <div class="card-body"> {% for log in borrowing %} <div> {{ log.checkout|date:"Y/m/d H:i" }} <a href="{% url 'book_view' log.book.id %}">{{ log.book.title }}</a> </div> {% endfor %} </div> </div> <hr> <div class="card"> <div class="card-header"> <form action="" method="get"> <div class="form-inline form-group"> <label>查詢書籍:</label> <input type="text" name="query" class="form-control" placeholder="請輸入關鍵字..." value="{{ query }}"/> <input type="submit" class="form-control btn btn-primary" value="送出"/> </div> </form> </div> <div id="book-list" class="card-body card-group"> {% for book in book_list %} <div class="card shadow-sm"> <a href="{% url 'checkout_log' reader.id book.id %}"> <img src="{{ book.preface.url }}" alt="{{ book.title }}" class="card-img-top"> </a> <div class="card-body"> <div class="card-title"> <a href="{% url 'checkout_log' reader.id book.id %}">{{ book.title }}</a> </div> <div class="card-text">{{ book.author }}</div> </div> </div> {% endfor %} </div> <div class="card-footer"> {% include "pagination.html" %} </div> </div> {% endblock %}

借書階段3:登錄借閱

登錄借閱後,重新導向回到前一頁繼續選擇同一讀者欲借之書籍,所以不另外建立頁面範本。

還書步驟1:選擇欲歸還的紀錄

{% extends "base.html" %} {% block content %} <div class="card"> <div class="card-header"> <form action="" method="get"> <div class="form-inline form-group"> <label>篩選書籍:</label> <input type="text" name="query" class="form-control" placeholder="請輸入關鍵字..." value="{{ query }}" /> <input type="submit" class="form-control btn btn-primary" value="篩選""/> </div> </form> </div> <div id="log-list" class="card-body"> <table class="table table-sm"> <thead> <tr> <th>借閱時間</th> <th>書籍</th> <th>借閱人</th> <th></th> </tr> </thead> <tbody> {% for log in log_list %} <tr> <td>{{ log.checkout|date:"Y/m/d H:i" }}</td> <td> <a href="{% url 'book_view' log.book.id %}"> {{ log.book.title }} </a> </td> <td> <a href="{% url 'reader_view' log.reader.id %}"> {{ log.reader.realname }} </a> </td> <td> <a href="{% url 'return_log' log.id %}" class="btn btn-sm btn-primary"> <i class="fas fa-undo"></i> 歸還 </a> </td> </tr> {% endfor %} </tbody> </table> </div> <div class="card-footer"> {% include "pagination.html" %} </div> </div>{% endblock %}

還書步驟2:完成還書登記

還書登錄後重新導向至還書步驟1的頁面,以便連續還書的作業。