[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 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| 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__": | ||||
|     app = QtWidgets.QApplication([]) | ||||
|     window = LibraryWindow() | ||||
|     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() | ||||
|     library_app = LibraryApp() | ||||
|     sys.exit(library_app.run()) | ||||
|  | ||||
| @ -7,6 +7,6 @@ from .member import Member | ||||
| from .librarian import Librarian | ||||
| from .loan import Loan | ||||
| 
 | ||||
| from .book_overview_view import BookOverviewView | ||||
| from .book_overview import BooksOverview | ||||
| 
 | ||||
| __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.QtQml import QQmlApplicationEngine | ||||
| from PySide6 import QtWidgets, QtCore | ||||
| from PySide6.QtGui import QAction | ||||
| from PySide6.QtWidgets import ( | ||||
|     QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea, | ||||
|     QFrame, QPushButton, QMessageBox, QVBoxLayout, QScrollArea | ||||
| ) | ||||
| from PySide6.QtCore import Qt | ||||
| 
 | ||||
| from ui.settings import SettingsDialog | ||||
| class LibraryDashboard(QtWidgets.QWidget): | ||||
| from .book_card import BookCard | ||||
| from models.book_overview import BooksOverview | ||||
| 
 | ||||
| from sqlalchemy import create_engine | ||||
| from sqlalchemy.orm import sessionmaker | ||||
| class LibraryDashboard(QWidget): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
| 
 | ||||
|         # Central widget and layout | ||||
|         main_layout = QtWidgets.QVBoxLayout(self) | ||||
|         main_layout = QVBoxLayout(self) | ||||
| 
 | ||||
|         # Title label | ||||
|         title_label = QtWidgets.QLabel("Dashboard", self) | ||||
|         title_label.setAlignment(QtCore.Qt.AlignCenter) | ||||
|         title_label = QLabel("Dashboard", self) | ||||
|         title_label.setAlignment(Qt.AlignCenter) | ||||
|         title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;") | ||||
|         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 | ||||
|         self.search_input = QtWidgets.QLineEdit() | ||||
|         self.search_input = QLineEdit() | ||||
|         self.search_input.setPlaceholderText("Type to search...") | ||||
|         self.search_input.textChanged.connect(self.filter_books) | ||||
|         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() | ||||
|         self.books = ["Book One", "Book Two", "Book Three", "Book Four", | ||||
|                       "Book Five", "Book Six", "Book Seven", "Book Eight"] | ||||
|         # Container widget for the scroll area | ||||
|         self.scroll_widget = QWidget() | ||||
|         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) | ||||
|         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) | ||||
|         # Example cards | ||||
|         # self.books = self.fetch_books_from_db() | ||||
|         # self.book_cards = [] | ||||
| 
 | ||||
|         # Borrowed books list | ||||
|         borrowed_label = QtWidgets.QLabel("Currently Borrowed Books") | ||||
|         borrowed_label.setStyleSheet("font-size: 16px;") | ||||
|         main_layout.addWidget(borrowed_label) | ||||
|         # for book in self.books: | ||||
|         #     card = BookCard(book) | ||||
|         #     self.scroll_layout.addWidget(card) | ||||
|         #     self.book_cards.append(card) | ||||
| 
 | ||||
|         self.borrowed_books_list = QtWidgets.QListWidget() | ||||
|         self.borrowed_books_list.addItems(["Book Two", "Book Four"]) | ||||
|         self.borrowed_books_list.itemClicked.connect(self.return_book) | ||||
|         main_layout.addWidget(self.borrowed_books_list) | ||||
|         self.scroll_widget.setLayout(self.scroll_layout) | ||||
|         self.scroll_area.setWidget(self.scroll_widget) | ||||
|         main_layout.addWidget(self.scroll_area) | ||||
| 
 | ||||
|         # Buttons for actions | ||||
|         button_layout = QtWidgets.QHBoxLayout() | ||||
|         register_member_button = QtWidgets.QPushButton("Add New Member") | ||||
|         button_layout = QHBoxLayout() | ||||
|         register_member_button = QPushButton("Add New Member") | ||||
|         register_member_button.clicked.connect(self.register_member) | ||||
|         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) | ||||
|         button_layout.addWidget(add_borrow_record_button) | ||||
| 
 | ||||
|         main_layout.addLayout(button_layout) | ||||
| 
 | ||||
|     def filter_books(self, text): | ||||
|         """Filter the available books list based on the search input.""" | ||||
|         self.available_books_list.clear() | ||||
|         filtered_books = [book for book in self.books if text.lower() in book.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.") | ||||
|         """Filter the cards based on the search input.""" | ||||
|         for card, book in zip(self.book_cards, self.books): | ||||
|             card.setVisible(text.lower() in book["title"].lower() or text.lower() in book["description"].lower()) | ||||
| 
 | ||||
|     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): | ||||
|         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 utils.config import UserConfig, TransactionLevel | ||||
| 
 | ||||
| class SettingsDialog(QtWidgets.QDialog): | ||||
|     def __init__(self, parent=None): | ||||
|         super().__init__(parent) | ||||
| 
 | ||||
|         self.setWindowTitle("Settings") | ||||
|         self.setMinimumSize(400, 300) | ||||
|         self.setMinimumSize(400, 100) | ||||
| 
 | ||||
