pyqt6-scaffold/examples/basic_demo.py
2026-03-06 19:51:15 +03:00

231 lines
No EOL
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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', 'Администратор', 100);
INSERT OR IGNORE INTO users VALUES (2, 'user', 'user', 'Пользователь', 25);
INSERT OR IGNORE INTO products VALUES (1, 'Ноутбук', 'Электроника', 79999, 5);
INSERT OR IGNORE INTO products VALUES (2, 'Мышь', 'Электроника', 1299, 0);
INSERT OR IGNORE INTO products VALUES (3, 'Стол', 'Мебель', 12500, 3);
INSERT OR IGNORE INTO products VALUES (4, 'Кресло', 'Мебель', 8900, 0);
INSERT OR IGNORE INTO products VALUES (5, 'Монитор', 'Электроника', 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", "Наименование", "Категория", "Цена", "На складе"]
def row_background(self, data):
from PyQt6.QtGui import QColor
if data[4] == 0: # нет на складе — голубой
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("Вход в систему")
self._login = QLineEdit()
self._password = QLineEdit()
self._password.setEchoMode(QLineEdit.EchoMode.Password)
self._btn = QPushButton("Войти")
self._hint = QLabel("admin/admin или user/user")
self._login.setPlaceholderText("Логин")
self._password.setPlaceholderText("Пароль")
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("Авторизация")
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, "Ошибка", "Неверный логин или пароль")
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"Пользователь: {name} | Роль: {role}")
self._table = QTableView()
self._model = ProductModel()
self._logout = QPushButton("Выйти")
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("Каталог товаров")
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()