Implementing Book Checkout and Return Features for PyQt5 Library Management Systems
Checkout Workflow Validation Rules
All checkout operations follow the below validation sequence to ensure data consistency:
- When a user inputs a book ID, the system automatically queries the book database and populates book metadata (title, author, category, publisher, publish date) if a matching record exists
- Empty or invalid book IDs trigger a warning alert and block further operation
- Users are restricted to a maximum of 5 active checked-out books; exceeding this limit blocks checkout
- Duplicate checkout of the same book (with an active unreturned record) is not allowed
- Successful checkout triggers three database operations: increment the user's borrowed book count, update the book's borrow count and reduce availible stock, and insert a new checkout record to the user-book relation table
- A success notification is displayed after all database operations complete successfully
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import qdarkstyle
import time
from PyQt5.QtSql import *
from db.userInfoManager import UserBookManager
from db.userInfoManager import BookDbManager
from db.userInfoManager import UserDbManager
import assets.icons
class BookCheckoutDialog(QDialog):
checkout_success = pyqtSignal()
def __init__(self, patron_id, parent=None):
super().__init__(parent)
self.patron_id = patron_id
self.init_ui()
self.setWindowModality(Qt.WindowModal)
self.setWindowTitle("Book Checkout")
self.user_book_ops = UserBookManager()
self.book_ops = BookDbManager()
self.user_ops = UserDbManager()
def init_ui(self):
BOOK_CATEGORIES = ["Philosophy", "Social Science", "Politics", "Law", "Military",
"Economics", "Culture", "Education", "Sports", "Linguistics",
"Art", "History", "Geography", "Astronomy", "Biology",
"Healthcare", "Agriculture"]
self.resize(320, 420)
self.form_layout = QFormLayout()
self.setLayout(self.form_layout)
self.patron_label = QLabel("Patron ID:")
self.patron_id_display = QLabel(self.patron_id)
self.header_label = QLabel(" Book Checkout")
self.title_label = QLabel("Book Title:")
self.book_id_label = QLabel("Book ID:")
self.author_label = QLabel("Author:")
self.category_label = QLabel("Category:")
self.publisher_label = QLabel("Publisher:")
self.pub_date_label = QLabel("Publish Date:")
self.submit_checkout_btn = QPushButton("Confirm Checkout")
self.title_input = QLineEdit()
self.book_id_input = QLineEdit()
self.author_input = QLineEdit()
self.category_dropdown = QComboBox()
self.category_dropdown.addItems(BOOK_CATEGORIES)
self.publisher_input = QLineEdit()
self.pub_date_input = QLineEdit()
self.title_input.setMaxLength(30)
self.book_id_input.setMaxLength(10)
self.author_input.setMaxLength(20)
self.publisher_input.setMaxLength(25)
self.form_layout.addRow("", self.header_label)
self.form_layout.addRow(self.patron_label, self.patron_id_display)
self.form_layout.addRow(self.title_label, self.title_input)
self.form_layout.addRow(self.book_id_label, self.book_id_input)
self.form_layout.addRow(self.author_label, self.author_input)
self.form_layout.addRow(self.category_label, self.category_dropdown)
self.form_layout.addRow(self.publisher_label, self.publisher_input)
self.form_layout.addRow(self.pub_date_label, self.pub_date_input)
self.form_layout.addRow("", self.submit_checkout_btn)
font = QFont()
font.setPixelSize(22)
self.header_label.setFont(font)
font.setPixelSize(16)
self.patron_id_display.setFont(font)
font.setPixelSize(14)
for field_label in [self.patron_label, self.title_label, self.book_id_label, self.author_label, self.category_label, self.publisher_label, self.pub_date_label]:
field_label.setFont(font)
for readonly_field in [self.title_input, self.author_input, self.publisher_input, self.pub_date_input]:
readonly_field.setFont(font)
readonly_field.setReadOnly(True)
readonly_field.setStyleSheet("background-color: #2d2d2d; color: #ffffff;")
self.book_id_input.setFont(font)
self.category_dropdown.setFont(font)
self.category_dropdown.setStyleSheet("background-color: #2d2d2d; color: #ffffff;")
font.setPixelSize(16)
self.submit_checkout_btn.setFont(font)
self.submit_checkout_btn.setFixedSize(150, 36)
self.header_label.setMargin(10)
self.form_layout.setVerticalSpacing(12)
self.submit_checkout_btn.clicked.connect(self.handle_checkout_submit)
self.book_id_input.textChanged.connect(self.handle_book_id_change)
self.book_id_input.returnPressed.connect(self.handle_checkout_submit)
def handle_checkout_submit(self):
book_id = self.book_id_input.text().strip()
if not book_id:
QMessageBox.warning(self, "Warning", "Please enter a valid book ID", QMessageBox.Yes)
return
book_data = self.book_ops.querybyBookID(book_id)
if not book_data:
QMessageBox.warning(self, "Warning", "No matching book found for the entered ID", QMessageBox.Yes)
return
active_checkouts = self.user_book_ops.countBorrowNum(self.patron_id)
current_checkout_count = active_checkouts[0][0] if active_checkouts else 0
if current_checkout_count >= 5:
QMessageBox.warning(self, "Warning", "You have reached the maximum limit of 5 checked out books", QMessageBox.Yes)
return
existing_checkout = self.user_book_ops.borrowStatus(self.patron_id, book_id)
if existing_checkout[0][0] > 0:
QMessageBox.warning(self, "Warning", "You have already checked out this book and not returned it yet", QMessageBox.Yes)
return
self.user_ops.borrowOrReturnBook(self.patron_id, borrow=1)
self.book_ops.borrowOrReturnBook(book_id, borrowflag=1)
checkout_date = time.strftime('%Y-%m-%d', time.localtime())
self.user_book_ops.borrowOrReturnBook(self.patron_id, book_id, checkout_date, borrowflag=1)
QMessageBox.information(self, "Success", "Book checked out successfully!", QMessageBox.Yes)
self.checkout_success.emit()
self.close()
def handle_book_id_change(self):
book_id = self.book_id_input.text().strip()
if not book_id:
self.title_input.clear()
self.author_input.clear()
self.publisher_input.clear()
self.pub_date_input.clear()
return
book_data = self.book_ops.querybyBookID(book_id)
if book_data:
self.title_input.setText(book_data[0][0])
self.author_input.setText(book_data[0][2])
self.category_dropdown.setCurrentText(book_data[0][3])
self.publisher_input.setText(book_data[0][4])
self.pub_date_input.setText(book_data[0][5])
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/icons/app_icon.png"))
app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
checkout_window = BookCheckoutDialog("test_user_001")
checkout_window.show()
sys.exit(app.exec_())
Return Workflow Validation Rules
Return operations follow the below validation logic:
- Empty book ID input triggers a input validation warning
- If no active unreturned checkout record exists for the input book ID under the current user, a notification is displayed indicating no return is required
- Successful return triggers three database operations: decrement the user's active borrowed count, increment the book's available stock, and update the corresponding user-book record with the return date to mark it as completed
- A success notification is displayed after all operations complete
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import qdarkstyle
import time
from PyQt5.QtSql import *
from db.userInfoManager import UserBookManager
from db.userInfoManager import BookDbManager
from db.userInfoManager import UserDbManager
import assets.icons
class BookReturnDialog(QDialog):
return_success = pyqtSignal()
def __init__(self, patron_id, parent=None):
super().__init__(parent)
self.patron_id = patron_id
self.init_ui()
self.setWindowModality(Qt.WindowModal)
self.setWindowTitle("Book Return")
self.user_book_ops = UserBookManager()
self.book_ops = BookDbManager()
self.user_ops = UserDbManager()
def init_ui(self):
BOOK_CATEGORIES = ["Philosophy", "Social Science", "Politics", "Law", "Military",
"Economics", "Culture", "Education", "Sports", "Linguistics",
"Art", "History", "Geography", "Astronomy", "Biology",
"Healthcare", "Agriculture"]
self.resize(320, 420)
self.form_layout = QFormLayout()
self.setLayout(self.form_layout)
self.patron_label = QLabel("Patron ID:")
self.patron_id_display = QLabel(self.patron_id)
self.header_label = QLabel(" Book Return")
self.title_label = QLabel("Book Title:")
self.book_id_label = QLabel("Book ID:")
self.author_label = QLabel("Author:")
self.category_label = QLabel("Category:")
self.publisher_label = QLabel("Publisher:")
self.pub_date_label = QLabel("Publish Date:")
self.submit_return_btn = QPushButton("Confirm Return")
self.title_input = QLineEdit()
self.book_id_input = QLineEdit()
self.author_input = QLineEdit()
self.category_dropdown = QComboBox()
self.category_dropdown.addItems(BOOK_CATEGORIES)
self.publisher_input = QLineEdit()
self.pub_date_input = QLineEdit()
self.title_input.setMaxLength(30)
self.book_id_input.setMaxLength(10)
self.author_input.setMaxLength(20)
self.publisher_input.setMaxLength(25)
self.form_layout.addRow("", self.header_label)
self.form_layout.addRow(self.patron_label, self.patron_id_display)
self.form_layout.addRow(self.title_label, self.title_input)
self.form_layout.addRow(self.book_id_label, self.book_id_input)
self.form_layout.addRow(self.author_label, self.author_input)
self.form_layout.addRow(self.category_label, self.category_dropdown)
self.form_layout.addRow(self.publisher_label, self.publisher_input)
self.form_layout.addRow(self.pub_date_label, self.pub_date_input)
self.form_layout.addRow("", self.submit_return_btn)
font = QFont()
font.setPixelSize(22)
self.header_label.setFont(font)
font.setPixelSize(16)
self.patron_id_display.setFont(font)
font.setPixelSize(14)
for field_label in [self.patron_label, self.title_label, self.book_id_label, self.author_label, self.category_label, self.publisher_label, self.pub_date_label]:
field_label.setFont(font)
for readonly_field in [self.title_input, self.author_input, self.publisher_input, self.pub_date_input, self.category_dropdown]:
readonly_field.setFont(font)
readonly_field.setReadOnly(True) if hasattr(readonly_field, 'setReadOnly') else None
readonly_field.setStyleSheet("background-color: #2d2d2d; color: #ffffff;")
self.book_id_input.setFont(font)
font.setPixelSize(16)
self.submit_return_btn.setFont(font)
self.submit_return_btn.setFixedSize(150, 36)
self.header_label.setMargin(10)
self.form_layout.setVerticalSpacing(12)
self.submit_return_btn.clicked.connect(self.handle_return_submit)
self.book_id_input.textChanged.connect(self.handle_book_id_change)
def handle_return_submit(self):
book_id = self.book_id_input.text().strip()
if not book_id:
QMessageBox.warning(self, "Warning", "Please enter a valid book ID", QMessageBox.Yes)
return
active_checkout = self.user_book_ops.borrowStatus(self.patron_id, book_id)
if active_checkout[0][0] == 0:
QMessageBox.information(self, "Notification", "You have not checked out this book, no return required", QMessageBox.Yes)
return
self.user_ops.borrowOrReturnBook(self.patron_id, borrow=0)
self.book_ops.borrowOrReturnBook(book_id, borrowflag=0)
return_date = time.strftime('%Y-%m-%d', time.localtime())
self.user_book_ops.borrowOrReturnBook(self.patron_id, book_id, return_date, borrowflag=0)
QMessageBox.information(self, "Success", "Book returned successfully!", QMessageBox.Yes)
self.return_success.emit()
self.close()
def handle_book_id_change(self):
book_id = self.book_id_input.text().strip()
if not book_id:
self.title_input.clear()
self.author_input.clear()
self.publisher_input.clear()
self.pub_date_input.clear()
return
active_checkout = self.user_book_ops.borrowStatus(self.patron_id, book_id)
if active_checkout[0][0] > 0:
book_data = self.book_ops.querybyBookID(book_id)
if book_data:
self.title_input.setText(book_data[0][0])
self.author_input.setText(book_data[0][2])
self.category_dropdown.setCurrentText(book_data[0][3])
self.publisher_input.setText(book_data[0][4])
self.pub_date_input.setText(book_data[0][5])
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/icons/app_icon.png"))
app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
return_window = BookReturnDialog("test_user_001")
return_window.show()
sys.exit(app.exec_())