|         # Position the dialog relative to the parent (main window) | ||||
|         if parent: | ||||
| @ -18,29 +20,24 @@ class SettingsDialog(QtWidgets.QDialog): | ||||
|         # Create layout | ||||
|         layout = QtWidgets.QVBoxLayout(self) | ||||
| 
 | ||||
|         # Checkbox: Enable Notifications | ||||
|         self.notifications_checkbox = QtWidgets.QCheckBox("Enable Notifications") | ||||
|         layout.addWidget(self.notifications_checkbox) | ||||
|         data_mode_layout = QtWidgets.QHBoxLayout() | ||||
| 
 | ||||
|         # Checkbox: Dark Mode | ||||
|         self.dark_mode_checkbox = QtWidgets.QCheckBox("Enable Dark Mode") | ||||
|         layout.addWidget(self.dark_mode_checkbox) | ||||
|         self.data_mode_label = QtWidgets.QLabel("Data mode:") | ||||
|         data_mode_layout.addWidget(self.data_mode_label) | ||||
| 
 | ||||
|         # Dropdown: Language Selection | ||||
|         self.language_label = QtWidgets.QLabel("Language:") | ||||
|         layout.addWidget(self.language_label) | ||||
|         self.data_mode_dropdown = QtWidgets.QComboBox() | ||||
|         for tl in TransactionLevel: | ||||
|             self.data_mode_dropdown.addItem(tl.name.capitalize(), tl.value) | ||||
| 
 | ||||
|         self.language_dropdown = QtWidgets.QComboBox() | ||||
|         self.language_dropdown.addItems(["English", "Spanish", "French", "German"]) | ||||
|         layout.addWidget(self.language_dropdown) | ||||
|         data_mode_layout.addWidget(self.data_mode_dropdown) | ||||
|         layout.addLayout(data_mode_layout) | ||||
| 
 | ||||
|         # Dropdown: Theme Selection | ||||
|         self.theme_label = QtWidgets.QLabel("Theme:") | ||||
|         layout.addWidget(self.theme_label) | ||||
| 
 | ||||
|         self.theme_dropdown = QtWidgets.QComboBox() | ||||
|         self.theme_dropdown.addItems(["Light", "Dark", "System Default"]) | ||||
|         layout.addWidget(self.theme_dropdown) | ||||
|         # Set the currently selected mode to the mode in UserConfig | ||||
|         config = UserConfig() | ||||
|         current_level = config.transaction_level | ||||
|         index = self.data_mode_dropdown.findData(current_level) | ||||
|         if index != -1: | ||||
|             self.data_mode_dropdown.setCurrentIndex(index) | ||||
| 
 | ||||
|         # Buttons | ||||
|         button_layout = QtWidgets.QHBoxLayout() | ||||
| @ -56,16 +53,13 @@ class SettingsDialog(QtWidgets.QDialog): | ||||
|         layout.addLayout(button_layout) | ||||
| 
 | ||||
|     def save_settings(self): | ||||
|         # Example of how to fetch settings | ||||
|         notifications = self.notifications_checkbox.isChecked() | ||||
|         dark_mode = self.dark_mode_checkbox.isChecked() | ||||
|         language = self.language_dropdown.currentText() | ||||
|         theme = self.theme_dropdown.currentText() | ||||
|         data_mode = self.data_mode_dropdown.currentData() | ||||
| 
 | ||||
|         config = UserConfig() | ||||
|         config.transaction_level = data_mode | ||||
| 
 | ||||
|         print("Settings Saved:") | ||||
|         print(f"Notifications: {notifications}") | ||||
|         print(f"Dark Mode: {dark_mode}") | ||||
|         print(f"Language: {language}") | ||||
|         print(f"Theme: {theme}") | ||||
|         print(f"Data Mode: {config.transaction_level}") | ||||
| 
 | ||||
| 
 | ||||
|         self.accept() | ||||
| @ -56,11 +56,11 @@ class LibraryWindow(QtWidgets.QMainWindow): | ||||
|         # File menu | ||||
|         file_menu = menu_bar.addMenu("File") | ||||
| 
 | ||||
|         import_action = QAction("Import", self) | ||||
|         import_action = QAction("Import books", self) | ||||
|         import_action.triggered.connect(self.import_data) | ||||
|         file_menu.addAction(import_action) | ||||
| 
 | ||||
|         export_action = QAction("Export", self) | ||||
|         export_action = QAction("Export overview", self) | ||||
|         export_action.triggered.connect(self.export_data) | ||||
|         file_menu.addAction(export_action) | ||||
| 
 | ||||
| @ -74,9 +74,10 @@ class LibraryWindow(QtWidgets.QMainWindow): | ||||
|         # Edit menu | ||||
|         edit_menu = menu_bar.addMenu("Edit") | ||||
| 
 | ||||
|         preferences = QAction("Preferences", self) | ||||
|         preferences.triggered.connect(self.edit_preferences) | ||||
|         edit_menu.addAction(preferences) | ||||
|         preferences_action = QAction("Preferences", self) | ||||
|         preferences_action.setShortcut("Ctrl+,") | ||||
|         preferences_action.triggered.connect(self.edit_preferences) | ||||
|         edit_menu.addAction(preferences_action) | ||||
| 
 | ||||
|         # Help menu | ||||
|         help_menu = menu_bar.addMenu("Help") | ||||
| @ -100,4 +101,4 @@ class LibraryWindow(QtWidgets.QMainWindow): | ||||
|         pass | ||||
| 
 | ||||
|     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