Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Book Checkout and Return Features for PyQt5 Library Management Systems

Tech 1

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_())

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.