231 lines
No EOL
7.8 KiB
Python
231 lines
No EOL
7.8 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', 'Администратор', 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() |