deskkit 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deskkit-1.1.0/LICENSE +21 -0
- deskkit-1.1.0/PKG-INFO +185 -0
- deskkit-1.1.0/README.md +152 -0
- deskkit-1.1.0/deskkit/__init__.py +44 -0
- deskkit-1.1.0/deskkit/app.py +114 -0
- deskkit-1.1.0/deskkit/auth.py +72 -0
- deskkit-1.1.0/deskkit/catalog.py +103 -0
- deskkit-1.1.0/deskkit/config.py +130 -0
- deskkit-1.1.0/deskkit/database.py +211 -0
- deskkit-1.1.0/deskkit/exceptions.py +17 -0
- deskkit-1.1.0/deskkit/generator/__init__.py +3 -0
- deskkit-1.1.0/deskkit/generator/export.py +237 -0
- deskkit-1.1.0/deskkit/generator/templates/auth.py +70 -0
- deskkit-1.1.0/deskkit/generator/templates/database.py +200 -0
- deskkit-1.1.0/deskkit/generator/templates/images_utils.py +115 -0
- deskkit-1.1.0/deskkit/generator/templates/main.py +72 -0
- deskkit-1.1.0/deskkit/generator/templates/qt_loader.py +54 -0
- deskkit-1.1.0/deskkit/images.py +127 -0
- deskkit-1.1.0/deskkit/qt_backend.py +83 -0
- deskkit-1.1.0/deskkit/roles.py +20 -0
- deskkit-1.1.0/deskkit/ui/__init__.py +0 -0
- deskkit-1.1.0/deskkit/ui/login_window.py +114 -0
- deskkit-1.1.0/deskkit/ui/main_window.py +289 -0
- deskkit-1.1.0/deskkit/ui/product_form.py +196 -0
- deskkit-1.1.0/deskkit/ui/styles.py +58 -0
- deskkit-1.1.0/deskkit.egg-info/PKG-INFO +185 -0
- deskkit-1.1.0/deskkit.egg-info/SOURCES.txt +31 -0
- deskkit-1.1.0/deskkit.egg-info/dependency_links.txt +1 -0
- deskkit-1.1.0/deskkit.egg-info/requires.txt +11 -0
- deskkit-1.1.0/deskkit.egg-info/top_level.txt +1 -0
- deskkit-1.1.0/pyproject.toml +42 -0
- deskkit-1.1.0/setup.cfg +4 -0
- deskkit-1.1.0/tests/test_catalog.py +61 -0
deskkit-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeskKit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deskkit-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deskkit
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Простой набор инструментов для создания десктоп-приложений с Qt и MySQL
|
|
5
|
+
Author: DeskKit Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/deskkit/deskkit
|
|
8
|
+
Project-URL: Documentation, https://github.com/deskkit/deskkit#readme
|
|
9
|
+
Keywords: qt,pyqt,pyside,mysql,desktop,gui
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mysql-connector-python>=8.0.0
|
|
25
|
+
Provides-Extra: pyqt
|
|
26
|
+
Requires-Dist: PyQt6>=6.4; extra == "pyqt"
|
|
27
|
+
Provides-Extra: pyside
|
|
28
|
+
Requires-Dist: PySide6>=6.4; extra == "pyside"
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: PyQt6>=6.4; extra == "all"
|
|
31
|
+
Requires-Dist: PySide6>=6.4; extra == "all"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# DeskKit
|
|
35
|
+
|
|
36
|
+
**DeskKit** — Python-библиотека для быстрого создания десктоп-приложений с каталогом товаров, авторизацией и CRUD на базе **Qt** (PySide6 / PyQt6) и **MySQL** (`mysql-connector-python`).
|
|
37
|
+
|
|
38
|
+
Название нейтральное: выглядит как обычный «desktop kit», без привязки к экзаменам.
|
|
39
|
+
|
|
40
|
+
## Установка
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# С PySide6 (рекомендуется)
|
|
44
|
+
pip install deskkit[pyside]
|
|
45
|
+
|
|
46
|
+
# Или с PyQt6
|
|
47
|
+
pip install deskkit[pyqt]
|
|
48
|
+
|
|
49
|
+
# Только ядро (нужен уже установленный Qt-биндинг)
|
|
50
|
+
pip install deskkit
|
|
51
|
+
pip install PySide6
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Быстрый старт
|
|
55
|
+
|
|
56
|
+
### Режим по умолчанию — генерация готового проекта
|
|
57
|
+
|
|
58
|
+
По умолчанию `export_project=True`: deskkit **генерирует автономный проект** с обычным Python-кодом. В сгенерированных файлах **нет импортов deskkit** — только PySide6/PyQt6 и mysql.connector, как будто приложение написано с нуля.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from deskkit import AppConfig, ShopApp
|
|
62
|
+
|
|
63
|
+
ShopApp(AppConfig(
|
|
64
|
+
title="Обувной магазин",
|
|
65
|
+
password="ваш_пароль",
|
|
66
|
+
database="shoe_store",
|
|
67
|
+
output_dir="obuvnoy_magazin", # куда положить проект
|
|
68
|
+
)).run()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
На выходе:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
obuvnoy_magazin/
|
|
75
|
+
├── main.py # точка входа
|
|
76
|
+
├── settings.py # настройки MySQL
|
|
77
|
+
├── database.py # mysql.connector
|
|
78
|
+
├── auth.py
|
|
79
|
+
├── catalog.py
|
|
80
|
+
├── images_utils.py
|
|
81
|
+
├── qt_loader.py # PySide6 / PyQt6
|
|
82
|
+
├── ui/ # окна Qt
|
|
83
|
+
├── sql/database.sql # создание БД + данные
|
|
84
|
+
├── requirements.txt
|
|
85
|
+
└── images/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Дальше работаете **только с этой папкой** — сдавайте её на экзамен.
|
|
89
|
+
|
|
90
|
+
### Параметры генерации
|
|
91
|
+
|
|
92
|
+
| Параметр | По умолчанию | Описание |
|
|
93
|
+
|----------|--------------|----------|
|
|
94
|
+
| `export_project` | `True` | Сгенерировать автономный проект |
|
|
95
|
+
| `output_dir` | slug от title | Папка результата |
|
|
96
|
+
| `include_seed_data` | `True` | Тестовые данные в SQL |
|
|
97
|
+
| `run_generated` | `True` | Запустить main.py после генерации |
|
|
98
|
+
| `library_runtime` | `False` | Запуск через deskkit (без генерации) |
|
|
99
|
+
|
|
100
|
+
### Запуск через библиотеку (режим разработки)
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
ShopApp(AppConfig(
|
|
104
|
+
export_project=False,
|
|
105
|
+
library_runtime=True,
|
|
106
|
+
)).run()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Минимальный пример (legacy runtime)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from deskkit import AppConfig, ShopApp
|
|
113
|
+
|
|
114
|
+
ShopApp(AppConfig(
|
|
115
|
+
title="Обувной магазин",
|
|
116
|
+
user="root",
|
|
117
|
+
password="ваш_пароль",
|
|
118
|
+
database="shoe_store",
|
|
119
|
+
images_dir="images",
|
|
120
|
+
export_project=False,
|
|
121
|
+
library_runtime=True,
|
|
122
|
+
)).run()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Что уже реализовано
|
|
126
|
+
|
|
127
|
+
| Требование демоэкзамена | DeskKit |
|
|
128
|
+
|-------------------------|---------|
|
|
129
|
+
| Подключение к MySQL | `AppConfig` + `Database` |
|
|
130
|
+
| Авторизация (логин/пароль из БД) | `LoginWindow` |
|
|
131
|
+
| Вход как гость | кнопка «Продолжить без авторизации» |
|
|
132
|
+
| 4 интерфейса по ролям | гость, клиент, менеджер, администратор |
|
|
133
|
+
| Каталог: фото + 9 полей | настраивается в `catalog_columns` |
|
|
134
|
+
| Условное форматирование (3 правила) | скидка, нет в наличии, скидка ≥15% |
|
|
135
|
+
| Поиск в реальном времени | по всем текстовым полям |
|
|
136
|
+
| Сортировка по количеству ↑↓ | кнопка в панели |
|
|
137
|
+
| Фильтр по поставщику + сброс | выпадающий список |
|
|
138
|
+
| CRUD товаров (админ) | форма с 12+ полями |
|
|
139
|
+
| Resize фото 300×200 | через Qt, без Pillow |
|
|
140
|
+
| Валидация (цена, кол-во ≥ 0) | `validate_product_data` |
|
|
141
|
+
| Удаление с проверкой заказов | `product_in_orders` |
|
|
142
|
+
| Обработка ошибок | `QMessageBox` |
|
|
143
|
+
| Навигация «Назад» | кнопка в форме редактирования |
|
|
144
|
+
|
|
145
|
+
## Выбор Qt-бэкенда
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Переменная окружения
|
|
149
|
+
set DESKKIT_QT=pyqt6 # Windows
|
|
150
|
+
export DESKKIT_QT=pyside6 # Linux/macOS
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Или в конфиге: `AppConfig(qt_backend="pyside6")`
|
|
154
|
+
|
|
155
|
+
## Структура для PyPI
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
deskkit/
|
|
159
|
+
├── pyproject.toml
|
|
160
|
+
├── README.md
|
|
161
|
+
├── LICENSE
|
|
162
|
+
├── deskkit/
|
|
163
|
+
│ ├── app.py # ShopApp — главная точка входа
|
|
164
|
+
│ ├── config.py # AppConfig
|
|
165
|
+
│ ├── database.py # MySQL
|
|
166
|
+
│ ├── auth.py # авторизация
|
|
167
|
+
│ ├── catalog.py # поиск / фильтр / сортировка
|
|
168
|
+
│ ├── images.py # работа с фото
|
|
169
|
+
│ └── ui/ # готовые окна Qt
|
|
170
|
+
└── examples/
|
|
171
|
+
└── shoe_shop.py
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Публикация на PyPI
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
cd deskkit
|
|
178
|
+
pip install build twine
|
|
179
|
+
python -m build
|
|
180
|
+
twine upload dist/*
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Лицензия
|
|
184
|
+
|
|
185
|
+
MIT
|
deskkit-1.1.0/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# DeskKit
|
|
2
|
+
|
|
3
|
+
**DeskKit** — Python-библиотека для быстрого создания десктоп-приложений с каталогом товаров, авторизацией и CRUD на базе **Qt** (PySide6 / PyQt6) и **MySQL** (`mysql-connector-python`).
|
|
4
|
+
|
|
5
|
+
Название нейтральное: выглядит как обычный «desktop kit», без привязки к экзаменам.
|
|
6
|
+
|
|
7
|
+
## Установка
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# С PySide6 (рекомендуется)
|
|
11
|
+
pip install deskkit[pyside]
|
|
12
|
+
|
|
13
|
+
# Или с PyQt6
|
|
14
|
+
pip install deskkit[pyqt]
|
|
15
|
+
|
|
16
|
+
# Только ядро (нужен уже установленный Qt-биндинг)
|
|
17
|
+
pip install deskkit
|
|
18
|
+
pip install PySide6
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Быстрый старт
|
|
22
|
+
|
|
23
|
+
### Режим по умолчанию — генерация готового проекта
|
|
24
|
+
|
|
25
|
+
По умолчанию `export_project=True`: deskkit **генерирует автономный проект** с обычным Python-кодом. В сгенерированных файлах **нет импортов deskkit** — только PySide6/PyQt6 и mysql.connector, как будто приложение написано с нуля.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from deskkit import AppConfig, ShopApp
|
|
29
|
+
|
|
30
|
+
ShopApp(AppConfig(
|
|
31
|
+
title="Обувной магазин",
|
|
32
|
+
password="ваш_пароль",
|
|
33
|
+
database="shoe_store",
|
|
34
|
+
output_dir="obuvnoy_magazin", # куда положить проект
|
|
35
|
+
)).run()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
На выходе:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
obuvnoy_magazin/
|
|
42
|
+
├── main.py # точка входа
|
|
43
|
+
├── settings.py # настройки MySQL
|
|
44
|
+
├── database.py # mysql.connector
|
|
45
|
+
├── auth.py
|
|
46
|
+
├── catalog.py
|
|
47
|
+
├── images_utils.py
|
|
48
|
+
├── qt_loader.py # PySide6 / PyQt6
|
|
49
|
+
├── ui/ # окна Qt
|
|
50
|
+
├── sql/database.sql # создание БД + данные
|
|
51
|
+
├── requirements.txt
|
|
52
|
+
└── images/
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Дальше работаете **только с этой папкой** — сдавайте её на экзамен.
|
|
56
|
+
|
|
57
|
+
### Параметры генерации
|
|
58
|
+
|
|
59
|
+
| Параметр | По умолчанию | Описание |
|
|
60
|
+
|----------|--------------|----------|
|
|
61
|
+
| `export_project` | `True` | Сгенерировать автономный проект |
|
|
62
|
+
| `output_dir` | slug от title | Папка результата |
|
|
63
|
+
| `include_seed_data` | `True` | Тестовые данные в SQL |
|
|
64
|
+
| `run_generated` | `True` | Запустить main.py после генерации |
|
|
65
|
+
| `library_runtime` | `False` | Запуск через deskkit (без генерации) |
|
|
66
|
+
|
|
67
|
+
### Запуск через библиотеку (режим разработки)
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
ShopApp(AppConfig(
|
|
71
|
+
export_project=False,
|
|
72
|
+
library_runtime=True,
|
|
73
|
+
)).run()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Минимальный пример (legacy runtime)
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from deskkit import AppConfig, ShopApp
|
|
80
|
+
|
|
81
|
+
ShopApp(AppConfig(
|
|
82
|
+
title="Обувной магазин",
|
|
83
|
+
user="root",
|
|
84
|
+
password="ваш_пароль",
|
|
85
|
+
database="shoe_store",
|
|
86
|
+
images_dir="images",
|
|
87
|
+
export_project=False,
|
|
88
|
+
library_runtime=True,
|
|
89
|
+
)).run()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Что уже реализовано
|
|
93
|
+
|
|
94
|
+
| Требование демоэкзамена | DeskKit |
|
|
95
|
+
|-------------------------|---------|
|
|
96
|
+
| Подключение к MySQL | `AppConfig` + `Database` |
|
|
97
|
+
| Авторизация (логин/пароль из БД) | `LoginWindow` |
|
|
98
|
+
| Вход как гость | кнопка «Продолжить без авторизации» |
|
|
99
|
+
| 4 интерфейса по ролям | гость, клиент, менеджер, администратор |
|
|
100
|
+
| Каталог: фото + 9 полей | настраивается в `catalog_columns` |
|
|
101
|
+
| Условное форматирование (3 правила) | скидка, нет в наличии, скидка ≥15% |
|
|
102
|
+
| Поиск в реальном времени | по всем текстовым полям |
|
|
103
|
+
| Сортировка по количеству ↑↓ | кнопка в панели |
|
|
104
|
+
| Фильтр по поставщику + сброс | выпадающий список |
|
|
105
|
+
| CRUD товаров (админ) | форма с 12+ полями |
|
|
106
|
+
| Resize фото 300×200 | через Qt, без Pillow |
|
|
107
|
+
| Валидация (цена, кол-во ≥ 0) | `validate_product_data` |
|
|
108
|
+
| Удаление с проверкой заказов | `product_in_orders` |
|
|
109
|
+
| Обработка ошибок | `QMessageBox` |
|
|
110
|
+
| Навигация «Назад» | кнопка в форме редактирования |
|
|
111
|
+
|
|
112
|
+
## Выбор Qt-бэкенда
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Переменная окружения
|
|
116
|
+
set DESKKIT_QT=pyqt6 # Windows
|
|
117
|
+
export DESKKIT_QT=pyside6 # Linux/macOS
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Или в конфиге: `AppConfig(qt_backend="pyside6")`
|
|
121
|
+
|
|
122
|
+
## Структура для PyPI
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
deskkit/
|
|
126
|
+
├── pyproject.toml
|
|
127
|
+
├── README.md
|
|
128
|
+
├── LICENSE
|
|
129
|
+
├── deskkit/
|
|
130
|
+
│ ├── app.py # ShopApp — главная точка входа
|
|
131
|
+
│ ├── config.py # AppConfig
|
|
132
|
+
│ ├── database.py # MySQL
|
|
133
|
+
│ ├── auth.py # авторизация
|
|
134
|
+
│ ├── catalog.py # поиск / фильтр / сортировка
|
|
135
|
+
│ ├── images.py # работа с фото
|
|
136
|
+
│ └── ui/ # готовые окна Qt
|
|
137
|
+
└── examples/
|
|
138
|
+
└── shoe_shop.py
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Публикация на PyPI
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
cd deskkit
|
|
145
|
+
pip install build twine
|
|
146
|
+
python -m build
|
|
147
|
+
twine upload dist/*
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Лицензия
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""DeskKit — простые десктоп-приложения на Qt и MySQL."""
|
|
2
|
+
|
|
3
|
+
from deskkit.app import ShopApp, run_app, run_from_json
|
|
4
|
+
from deskkit.auth import AuthService, make_guest, user_display_name
|
|
5
|
+
from deskkit.catalog import apply_catalog_filters, search_products, sort_products
|
|
6
|
+
from deskkit.config import AppConfig
|
|
7
|
+
from deskkit.database import Database
|
|
8
|
+
from deskkit.exceptions import AuthError, DatabaseError, DeskKitError, ValidationError
|
|
9
|
+
from deskkit.generator import export_project, run_generated
|
|
10
|
+
from deskkit.images import load_pixmap, save_product_image, validate_product_data
|
|
11
|
+
from deskkit.qt_backend import backend_name, init_qt
|
|
12
|
+
from deskkit.roles import ADMIN, CLIENT, GUEST, MANAGER
|
|
13
|
+
|
|
14
|
+
__version__ = "1.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ShopApp",
|
|
18
|
+
"AppConfig",
|
|
19
|
+
"Database",
|
|
20
|
+
"AuthService",
|
|
21
|
+
"export_project",
|
|
22
|
+
"run_generated",
|
|
23
|
+
"run_app",
|
|
24
|
+
"run_from_json",
|
|
25
|
+
"apply_catalog_filters",
|
|
26
|
+
"search_products",
|
|
27
|
+
"sort_products",
|
|
28
|
+
"load_pixmap",
|
|
29
|
+
"save_product_image",
|
|
30
|
+
"validate_product_data",
|
|
31
|
+
"make_guest",
|
|
32
|
+
"user_display_name",
|
|
33
|
+
"init_qt",
|
|
34
|
+
"backend_name",
|
|
35
|
+
"ADMIN",
|
|
36
|
+
"CLIENT",
|
|
37
|
+
"GUEST",
|
|
38
|
+
"MANAGER",
|
|
39
|
+
"DeskKitError",
|
|
40
|
+
"DatabaseError",
|
|
41
|
+
"AuthError",
|
|
42
|
+
"ValidationError",
|
|
43
|
+
"__version__",
|
|
44
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Точка входа — запуск готового приложения."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from deskkit import qt_backend
|
|
9
|
+
from deskkit.auth import AuthService
|
|
10
|
+
from deskkit.config import AppConfig
|
|
11
|
+
from deskkit.database import Database
|
|
12
|
+
from deskkit.exceptions import DatabaseError
|
|
13
|
+
from deskkit.images import ensure_images_dir
|
|
14
|
+
from deskkit.ui.login_window import LoginWindow
|
|
15
|
+
from deskkit.ui.main_window import MainWindow
|
|
16
|
+
from deskkit.ui.styles import APP_STYLE
|
|
17
|
+
|
|
18
|
+
qt_backend.init_qt()
|
|
19
|
+
QtWidgets = qt_backend.QtWidgets
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ShopApp:
|
|
23
|
+
"""
|
|
24
|
+
Готовое приложение «из коробки».
|
|
25
|
+
|
|
26
|
+
Пример:
|
|
27
|
+
from deskkit import ShopApp, AppConfig
|
|
28
|
+
|
|
29
|
+
ShopApp(AppConfig(
|
|
30
|
+
title="Обувной магазин",
|
|
31
|
+
database="shoe_store",
|
|
32
|
+
password="root",
|
|
33
|
+
)).run()
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: AppConfig | None = None):
|
|
37
|
+
self.config = config or AppConfig()
|
|
38
|
+
qt_backend.init_qt(self.config.qt_backend)
|
|
39
|
+
self.db = Database(self.config)
|
|
40
|
+
self.auth = AuthService(self.db)
|
|
41
|
+
self._app: QtWidgets.QApplication | None = None
|
|
42
|
+
self._main_window: MainWindow | None = None
|
|
43
|
+
|
|
44
|
+
def run(self) -> int:
|
|
45
|
+
"""Запустить приложение или сгенерировать автономный проект."""
|
|
46
|
+
if self.config.export_project and not self.config.library_runtime:
|
|
47
|
+
from deskkit.generator import export_project, run_generated
|
|
48
|
+
|
|
49
|
+
out = export_project(self.config)
|
|
50
|
+
print(f"Проект создан: {out}")
|
|
51
|
+
if self.config.run_generated:
|
|
52
|
+
return run_generated(self.config, out)
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
return self._run_library()
|
|
56
|
+
|
|
57
|
+
def _run_library(self) -> int:
|
|
58
|
+
"""Запуск через deskkit (режим разработки)."""
|
|
59
|
+
ensure_images_dir(self.config)
|
|
60
|
+
|
|
61
|
+
self._app = QtWidgets.QApplication.instance()
|
|
62
|
+
if self._app is None:
|
|
63
|
+
self._app = QtWidgets.QApplication(sys.argv)
|
|
64
|
+
self._app.setStyleSheet(APP_STYLE)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
if not self.db.test_connection():
|
|
68
|
+
QtWidgets.QMessageBox.critical(
|
|
69
|
+
None,
|
|
70
|
+
"Ошибка подключения",
|
|
71
|
+
"Не удалось подключиться к базе данных.\n"
|
|
72
|
+
"Проверьте настройки AppConfig.",
|
|
73
|
+
)
|
|
74
|
+
return 1
|
|
75
|
+
except DatabaseError as exc:
|
|
76
|
+
QtWidgets.QMessageBox.critical(None, "Ошибка подключения", str(exc))
|
|
77
|
+
return 1
|
|
78
|
+
|
|
79
|
+
if not self._show_login():
|
|
80
|
+
return 0
|
|
81
|
+
return qt_backend.exec_app(self._app)
|
|
82
|
+
|
|
83
|
+
def _show_login(self) -> bool:
|
|
84
|
+
def on_success(user: dict) -> None:
|
|
85
|
+
self._show_main()
|
|
86
|
+
|
|
87
|
+
login = LoginWindow(self.config, self.auth, on_success)
|
|
88
|
+
result = login.exec()
|
|
89
|
+
return result == QtWidgets.QDialog.DialogCode.Accepted
|
|
90
|
+
|
|
91
|
+
def _show_main(self) -> None:
|
|
92
|
+
if self._main_window:
|
|
93
|
+
self._main_window.close()
|
|
94
|
+
|
|
95
|
+
def on_logout() -> None:
|
|
96
|
+
self._show_login()
|
|
97
|
+
|
|
98
|
+
self._main_window = MainWindow(
|
|
99
|
+
self.config,
|
|
100
|
+
self.db,
|
|
101
|
+
self.auth,
|
|
102
|
+
on_logout=on_logout,
|
|
103
|
+
)
|
|
104
|
+
self._main_window.show()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run_app(config: AppConfig | None = None) -> int:
|
|
108
|
+
"""Быстрый запуск приложения одной функцией."""
|
|
109
|
+
return ShopApp(config).run()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run_from_json(path: str) -> int:
|
|
113
|
+
"""Запуск по JSON-файлу настроек."""
|
|
114
|
+
return ShopApp(AppConfig.from_json(path)).run()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Авторизация пользователей."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from deskkit.database import Database
|
|
8
|
+
from deskkit.exceptions import AuthError
|
|
9
|
+
from deskkit.roles import ADMIN, CLIENT, GUEST, MANAGER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_guest() -> dict[str, Any]:
|
|
13
|
+
return {
|
|
14
|
+
"id_user": 0,
|
|
15
|
+
"login": "guest",
|
|
16
|
+
"first_name": "Гость",
|
|
17
|
+
"last_name": "",
|
|
18
|
+
"middle_name": None,
|
|
19
|
+
"id_role": 0,
|
|
20
|
+
"role_name": GUEST,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def user_display_name(user: dict[str, Any]) -> str:
|
|
25
|
+
parts = [user.get("last_name", ""), user.get("first_name", "")]
|
|
26
|
+
middle = user.get("middle_name")
|
|
27
|
+
if middle:
|
|
28
|
+
parts.append(middle)
|
|
29
|
+
name = " ".join(p for p in parts if p).strip()
|
|
30
|
+
return name or user.get("login", "Пользователь")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AuthService:
|
|
34
|
+
def __init__(self, db: Database):
|
|
35
|
+
self.db = db
|
|
36
|
+
self.current_user: dict[str, Any] | None = None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def role(self) -> str:
|
|
40
|
+
if not self.current_user:
|
|
41
|
+
return GUEST
|
|
42
|
+
return str(self.current_user.get("role_name", GUEST))
|
|
43
|
+
|
|
44
|
+
def login(self, login: str, password: str) -> dict[str, Any]:
|
|
45
|
+
if not login.strip() or not password.strip():
|
|
46
|
+
raise AuthError("Введите логин и пароль")
|
|
47
|
+
|
|
48
|
+
user = self.db.authenticate(login.strip(), password)
|
|
49
|
+
if not user:
|
|
50
|
+
raise AuthError("Неверный логин или пароль")
|
|
51
|
+
|
|
52
|
+
self.current_user = user
|
|
53
|
+
return user
|
|
54
|
+
|
|
55
|
+
def login_as_guest(self) -> dict[str, Any]:
|
|
56
|
+
self.current_user = make_guest()
|
|
57
|
+
return self.current_user
|
|
58
|
+
|
|
59
|
+
def logout(self) -> None:
|
|
60
|
+
self.current_user = None
|
|
61
|
+
|
|
62
|
+
def is_admin(self) -> bool:
|
|
63
|
+
return self.role == ADMIN
|
|
64
|
+
|
|
65
|
+
def is_manager(self) -> bool:
|
|
66
|
+
return self.role == MANAGER
|
|
67
|
+
|
|
68
|
+
def is_client(self) -> bool:
|
|
69
|
+
return self.role == CLIENT
|
|
70
|
+
|
|
71
|
+
def is_guest(self) -> bool:
|
|
72
|
+
return self.role == GUEST
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Поиск, фильтрация и сортировка товаров."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _text(value: Any) -> str:
|
|
9
|
+
if value is None:
|
|
10
|
+
return ""
|
|
11
|
+
return str(value).lower()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def search_products(
|
|
15
|
+
products: list[dict[str, Any]],
|
|
16
|
+
query: str,
|
|
17
|
+
fields: list[str] | None = None,
|
|
18
|
+
) -> list[dict[str, Any]]:
|
|
19
|
+
"""Поиск в реальном времени по текстовым полям."""
|
|
20
|
+
if not query.strip():
|
|
21
|
+
return list(products)
|
|
22
|
+
|
|
23
|
+
q = query.strip().lower()
|
|
24
|
+
if fields is None:
|
|
25
|
+
fields = [
|
|
26
|
+
"article_number",
|
|
27
|
+
"product_name",
|
|
28
|
+
"description",
|
|
29
|
+
"category_name",
|
|
30
|
+
"manufacturer_name",
|
|
31
|
+
"supplier_name",
|
|
32
|
+
"unit",
|
|
33
|
+
"availability_status",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
result = []
|
|
37
|
+
for product in products:
|
|
38
|
+
for field in fields:
|
|
39
|
+
if q in _text(product.get(field)):
|
|
40
|
+
result.append(product)
|
|
41
|
+
break
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def filter_by_supplier(
|
|
46
|
+
products: list[dict[str, Any]],
|
|
47
|
+
supplier_id: int | None,
|
|
48
|
+
) -> list[dict[str, Any]]:
|
|
49
|
+
if supplier_id is None or supplier_id <= 0:
|
|
50
|
+
return list(products)
|
|
51
|
+
return [p for p in products if int(p.get("id_supplier") or 0) == supplier_id]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def filter_by_category(
|
|
55
|
+
products: list[dict[str, Any]],
|
|
56
|
+
category_id: int | None,
|
|
57
|
+
) -> list[dict[str, Any]]:
|
|
58
|
+
if category_id is None or category_id <= 0:
|
|
59
|
+
return list(products)
|
|
60
|
+
return [p for p in products if int(p.get("id_category") or 0) == category_id]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def sort_products(
|
|
64
|
+
products: list[dict[str, Any]],
|
|
65
|
+
field: str = "stock_quantity",
|
|
66
|
+
ascending: bool = True,
|
|
67
|
+
) -> list[dict[str, Any]]:
|
|
68
|
+
def key_fn(item: dict[str, Any]) -> Any:
|
|
69
|
+
value = item.get(field, 0)
|
|
70
|
+
if value is None:
|
|
71
|
+
return 0
|
|
72
|
+
return value
|
|
73
|
+
|
|
74
|
+
return sorted(products, key=key_fn, reverse=not ascending)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def apply_catalog_filters(
|
|
78
|
+
products: list[dict[str, Any]],
|
|
79
|
+
*,
|
|
80
|
+
search: str = "",
|
|
81
|
+
supplier_id: int | None = None,
|
|
82
|
+
category_id: int | None = None,
|
|
83
|
+
sort_field: str = "product_name",
|
|
84
|
+
sort_asc: bool = True,
|
|
85
|
+
) -> list[dict[str, Any]]:
|
|
86
|
+
"""Комбинирует поиск, фильтры и сортировку."""
|
|
87
|
+
result = search_products(products, search)
|
|
88
|
+
result = filter_by_supplier(result, supplier_id)
|
|
89
|
+
result = filter_by_category(result, category_id)
|
|
90
|
+
return sort_products(result, sort_field, sort_asc)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def row_style(product: dict[str, Any], rules: list[dict[str, Any]]) -> dict[str, str]:
|
|
94
|
+
"""Возвращает стили строки по правилам условного форматирования."""
|
|
95
|
+
styles: dict[str, str] = {}
|
|
96
|
+
for rule in rules:
|
|
97
|
+
condition: Callable[[dict[str, Any]], bool] = rule["when"]
|
|
98
|
+
if condition(product):
|
|
99
|
+
if "background" in rule:
|
|
100
|
+
styles["background"] = rule["background"]
|
|
101
|
+
if "foreground" in rule:
|
|
102
|
+
styles["foreground"] = rule["foreground"]
|
|
103
|
+
return styles
|