[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.scroll_layout.setContentsMargins(0, 0, 0, 0) | ||||||
| 
 | 
 | ||||||
|         self.available_books_list.addItems(self.books) |         # Example cards | ||||||
|         self.available_books_list.itemClicked.connect(self.edit_book) |         # self.books = self.fetch_books_from_db() | ||||||
|         self.available_books_list.setStyleSheet(""" |         # self.book_cards = [] | ||||||
|             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 |         # for book in self.books: | ||||||
|         borrowed_label = QtWidgets.QLabel("Currently Borrowed Books") |         #     card = BookCard(book) | ||||||
|         borrowed_label.setStyleSheet("font-size: 16px;") |         #     self.scroll_layout.addWidget(card) | ||||||
|         main_layout.addWidget(borrowed_label) |         #     self.book_cards.append(card) | ||||||
| 
 | 
 | ||||||
|         self.borrowed_books_list = QtWidgets.QListWidget() |         self.scroll_widget.setLayout(self.scroll_layout) | ||||||
|         self.borrowed_books_list.addItems(["Book Two", "Book Four"]) |         self.scroll_area.setWidget(self.scroll_widget) | ||||||
|         self.borrowed_books_list.itemClicked.connect(self.return_book) |         main_layout.addWidget(self.scroll_area) | ||||||
|         main_layout.addWidget(self.borrowed_books_list) |  | ||||||
| 
 | 
 | ||||||
|         # 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