Add example demo
This commit is contained in:
parent
d21d999a97
commit
effffe5706
2 changed files with 232 additions and 1 deletions
231
examples/basic_demo.py
Normal file
231
examples/basic_demo.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
"""
|
||||
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()
|
||||
|
|
@ -36,7 +36,7 @@ all = ["psycopg2-binary>=2.9.0", "pymysql>=1.0.0"]
|
|||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["pyqt6_scaffold*"]
|
||||
exclude = ["docs*", "README.md"]
|
||||
exclude = ["docs*", "README.md", "examples*"]
|
||||
|
||||
[project.urls]
|
||||
repository = "https://codeberg.org/zerumarex/pyqt6-scaffold"
|
||||
Loading…
Add table
Add a link
Reference in a new issue