[main] WIP major rework of current design, database management and other things
This commit is contained in:
parent
4bf43721e3
commit
a2ffd8aa3b
84
src/app.py
84
src/app.py
@ -1,38 +1,60 @@
|
|||||||
import sys
|
import sys
|
||||||
from PySide6 import QtWidgets, QtCore
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QMessageBox, QApplication
|
||||||
|
|
||||||
|
from utils.config import UserConfig
|
||||||
|
from utils.database import DatabaseManager
|
||||||
|
from utils.errors.database_config_error import DatabaseConfigError
|
||||||
|
from utils.setup_logger import setup_logger
|
||||||
|
|
||||||
from ui.window import LibraryWindow
|
from ui.window import LibraryWindow
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryApp():
|
||||||
|
def __init__(self, user_config: UserConfig | None = None):
|
||||||
|
setup_logger()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logger.info("Starting")
|
||||||
|
|
||||||
|
self.qt_app = QApplication([])
|
||||||
|
self.user_config = user_config
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.database_manger = DatabaseManager()
|
||||||
|
except DatabaseConfigError as e:
|
||||||
|
detail_text = f"Invalid config: {e.config_name}"
|
||||||
|
self.show_error(e.message, detail_text=detail_text)
|
||||||
|
sys.exit(1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.show_error("Configuration not found")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.window = LibraryWindow()
|
||||||
|
|
||||||
|
def run(self) -> int:
|
||||||
|
self.window.show()
|
||||||
|
status = self.qt_app.exec()
|
||||||
|
self.cleanup()
|
||||||
|
self.logger.info("Exitting")
|
||||||
|
return status
|
||||||
|
|
||||||
|
def show_error(self, text: str, detail_text: str = ""):
|
||||||
|
error_dialog = QMessageBox()
|
||||||
|
error_dialog.setIcon(QMessageBox.Icon.Critical)
|
||||||
|
error_dialog.setWindowTitle("Error")
|
||||||
|
error_dialog.setText(text)
|
||||||
|
if detail_text:
|
||||||
|
error_dialog.setInformativeText(detail_text)
|
||||||
|
error_dialog.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||||||
|
error_dialog.exec()
|
||||||
|
|
||||||
|
def cleanup(self) -> None:
|
||||||
|
self.logger.info("Cleaning up")
|
||||||
|
self.database_manger.cleanup()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QtWidgets.QApplication([])
|
library_app = LibraryApp()
|
||||||
window = LibraryWindow()
|
sys.exit(library_app.run())
|
||||||
window.show()
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
# from sqlalchemy import create_engine
|
|
||||||
# from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
# from models.book_overview_view import BookOverviewView
|
|
||||||
|
|
||||||
# # Replace with your MySQL database credentials
|
|
||||||
# DATABASE_URI = 'mysql+mysqlconnector://username:password@localhost:3306/library'
|
|
||||||
|
|
||||||
# # Create the engine
|
|
||||||
# engine = create_engine(DATABASE_URI)
|
|
||||||
|
|
||||||
# from models.base import Base
|
|
||||||
# Base.metadata.create_all(engine)
|
|
||||||
|
|
||||||
# # Create a configured session class
|
|
||||||
# SessionLocal = sessionmaker(bind=engine)
|
|
||||||
|
|
||||||
# # Create a session instance
|
|
||||||
# session = SessionLocal()
|
|
||||||
|
|
||||||
# books = session.query(BookOverviewView).all()
|
|
||||||
# for book in books:
|
|
||||||
# print(book.title, book.author_name, book.categories)
|
|
||||||
|
|
||||||
# session.close()
|
|
||||||
|
@ -7,6 +7,6 @@ from .member import Member
|
|||||||
from .librarian import Librarian
|
from .librarian import Librarian
|
||||||
from .loan import Loan
|
from .loan import Loan
|
||||||
|
|
||||||
from .book_overview_view import BookOverviewView
|
from .book_overview import BooksOverview
|
||||||
|
|
||||||
__all__ = ["Author", "Book", "BookCategory", "BookCategoryLink", "Member", "Librarian", "Loan", "BookOverviewView"]
|
__all__ = ["Author", "Book", "BookCategory", "BookCategoryLink", "Member", "Librarian", "Loan", "BookOverviewView"]
|
||||||
|
31
src/models/book_overview.py
Normal file
31
src/models/book_overview.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from sqlalchemy import Column, String, TIMESTAMP, Integer, Text, Enum
|
||||||
|
|
||||||
|
from .base import Base
|
||||||
|
|
||||||
|
from models.book import BookStatusEnum
|
||||||
|
|
||||||
|
class BooksOverview(Base):
|
||||||
|
__tablename__ = 'books_overview'
|
||||||
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
title = Column(String, nullable=False)
|
||||||
|
author_name = Column(String, nullable=False)
|
||||||
|
author_id = Column(Integer, nullable=False)
|
||||||
|
categories = Column(Text, nullable=True)
|
||||||
|
year_published = Column(Integer, nullable=True)
|
||||||
|
isbn = Column(String, nullable=True)
|
||||||
|
status = Column(Enum(BookStatusEnum), nullable=False)
|
||||||
|
created_at = Column(TIMESTAMP, nullable=False)
|
||||||
|
borrower_name = Column(String, nullable=True)
|
||||||
|
member_id = Column(Integer, nullable=True)
|
||||||
|
librarian_name = Column(String, nullable=True)
|
||||||
|
librarian_id = Column(Integer, nullable=True)
|
||||||
|
|
||||||
|
# This prevents accidental updates/deletes as it's a view
|
||||||
|
def __repr__(self):
|
||||||
|
return (f"<BooksOverview(id={self.id}, title={self.title}, author_name={self.author_name}, "
|
||||||
|
f"categories={self.categories}, year_published={self.year_published}, isbn={self.isbn}, "
|
||||||
|
f"status={self.status}, created_at={self.created_at}, borrower_name={self.borrower_name}, "
|
||||||
|
f"librarian_name={self.librarian_name})>")
|
@ -1,16 +0,0 @@
|
|||||||
from sqlalchemy import Column, String, TIMESTAMP, Integer
|
|
||||||
|
|
||||||
from .base import Base
|
|
||||||
|
|
||||||
class BookOverviewView(Base):
|
|
||||||
__tablename__ = 'books_overview'
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
||||||
title = Column(String())
|
|
||||||
author_name = Column(String())
|
|
||||||
categories = Column(String())
|
|
||||||
year_published = Column(String())
|
|
||||||
isbn = Column(String())
|
|
||||||
created_at = Column(TIMESTAMP())
|
|
||||||
borrower_name = Column(String())
|
|
||||||
librarian_name = Column(String())
|
|
68
src/ui/dashboard/book_card.py
Normal file
68
src/ui/dashboard/book_card.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
from PySide6.QtGui import QGuiApplication, QAction
|
||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget, QMenu, QSizePolicy, QLayout
|
||||||
|
|
||||||
|
from models.book import BookStatusEnum
|
||||||
|
|
||||||
|
from models.book_overview import BooksOverview
|
||||||
|
|
||||||
|
STATUS_TO_COLOR_MAP = {
|
||||||
|
BookStatusEnum.available: "#3c702e",
|
||||||
|
BookStatusEnum.borrowed: "#702525",
|
||||||
|
BookStatusEnum.reserved: "#bc7613"
|
||||||
|
}
|
||||||
|
class BookCard(QWidget):
|
||||||
|
def __init__(self, book_overview: BooksOverview):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Create the layout for the card
|
||||||
|
layout = QHBoxLayout(self)
|
||||||
|
layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) # Ensure minimum size is respected
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) # Avoid vertical stretching
|
||||||
|
|
||||||
|
layout.setContentsMargins(10, 10, 10, 10) # Optional: Add margins for spacing
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# Left-side content
|
||||||
|
left_side = QVBoxLayout()
|
||||||
|
layout.addLayout(left_side)
|
||||||
|
title_label = QLabel(book_overview.title)
|
||||||
|
author_label = QLabel("By: " + book_overview.author_name)
|
||||||
|
isbn_label = QLabel("ISBN: " + (book_overview.isbn or "Not Available"))
|
||||||
|
left_side.addWidget(title_label)
|
||||||
|
left_side.addWidget(author_label)
|
||||||
|
left_side.addWidget(isbn_label)
|
||||||
|
|
||||||
|
# Right-side content
|
||||||
|
right_side = QVBoxLayout()
|
||||||
|
layout.addLayout(right_side)
|
||||||
|
status_label = QLabel(str(book_overview.status))
|
||||||
|
status_label.setStyleSheet(f"color: {STATUS_TO_COLOR_MAP[book_overview.status]}")
|
||||||
|
right_side.addWidget(status_label)
|
||||||
|
|
||||||
|
if book_overview.librarian_name and book_overview.borrower_name:
|
||||||
|
borrower_label = QLabel("Borrowed: " + book_overview.borrower_name)
|
||||||
|
librarian_label = QLabel("By: " + book_overview.librarian_name)
|
||||||
|
right_side.addWidget(borrower_label)
|
||||||
|
right_side.addWidget(librarian_label)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event):
|
||||||
|
"""Override to create a custom right-click menu."""
|
||||||
|
context_menu = QMenu(self)
|
||||||
|
|
||||||
|
# Add actions to the menu
|
||||||
|
action_edit_book = context_menu.addAction("Edit Book")
|
||||||
|
action_edit_author = context_menu.addAction("Edit Author")
|
||||||
|
action_mark_returned = context_menu.addAction("Mark as Returned")
|
||||||
|
|
||||||
|
# Execute the menu and get the selected action
|
||||||
|
action = context_menu.exec_(self.mapToGlobal(event.pos()))
|
||||||
|
if action == action_edit_book:
|
||||||
|
self.label.setText("Edit Book selected")
|
||||||
|
elif action == action_edit_author:
|
||||||
|
self.label.setText("Edit Author selected")
|
||||||
|
elif action == action_mark_returned:
|
||||||
|
self.label.setText("Mark as Returned selected")
|
@ -1,27 +0,0 @@
|
|||||||
from PySide6.QtGui import QGuiApplication, QAction
|
|
||||||
from PySide6.QtQml import QQmlApplicationEngine
|
|
||||||
from PySide6 import QtWidgets, QtCore
|
|
||||||
|
|
||||||
from models.book import BookStatusEnum
|
|
||||||
|
|
||||||
STATUS_TO_COLOR_MAP = {
|
|
||||||
BookStatusEnum.available: "highlight",
|
|
||||||
BookStatusEnum.borrowed: "highlighted-text",
|
|
||||||
BookStatusEnum.reserved: "base"
|
|
||||||
}
|
|
||||||
|
|
||||||
class BookListEntry(QtWidgets.QWidget):
|
|
||||||
def __init__(self, title, status):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
layout = QtWidgets.QHBoxLayout(self)
|
|
||||||
|
|
||||||
book_label = QtWidgets.QLabel(title)
|
|
||||||
layout.addWidget(book_label)
|
|
||||||
|
|
||||||
status_label = QtWidgets.QLabel(status)
|
|
||||||
status_label.setStyleSheet(f"color: palette({STATUS_TO_COLOR_MAP[status]});")
|
|
||||||
layout.addWidget(status_label)
|
|
||||||
|
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
@ -1,92 +1,87 @@
|
|||||||
from PySide6.QtGui import QGuiApplication, QAction
|
from PySide6.QtGui import QAction
|
||||||
from PySide6.QtQml import QQmlApplicationEngine
|
from PySide6.QtWidgets import (
|
||||||
from PySide6 import QtWidgets, QtCore
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea,
|
||||||
|
QFrame, QPushButton, QMessageBox, QVBoxLayout, QScrollArea
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
from ui.settings import SettingsDialog
|
from .book_card import BookCard
|
||||||
class LibraryDashboard(QtWidgets.QWidget):
|
from models.book_overview import BooksOverview
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
class LibraryDashboard(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# Central widget and layout
|
# Central widget and layout
|
||||||
main_layout = QtWidgets.QVBoxLayout(self)
|
main_layout = QVBoxLayout(self)
|
||||||
|
|
||||||
# Title label
|
# Title label
|
||||||
title_label = QtWidgets.QLabel("Dashboard", self)
|
title_label = QLabel("Dashboard", self)
|
||||||
title_label.setAlignment(QtCore.Qt.AlignCenter)
|
title_label.setAlignment(Qt.AlignCenter)
|
||||||
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;")
|
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;")
|
||||||
main_layout.addWidget(title_label)
|
main_layout.addWidget(title_label)
|
||||||
|
|
||||||
|
|
||||||
# Available books list
|
|
||||||
available_label = QtWidgets.QLabel("Available Books")
|
|
||||||
available_label.setStyleSheet("font-size: 16px;")
|
|
||||||
main_layout.addWidget(available_label)
|
|
||||||
|
|
||||||
# Search bar
|
# Search bar
|
||||||
self.search_input = QtWidgets.QLineEdit()
|
self.search_input = QLineEdit()
|
||||||
self.search_input.setPlaceholderText("Type to search...")
|
self.search_input.setPlaceholderText("Type to search...")
|
||||||
self.search_input.textChanged.connect(self.filter_books)
|
self.search_input.textChanged.connect(self.filter_books)
|
||||||
main_layout.addWidget(self.search_input)
|
main_layout.addWidget(self.search_input)
|
||||||
|
|
||||||
|
# Scrollable area for cards
|
||||||
|
self.scroll_area = QScrollArea()
|
||||||
|
self.scroll_area.setWidgetResizable(True)
|
||||||
|
|
||||||
self.available_books_list = QtWidgets.QListWidget()
|
# Container widget for the scroll area
|
||||||
self.books = ["Book One", "Book Two", "Book Three", "Book Four",
|
self.scroll_widget = QWidget()
|
||||||
"Book Five", "Book Six", "Book Seven", "Book Eight"]
|
self.scroll_layout = QVBoxLayout(self.scroll_widget)
|
||||||
|
self.scroll_layout.setSpacing(5) # Optional: Adjust spacing between cards
|
||||||
self.available_books_list.addItems(self.books)
|
self.scroll_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.available_books_list.itemClicked.connect(self.edit_book)
|
|
||||||
self.available_books_list.setStyleSheet("""
|
|
||||||
QListWidget {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
QListWidget::item {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
QListWidget::item:hover {
|
|
||||||
background-color: palette(highlight);
|
|
||||||
color: palette(highlighted-text);
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
main_layout.addWidget(self.available_books_list)
|
|
||||||
|
|
||||||
# Borrowed books list
|
# Example cards
|
||||||
borrowed_label = QtWidgets.QLabel("Currently Borrowed Books")
|
# self.books = self.fetch_books_from_db()
|
||||||
borrowed_label.setStyleSheet("font-size: 16px;")
|
# self.book_cards = []
|
||||||
main_layout.addWidget(borrowed_label)
|
|
||||||
|
|
||||||
self.borrowed_books_list = QtWidgets.QListWidget()
|
# for book in self.books:
|
||||||
self.borrowed_books_list.addItems(["Book Two", "Book Four"])
|
# card = BookCard(book)
|
||||||
self.borrowed_books_list.itemClicked.connect(self.return_book)
|
# self.scroll_layout.addWidget(card)
|
||||||
main_layout.addWidget(self.borrowed_books_list)
|
# self.book_cards.append(card)
|
||||||
|
|
||||||
|
self.scroll_widget.setLayout(self.scroll_layout)
|
||||||
|
self.scroll_area.setWidget(self.scroll_widget)
|
||||||
|
main_layout.addWidget(self.scroll_area)
|
||||||
|
|
||||||
# Buttons for actions
|
# Buttons for actions
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
button_layout = QHBoxLayout()
|
||||||
register_member_button = QtWidgets.QPushButton("Add New Member")
|
register_member_button = QPushButton("Add New Member")
|
||||||
register_member_button.clicked.connect(self.register_member)
|
register_member_button.clicked.connect(self.register_member)
|
||||||
button_layout.addWidget(register_member_button)
|
button_layout.addWidget(register_member_button)
|
||||||
|
|
||||||
add_borrow_record_button = QtWidgets.QPushButton("Add Borrow Record")
|
add_borrow_record_button = QPushButton("Add Borrow Record")
|
||||||
add_borrow_record_button.clicked.connect(self.add_borrow_record)
|
add_borrow_record_button.clicked.connect(self.add_borrow_record)
|
||||||
button_layout.addWidget(add_borrow_record_button)
|
button_layout.addWidget(add_borrow_record_button)
|
||||||
|
|
||||||
main_layout.addLayout(button_layout)
|
main_layout.addLayout(button_layout)
|
||||||
|
|
||||||
def filter_books(self, text):
|
def filter_books(self, text):
|
||||||
"""Filter the available books list based on the search input."""
|
"""Filter the cards based on the search input."""
|
||||||
self.available_books_list.clear()
|
for card, book in zip(self.book_cards, self.books):
|
||||||
filtered_books = [book for book in self.books if text.lower() in book.lower()]
|
card.setVisible(text.lower() in book["title"].lower() or text.lower() in book["description"].lower())
|
||||||
self.available_books_list.addItems(filtered_books)
|
|
||||||
|
|
||||||
# Slots for button actions
|
|
||||||
def edit_book(self, item):
|
|
||||||
QtWidgets.QMessageBox.information(self, "Edit Book", f"Edit details for '{item.text()}'.")
|
|
||||||
|
|
||||||
def return_book(self, item):
|
|
||||||
QtWidgets.QMessageBox.information(self, "Return Book", f"Mark '{item.text()}' as returned.")
|
|
||||||
|
|
||||||
def register_member(self):
|
def register_member(self):
|
||||||
QtWidgets.QMessageBox.information(self, "Add Member", "Open dialog to register a new member.")
|
QMessageBox.information(self, "Add Member", "Open dialog to register a new member.")
|
||||||
|
|
||||||
def add_borrow_record(self):
|
def add_borrow_record(self):
|
||||||
QtWidgets.QMessageBox.information(self, "Add Borrow Record", "Open dialog to add a borrow record.")
|
QMessageBox.information(self, "Add Borrow Record", "Open dialog to add a borrow record.")
|
||||||
|
|
||||||
|
def fetch_books_from_db(self):
|
||||||
|
|
||||||
|
"""Fetch all books from the database."""
|
||||||
|
try:
|
||||||
|
session = SessionLocal()
|
||||||
|
books = session.query(BooksOverview).all()
|
||||||
|
return books
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Database Error", f"Failed to fetch books: {e}")
|
||||||
|
return []
|
@ -2,12 +2,14 @@ import sys
|
|||||||
|
|
||||||
from PySide6 import QtWidgets
|
from PySide6 import QtWidgets
|
||||||
|
|
||||||
|
from utils.config import UserConfig, TransactionLevel
|
||||||
|
|
||||||
class SettingsDialog(QtWidgets.QDialog):
|
class SettingsDialog(QtWidgets.QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.setWindowTitle("Settings")
|
self.setWindowTitle("Settings")
|
||||||
self.setMinimumSize(400, 300)
|
self.setMinimumSize(400, 100)
|
||||||
|
|
||||||
# Position the dialog relative to the parent (main window)
|
# Position the dialog relative to the parent (main window)
|
||||||
if parent:
|
if parent:
|
||||||
@ -18,29 +20,24 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
# Create layout
|
# Create layout
|
||||||
layout = QtWidgets.QVBoxLayout(self)
|
layout = QtWidgets.QVBoxLayout(self)
|
||||||
|
|
||||||
# Checkbox: Enable Notifications
|
data_mode_layout = QtWidgets.QHBoxLayout()
|
||||||
self.notifications_checkbox = QtWidgets.QCheckBox("Enable Notifications")
|
|
||||||
layout.addWidget(self.notifications_checkbox)
|
|
||||||
|
|
||||||
# Checkbox: Dark Mode
|
self.data_mode_label = QtWidgets.QLabel("Data mode:")
|
||||||
self.dark_mode_checkbox = QtWidgets.QCheckBox("Enable Dark Mode")
|
data_mode_layout.addWidget(self.data_mode_label)
|
||||||
layout.addWidget(self.dark_mode_checkbox)
|
|
||||||
|
|
||||||
# Dropdown: Language Selection
|
self.data_mode_dropdown = QtWidgets.QComboBox()
|
||||||
self.language_label = QtWidgets.QLabel("Language:")
|
for tl in TransactionLevel:
|
||||||
layout.addWidget(self.language_label)
|
self.data_mode_dropdown.addItem(tl.name.capitalize(), tl.value)
|
||||||
|
|
||||||
self.language_dropdown = QtWidgets.QComboBox()
|
data_mode_layout.addWidget(self.data_mode_dropdown)
|
||||||
self.language_dropdown.addItems(["English", "Spanish", "French", "German"])
|
layout.addLayout(data_mode_layout)
|
||||||
layout.addWidget(self.language_dropdown)
|
|
||||||
|
|
||||||
# Dropdown: Theme Selection
|
# Set the currently selected mode to the mode in UserConfig
|
||||||
self.theme_label = QtWidgets.QLabel("Theme:")
|
config = UserConfig()
|
||||||
layout.addWidget(self.theme_label)
|
current_level = config.transaction_level
|
||||||
|
index = self.data_mode_dropdown.findData(current_level)
|
||||||
self.theme_dropdown = QtWidgets.QComboBox()
|
if index != -1:
|
||||||
self.theme_dropdown.addItems(["Light", "Dark", "System Default"])
|
self.data_mode_dropdown.setCurrentIndex(index)
|
||||||
layout.addWidget(self.theme_dropdown)
|
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
button_layout = QtWidgets.QHBoxLayout()
|
||||||
@ -56,16 +53,13 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
layout.addLayout(button_layout)
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
# Example of how to fetch settings
|
data_mode = self.data_mode_dropdown.currentData()
|
||||||
notifications = self.notifications_checkbox.isChecked()
|
|
||||||
dark_mode = self.dark_mode_checkbox.isChecked()
|
config = UserConfig()
|
||||||
language = self.language_dropdown.currentText()
|
config.transaction_level = data_mode
|
||||||
theme = self.theme_dropdown.currentText()
|
|
||||||
|
|
||||||
print("Settings Saved:")
|
print("Settings Saved:")
|
||||||
print(f"Notifications: {notifications}")
|
print(f"Data Mode: {config.transaction_level}")
|
||||||
print(f"Dark Mode: {dark_mode}")
|
|
||||||
print(f"Language: {language}")
|
|
||||||
print(f"Theme: {theme}")
|
|
||||||
|
|
||||||
self.accept()
|
self.accept()
|
@ -56,11 +56,11 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
|||||||
# File menu
|
# File menu
|
||||||
file_menu = menu_bar.addMenu("File")
|
file_menu = menu_bar.addMenu("File")
|
||||||
|
|
||||||
import_action = QAction("Import", self)
|
import_action = QAction("Import books", self)
|
||||||
import_action.triggered.connect(self.import_data)
|
import_action.triggered.connect(self.import_data)
|
||||||
file_menu.addAction(import_action)
|
file_menu.addAction(import_action)
|
||||||
|
|
||||||
export_action = QAction("Export", self)
|
export_action = QAction("Export overview", self)
|
||||||
export_action.triggered.connect(self.export_data)
|
export_action.triggered.connect(self.export_data)
|
||||||
file_menu.addAction(export_action)
|
file_menu.addAction(export_action)
|
||||||
|
|
||||||
@ -74,9 +74,10 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
|||||||
# Edit menu
|
# Edit menu
|
||||||
edit_menu = menu_bar.addMenu("Edit")
|
edit_menu = menu_bar.addMenu("Edit")
|
||||||
|
|
||||||
preferences = QAction("Preferences", self)
|
preferences_action = QAction("Preferences", self)
|
||||||
preferences.triggered.connect(self.edit_preferences)
|
preferences_action.setShortcut("Ctrl+,")
|
||||||
edit_menu.addAction(preferences)
|
preferences_action.triggered.connect(self.edit_preferences)
|
||||||
|
edit_menu.addAction(preferences_action)
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
help_menu = menu_bar.addMenu("Help")
|
help_menu = menu_bar.addMenu("Help")
|
||||||
@ -100,4 +101,4 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def about(self):
|
def about(self):
|
||||||
QtWidgets.QMessageBox.information(self, "About", "Library Dashboard v1.0\nDeveloped by You.")
|
QtWidgets.QMessageBox.information(self, "About", "Library app demonstrating the phantom read problem")
|
0
src/utils/__init__.py
Normal file
0
src/utils/__init__.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import enum
|
||||||
|
import os
|
||||||
|
|
||||||
|
from utils.errors.database_config_error import DatabaseConfigError
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseConfig():
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logger.debug("Parsing .env")
|
||||||
|
|
||||||
|
anything_set = load_dotenv()
|
||||||
|
|
||||||
|
if anything_set:
|
||||||
|
self.logger.debug(".env found")
|
||||||
|
else:
|
||||||
|
self.logger.error(".env not found")
|
||||||
|
raise FileNotFoundError(".env not found.")
|
||||||
|
|
||||||
|
host = os.getenv("DATABASE_HOST")
|
||||||
|
port = os.getenv("DATABASE_PORT", "3306")
|
||||||
|
name = os.getenv("DATABASE_NAME")
|
||||||
|
user = os.getenv("DATABASE_USER")
|
||||||
|
password = os.getenv("DATABASE_PASSWORD")
|
||||||
|
|
||||||
|
self.logger.debug("Validating fetched values from .env")
|
||||||
|
|
||||||
|
if host is None or host == "":
|
||||||
|
self.logger.error("DATABASE_HOST is empty")
|
||||||
|
raise DatabaseConfigError(
|
||||||
|
"Config is invalid.", "DATABASE_HOST", "(Empty)")
|
||||||
|
if name is None or name == "":
|
||||||
|
self.logger.error("DATABASE_NAME is empty")
|
||||||
|
raise DatabaseConfigError(
|
||||||
|
"Config is invalid.", "DATABASE_NAME", "(Empty)")
|
||||||
|
if user is None or user == "":
|
||||||
|
self.logger.error("DATABASE_USER is empty")
|
||||||
|
raise DatabaseConfigError(
|
||||||
|
"Config is invalid.", "DATABASE_USER", "(Empty)")
|
||||||
|
if password is None or password == "":
|
||||||
|
self.logger.error("DATABASE_PASSWORD is empty")
|
||||||
|
raise DatabaseConfigError(
|
||||||
|
"Config is invalid.", "DATABASE_PASSWORD", "(Empty)")
|
||||||
|
|
||||||
|
if not port.isdigit():
|
||||||
|
self.logger.error("DATABASE_PORT is invalid. Not a number")
|
||||||
|
raise DatabaseConfigError(
|
||||||
|
"Config is invalid", "DATABASE_PORT", port)
|
||||||
|
|
||||||
|
self.logger.debug("All config validated")
|
||||||
|
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.name = name
|
||||||
|
self.user = user
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionLevel(enum.Enum):
|
||||||
|
insecure = 'READ UNCOMMITED'
|
||||||
|
secure = 'SERIALIZABLE'
|
||||||
|
|
||||||
|
|
||||||
|
class UserConfig():
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not hasattr(self, "transaction_level"):
|
||||||
|
self.transaction_level = TransactionLevel.insecure
|
27
src/utils/database.py
Normal file
27
src/utils/database.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from mysql.connector import connect, cursor
|
||||||
|
|
||||||
|
from utils.config import DatabaseConfig
|
||||||
|
from utils.errors.database_config_error import DatabaseConfigError
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager():
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logger.info("Reading database config")
|
||||||
|
self.database_config = DatabaseConfig()
|
||||||
|
self.connection = connect(
|
||||||
|
host=self.database_config.host,
|
||||||
|
port=self.database_config.port,
|
||||||
|
database=self.database_config.name,
|
||||||
|
user=self.database_config.user,
|
||||||
|
password=self.database_config.password
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_cursor(self) -> cursor.MySQLCursorAbstract:
|
||||||
|
return self.connection.cursor(dictionary=True)
|
||||||
|
|
||||||
|
def cleanup(self) -> None:
|
||||||
|
self.logger.debug("Closing connection")
|
||||||
|
self.connection.close()
|
0
src/utils/errors/__init__.py
Normal file
0
src/utils/errors/__init__.py
Normal file
7
src/utils/errors/database_config_error.py
Normal file
7
src/utils/errors/database_config_error.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class DatabaseConfigError(Exception):
|
||||||
|
def __init__(self, message: str, config_name: str, config_value: str):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
self.message = message
|
||||||
|
self.config_name = config_name
|
||||||
|
self.config_value = config_value
|
14
src/utils/setup_logger.py
Normal file
14
src/utils/setup_logger.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
formatter = logging.Formatter("[%(levelname)s] - %(name)s - %(message)s")
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(handler)
|
Loading…
x
Reference in New Issue
Block a user