借還書登記
資料表定義
修改 library/log/models.py
,新增書籍借閱紀錄的資料模型:
from django.db.models import *
from book.models import *
from reader.models import *
class Log(Model):
reader = ForeignKey(Reader, CASCADE)
book = ForeignKey(Book, CASCADE)
checkout = DateTimeField('借閱時間', auto_now_add=True)
returned = DateTimeField('歸還時間', null=True)
def __str__(self):
return "{} | {} | {}".format(
self.checkout,
self.reader.realname,
self.book.title
)
新增完資料庫表格定義後,在終端機下指令讓 python 依上述定義對資料庫進行變更。
python manage.py makemigrations
python manage.py migrate
借還書流程說明
借書的時候,需要登錄讀者以及其所欲借書籍,整個過程可區分為3個階段:
- 選擇讀者,可鍵入關鍵字依姓名來篩選讀者
- 選擇書籍,可鍵入關鍵字依書名來篩選可外借之書籍
- 完成借書登錄,返回第 2 階段,為同一讀者選擇下一本欲借閱的書籍
階段 |
主要作業 |
路徑 |
1 |
選擇讀者 |
/log/checkout/ |
2 |
選擇書籍 |
/log/checkout/<reader_id>/ |
3 |
借書登錄 |
/log/checkout/<reader_id>/<book_id>/ |
還書時,只要從未歸還的借閱紀錄中選擇要還書的紀錄就可以了,分為以下 2 個步驟:
- 選擇欲歸還的書籍,這可由借閱紀錄的
returned
欄位為空值(null
)來篩選
- 完成還書登錄,為所點選的借閱記錄的
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
- 第 5 - 7 行,分別從 3 個應用程式引用資料模型
- 第 12 行,依借閱時間(
checkout
)欄位的值排序,欄位前方的減號 -
表示反向排序。
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` 排序過再回傳
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
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})
- 因為完成借書登錄後,重新導向回借書第 2 階段,為同一位讀者選擇下一本欲借的書籍,所以讓
CheckoutLog
繼承 RedirectView
視圖
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
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的頁面,以便連續還書的作業。
借還書登記
資料表定義
修改
library/log/models.py
,新增書籍借閱紀錄的資料模型:新增完資料庫表格定義後,在終端機下指令讓 python 依上述定義對資料庫進行變更。
借還書流程說明
借書的時候,需要登錄讀者以及其所欲借書籍,整個過程可區分為3個階段:
/log/checkout/
/log/checkout/<reader_id>/
/log/checkout/<reader_id>/<book_id>/
還書時,只要從未歸還的借閱紀錄中選擇要還書的紀錄就可以了,分為以下 2 個步驟:
returned
欄位為空值(null
)來篩選returned
欄位填入當下的時間再回存,返回第 1 步驟再選擇下一本欲歸還的書籍/log/return/
/log/return/<log_id>/
新增路徑對應
新增路徑規則檔
library/log/urls.py
,內容如下:定義處理視圖
開啟
library/log/views.py
,將內容修改如下,因篇幅較長,以下將程式碼分段解說:checkout
)欄位的值排序,欄位前方的減號-
表示反向排序。CheckoutLog
繼承RedirectView
視圖建立借書頁面範本
導覽列新增借書連結
修改
library/templates/navbar.html
,新增第 23 - 27 行將借閱紀錄的路徑加到導覽列:借閱紀錄列表
請先新增
library/templates/log
資料來,用來存放應用程式Log
所需的頁面範本檔案。接著建立借閱紀錄列表範本檔library/templates/log/log_list.html
:借書階段1:選擇讀者
新增頁面範本
library/templates/log/checkout_reader_list.html
,用來產生借書選擇讀者的頁面:借書階段2:選擇書籍
新增範本
library/templates/log/checkout_book_list.html
,用來產生指定讀者後,選擇欲借書籍的頁面:借書階段3:登錄借閱
登錄借閱後,重新導向回到前一頁繼續選擇同一讀者欲借之書籍,所以不另外建立頁面範本。
還書步驟1:選擇欲歸還的紀錄
還書步驟2:完成還書登記
還書登錄後重新導向至還書步驟1的頁面,以便連續還書的作業。