Compare commits

..

8 Commits

20 changed files with 286 additions and 116 deletions

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
DB_USER=
DB_NAME=
DB_PASSWORD=
DB_HOST=

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/app.py",
"console": "integratedTerminal"
}
]
}

View File

@ -37,3 +37,9 @@ My solution will use the following tech stack:
5. **`python-dotenv`** for configurability
The problem in question will be demonstrated on a library application
## Sources
1. [Qt documentation for pyside2](https://doc.qt.io/qtforpython-5/PySide2/QtWidgets)
1. [Qt documentation for PySide6](https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/)
2. [Pythontutorial.net](https://www.pythontutorial.net/pyqt/pyqt-qtoolbar/)
3. [Docs.sqlalchemy.org](https://docs.sqlalchemy.org/)

View File

@ -1,11 +1,13 @@
greenlet==3.1.1
mypy==1.14.1
mypy-extensions==1.0.0
mysql-connector-python==9.1.0
PySide6==6.8.1
PySide6_Addons==6.8.1
PySide6_Essentials==6.8.1
python-dotenv==1.0.1
shiboken2==5.13.2
shiboken6==6.8.1
SQLAlchemy==2.0.36
sqlalchemy-stubs==0.4
typing_extensions==4.12.2

View File

@ -1,56 +1,39 @@
# import sys
# from PySide6.QtGui import QGuiApplication
# from PySide6.QtQml import QQmlApplicationEngine
# if __name__ == "__main__":
# app = QGuiApplication(sys.argv)
# engine = QQmlApplicationEngine()
# engine.addImportPath(sys.path[0])
# engine.loadFromModule("ui", "Main")
# if not engine.rootObjects():
# sys.exit(-1)
# exit_code = app.exec()
# del engine
# sys.exit(exit_code)
import sys
from PySide6 import QtWidgets, QtCore
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ui.dashboard import LibraryDashboard
from models.book import Book, BookStatusEnum
from models.author import Author
# Replace with your MySQL database credentials
DATABASE_URI = 'mysql+mysqlconnector://username:password@localhost:3306/library'
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = LibraryDashboard()
window.show()
sys.exit(app.exec())
# Create the engine
engine = create_engine(DATABASE_URI, echo=True)
# from sqlalchemy import create_engine
# from sqlalchemy.orm import sessionmaker
# Create a configured session class
SessionLocal = sessionmaker(bind=engine)
# from models.book import Book, BookStatusEnum
# from models.author import Author
# Create a session instance
session = SessionLocal()
# # Replace with your MySQL database credentials
# DATABASE_URI = 'mysql+mysqlconnector://username:password@localhost:3306/library'
new_author = Author(first_name="John", last_name="Doe")
# # Create the engine
# engine = create_engine(DATABASE_URI)
session.add(new_author)
session.commit()
# from models.base import Base
# Base.metadata.create_all(engine)
new_book = Book(
title="Sample Book",
description="A fascinating tale.",
year_published="2023",
isbn="1234567890123",
status='available',
author_id=new_author.id
)
# # Create a configured session class
# SessionLocal = sessionmaker(bind=engine)
session.add(new_book)
session.commit()
# # Create a session instance
# session = SessionLocal()
books = session.query(Book).all()
for book in books:
print(book.title, book.author.first_name)
# books = session.query(Book).all()
# for book in books:
# print(book.title, book.author.first_name)
session.close()
# session.close()

View File

@ -1,7 +1,10 @@
from .base import Base
from .author import Author
from .book_category import BookCategory
from .book import Book
from .book_category import BookCategory
from .book_category_link import BookCategoryLink
from .member import Member
from .librarian import Librarian
from .loan import Loan
__all__ = ["Author", "Book", "BookCategory", "BookCategoryLink", "Member", "Librarian", "Loan"]

View File

@ -1,15 +1,17 @@
from sqlalchemy import Column, Integer, String, TIMESTAMP, UniqueConstraint, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from .base import Base
class Author(Base):
__tablename__ = 'author'
__tablename__ = 'author'
__table_args__ = (UniqueConstraint('id'),)
id = Column(Integer, primary_key=True, autoincrement=True)
first_name = Column(String(50), nullable=False)
last_name = Column(String(50), nullable=False)
id = Column(Integer, primary_key=True, autoincrement=True)
first_name = Column(String(50), nullable=False)
last_name = Column(String(50), nullable=False)
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
# Reference 'Book' as a string to avoid direct import
books = relationship('Book', back_populates='author')

3
src/models/base.py Normal file
View File

@ -0,0 +1,3 @@
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

View File

@ -2,12 +2,8 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from .author import Author
from .book_category import BookCategory
Base = declarative_base()
from .base import Base
class BookStatusEnum(enum.Enum):
@ -15,18 +11,20 @@ class BookStatusEnum(enum.Enum):
borrowed = 'borrowed'
reserved = 'reserved'
class Book(Base):
__tablename__ = 'book'
__tablename__ = 'book'
__table_args__ = (UniqueConstraint('isbn'),)
id = Column(Integer, primary_key=True, autoincrement=True)
author_id = Column(Integer, ForeignKey('author.id'), nullable=False)
title = Column(String(100), nullable=False)
description = Column(Text, nullable=False)
id = Column(Integer, primary_key=True, autoincrement=True)
author_id = Column(Integer, ForeignKey('author.id'), nullable=False)
title = Column(String(100), nullable=False)
description = Column(Text, nullable=False)
year_published = Column(String(4), nullable=False)
isbn = Column(String(13), nullable=False, unique=True)
status = Column(Enum(BookStatusEnum), nullable=False, default=BookStatusEnum.available)
created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
isbn = Column(String(13), nullable=False, unique=True)
status = Column(Enum(BookStatusEnum), nullable=False, default=BookStatusEnum.available)
created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
author = relationship('Author', backref='books')
# Reference 'Author' as a string to avoid direct import
author = relationship('Author', back_populates='books')

View File

@ -1,9 +1,7 @@
from sqlalchemy import Column, Integer, String, TIMESTAMP, ForeignKey, UniqueConstraint, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from .base import Base
class BookCategory(Base):
__tablename__ = 'book_category'

View File

@ -1,12 +1,9 @@
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from .book import Book
from .book_category import BookCategory
Base = declarative_base()
from .base import Base
class BookCategoryLink(Base):
__tablename__ = 'book_category_link'

View File

@ -2,10 +2,8 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from .base import Base
class LibrarianStatusEnum(enum.Enum):

View File

@ -2,14 +2,12 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, Float, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from .base import Base
from .book import Book
from .member import Member
from .librarian import Librarian
Base = declarative_base()
class LoanStatusEnum(enum.Enum):
borrowed = 'borrowed'

View File

@ -2,13 +2,14 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from .base import Base
class MemberStatusEnum(enum.Enum):
active = 'active'
inactive = 'inactive'
Base = declarative_base()
class Member(Base):
__tablename__ = 'member'

View File

@ -1,32 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Window {
width: 300
height: 200
visible: true
title: "Hello World"
readonly property list<string> texts: ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"]
function setText() {
var i = Math.round(Math.random() * 3);
text.text = texts[i];
}
ColumnLayout {
anchors.fill: parent
Text {
id: text
text: "Hello World"
Layout.alignment: Qt.AlignHCenter
}
Button {
text: "Click me"
Layout.alignment: Qt.AlignHCenter
onClicked: setText()
}
}
}

0
src/ui/__init__.py Normal file
View File

125
src/ui/dashboard.py Normal file
View File

@ -0,0 +1,125 @@
from PySide6.QtGui import QGuiApplication, QAction
from PySide6.QtQml import QQmlApplicationEngine
from PySide6 import QtWidgets, QtCore
from ui.settings import SettingsDialog
class LibraryDashboard(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# Set up main window properties
self.setWindowTitle("Library Dashboard")
self.setGeometry(100, 100, 400, 400)
# Set up menu bar
self.create_menu_bar()
# Central widget and layout
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
main_layout = QtWidgets.QVBoxLayout(central_widget)
# Title label
title_label = QtWidgets.QLabel("Library Dashboard", self)
title_label.setAlignment(QtCore.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)
self.available_books_list = QtWidgets.QListWidget()
self.available_books_list.addItems(["Book One", "Book Two", "Book Three", "Book Four","Book One", "Book Two", "Book Three", "Book Four","Book One", "Book Two", "Book Three", "Book Four"])
self.available_books_list.itemClicked.connect(self.edit_book)
main_layout.addWidget(self.available_books_list)
# Borrowed books list
borrowed_label = QtWidgets.QLabel("Currently Borrowed Books")
borrowed_label.setStyleSheet("font-size: 16px;")
main_layout.addWidget(borrowed_label)
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)
# Buttons for actions
button_layout = QtWidgets.QHBoxLayout()
register_member_button = QtWidgets.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.clicked.connect(self.add_borrow_record)
button_layout.addWidget(add_borrow_record_button)
main_layout.addLayout(button_layout)
# 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):
QtWidgets.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.")
def create_menu_bar(self):
# Create the menu bar
menu_bar = self.menuBar()
# File menu
file_menu = menu_bar.addMenu("File")
import_action = QAction("Import", self)
import_action.triggered.connect(self.import_data)
file_menu.addAction(import_action)
export_action = QAction("Export", self)
export_action.triggered.connect(self.export_data)
file_menu.addAction(export_action)
file_menu.addSeparator()
exit_action = QAction("Exit", self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# Edit menu
edit_menu = menu_bar.addMenu("Edit")
preferences = QAction("Preferences", self)
preferences.triggered.connect(self.edit_preferences)
edit_menu.addAction(preferences)
# Help menu
help_menu = menu_bar.addMenu("Help")
about_action = QAction("About", self)
about_action.triggered.connect(self.about)
help_menu.addAction(about_action)
# Menu action slots
def edit_preferences(self):
dialog = SettingsDialog(parent=self)
if dialog.exec() == QtWidgets.QDialog.Accepted:
print("Settings were saved.")
def open_file(self):
QtWidgets.QMessageBox.information(self, "Open File", "Open an existing file.")
def import_data(self):
pass
def export_data(self):
pass
def about(self):
QtWidgets.QMessageBox.information(self, "About", "Library Dashboard v1.0\nDeveloped by You.")

View File

@ -1,2 +0,0 @@
module Example
Main 254.0 Main.qml

71
src/ui/settings.py Normal file
View File

@ -0,0 +1,71 @@
import sys
from PySide6 import QtWidgets
class SettingsDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
self.setMinimumSize(400, 300)
# Position the dialog relative to the parent (main window)
if parent:
x = parent.geometry().x() + parent.geometry().width() // 2 - self.width() // 2
y = parent.geometry().y() + parent.geometry().height() // 2 - self.height() // 2
self.move(x, y)
# Create layout
layout = QtWidgets.QVBoxLayout(self)
# Checkbox: Enable Notifications
self.notifications_checkbox = QtWidgets.QCheckBox("Enable Notifications")
layout.addWidget(self.notifications_checkbox)
# Checkbox: Dark Mode
self.dark_mode_checkbox = QtWidgets.QCheckBox("Enable Dark Mode")
layout.addWidget(self.dark_mode_checkbox)
# Dropdown: Language Selection
self.language_label = QtWidgets.QLabel("Language:")
layout.addWidget(self.language_label)
self.language_dropdown = QtWidgets.QComboBox()
self.language_dropdown.addItems(["English", "Spanish", "French", "German"])
layout.addWidget(self.language_dropdown)
# 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)
# Buttons
button_layout = QtWidgets.QHBoxLayout()
self.save_button = QtWidgets.QPushButton("Save")
self.save_button.clicked.connect(self.save_settings)
button_layout.addWidget(self.save_button)
self.cancel_button = QtWidgets.QPushButton("Cancel")
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_button)
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()
print("Settings Saved:")
print(f"Notifications: {notifications}")
print(f"Dark Mode: {dark_mode}")
print(f"Language: {language}")
print(f"Theme: {theme}")
self.accept()

0
src/utils/config.py Normal file
View File