pyqt6-scaffold/examples/basic_demo.py
2026-03-06 20:07:10 +03:00

231 lines
No EOL
6.9 KiB
Python

"""
Demo application using pyqt6_scaffold.
Run via start.sh / start.cmd - environment variables must be set.
start.sh:
export SQLITE_PATH=demo.db
python demo_app.py
"""
import sys
import sqlite3
from PyQt6.QtWidgets import (
QApplication, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QHBoxLayout,
QWidget, QTableView, QMessageBox
)
from PyQt6.QtCore import Qt
from pyqt6_scaffold import (
AbstractDatabase, BaseWindow, Composer,
BaseUser, NavigateRequest, NavigationContext,
BaseTableModel
)
from pyqt6_scaffold.contrib.auth import Role
# --- Database -----------------------------------------------------------
class AppDatabase(AbstractDatabase):
@property
def placeholder(self) -> str:
return "?"
def _connect(self):
import os
path = os.getenv("SQLITE_PATH", "demo.db")
conn = sqlite3.connect(path)
self._bootstrap(conn)
return conn
def _bootstrap(self, conn):
"""Create tables and seed data if not exists."""
conn.executescript("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
login TEXT UNIQUE,
password TEXT,
role TEXT,
level INTEGER
);
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY,
name TEXT,
category TEXT,
price REAL,
stock INTEGER
);
INSERT OR IGNORE INTO users VALUES (1, 'admin', 'admin', 'Administrator', 100);
INSERT OR IGNORE INTO users VALUES (2, 'user', 'user', 'User', 25);
INSERT OR IGNORE INTO products VALUES (1, 'Notebook', 'Electronics', 79999, 5);
INSERT OR IGNORE INTO products VALUES (2, 'Mouse', 'Electronics', 1299, 0);
INSERT OR IGNORE INTO products VALUES (3, 'Table', 'Furniture', 12500, 3);
INSERT OR IGNORE INTO products VALUES (4, 'Chair', 'Furniture', 8900, 0);
INSERT OR IGNORE INTO products VALUES (5, 'Monitor', 'Electronics', 24999, 2);
""")
conn.commit()
def auth(self, login: str, password: str) -> BaseUser | None:
with self.execute(
f"SELECT id, login, role, level FROM users WHERE login = {self.placeholder} AND password = {self.placeholder}",
(login, password)
) as cursor:
row = cursor.fetchone()
if not row:
return None
role = Role(name=row[2], level=row[3])
return BaseUser(id=row[0], name=row[1], role=role)
def get_products(self) -> list:
with self.execute(
"SELECT id, name, category, price, stock FROM products ORDER BY id"
) as cursor:
return cursor.fetchall()
# --- Models -----------------------------------------------------------
class ProductModel(BaseTableModel):
headers = ["ID", "Name", "Category", "Price", "In stock"]
def row_background(self, data):
from PyQt6.QtGui import QColor
if data[4] == 0: # if out of stock - blue
return QColor("#cce5ff")
return None
def row_foreground(self, data):
return None
def row_font(self, data):
return None
# --- Windows -----------------------------------------------------------
class LoginWindow(BaseWindow):
def _define_widgets(self):
self._title = QLabel("Entry in system")
self._login = QLineEdit()
self._password = QLineEdit()
self._password.setEchoMode(QLineEdit.EchoMode.Password)
self._btn = QPushButton("Enter")
self._hint = QLabel("admin/admin or user/user")
self._login.setPlaceholderText("Login")
self._password.setPlaceholderText("Password")
self._hint.setAlignment(Qt.AlignmentFlag.AlignCenter)
def _tune_layouts(self):
layout = QVBoxLayout()
layout.setSpacing(10)
layout.setContentsMargins(40, 40, 40, 40)
layout.addWidget(self._title)
layout.addWidget(self._login)
layout.addWidget(self._password)
layout.addWidget(self._btn)
layout.addWidget(self._hint)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def _connect_slots(self):
self._btn.clicked.connect(self._on_login)
self._password.returnPressed.connect(self._on_login)
def _apply_windows_settings(self):
self.setWindowTitle("Authorization")
self.setFixedSize(300, 220)
def _on_login(self):
user = self._db.auth(
self._login.text().strip(),
self._password.text().strip()
)
if user is None:
QMessageBox.warning(self, "Error", "Incorrect login or password")
return
self._composer.navigate_request.emit(
NavigateRequest(
target="main",
context=NavigationContext(data={"user": user})
)
)
class MainWindow(BaseWindow):
def _define_widgets(self):
user = self._composer.context.data.get("user")
name = user.name if user else ""
role = user.role.name if user else ""
self._info = QLabel(f"User: {name} | Role: {role}")
self._table = QTableView()
self._model = ProductModel()
self._logout = QPushButton("Exit")
self._table.setModel(self._model)
self._table.horizontalHeader().setStretchLastSection(True)
self._table.setSelectionBehavior(
QTableView.SelectionBehavior.SelectRows
)
self._table.setEditTriggers(
QTableView.EditTrigger.NoEditTriggers
)
rows = self._db.get_products()
self._model.refresh(rows)
def _tune_layouts(self):
top = QHBoxLayout()
top.addWidget(self._info)
top.addStretch()
top.addWidget(self._logout)
layout = QVBoxLayout()
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(8)
layout.addLayout(top)
layout.addWidget(self._table)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def _connect_slots(self):
self._logout.clicked.connect(self._on_logout)
def _apply_windows_settings(self):
self.setWindowTitle("Product catalog")
self.resize(700, 400)
def _on_logout(self):
self._composer.navigate_request.emit(
NavigateRequest(
target="login",
context=NavigationContext()
)
)
# --- Entry point -----------------------------------------------------------
def main():
app = QApplication(sys.argv)
db = AppDatabase()
db.connect()
composer = Composer(app=app, db=db)
composer.register("login", LoginWindow)
composer.register("main", MainWindow)
sys.exit(composer.run(start="login"))
if __name__ == "__main__":
main()