static-files/README.md

241 lines
No EOL
10 KiB
Markdown
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.

# Шпаргалка — экзамен (pyqt6 + QtSql)
Коротко, по делу. Храни в `README.md` и читай в метро.
---
## 1. Цель на экзамене
- Сделать минимальное рабочее приложение: **логин → ролевое окно (админ/менеджер/клиент)**.
- Главное: видно, работает, объясняешь. Не идеальная архитектура — рабочий код.
---
## 2. Минимальная структура проекта (рабочая)
```
project/
├─ main.py # вызывает Composer.run()
├─ composer.py # composition root: создаёт QApplication, LoginWindow, (после логина) MainWindow/Screen
├─ ui/
│ ├─ base.py # BaseWindow, BaseScreen
│ ├─ windows.py # LoginWindow, MainWindow
│ └─ screens.py # LoginScreen, AdminScreen (QTabWidget / TableTab)
├─ utils/
│ ├─ ui.py # TableTab (QTableView + кнопки)
│ ├─ drivers.py # LoginDriver, AdminDriver (логика кнопок)
│ └─ dialogs.py
├─ db/
│ ├─ core.py # get_connection(), DB wrappers
│ └─ objects.py # User, Rights
└─ requirements.txt
```
---
## 3. Быстрая идея архитектуры (экзамен)
- **UI** — только виджеты (без бизнес-логики).
- **Driver** — логика виджета (обработчики кнопок), вызывает callback/сигналы.
- **Composer** — создаёт окна/драйверы и подписывается на сигналы (переходы экранов).
**Почему callback:** не импортируем Composer в UI (избегаем циклических импортов). Передаём `on_event` в драйвер — драйвер вызывает его.
Пример передачи callback:
```python
# в Composer
driver = LoginDriver(login_screen, on_success=self._on_login_success)
# в LoginDriver
self.on_success(user) # вместо `composer.do(...)`
```
4. Минимум кода для переключения экранов
```python
# Composer._login_fabric()
self.wlogin = LoginWindow()
self.slogin = LoginScreen()
self.wlogin.setCentralWidget(self.slogin)
self.dlogin = LoginDriver(self.slogin, on_success=self._on_login_success)
def _on_login_success(self, user):
if user.rights == Rights.ADMIN:
self._admin_fabric()
self.main.show()
```
5. AdminScreen / TableTab — разметка
QTabWidget с вкладками: bookings, payments, rooms, guests, users, staff.
Вкладка содержит:
(опционально) область фильтра сверху (QDateEdit/QDateTimeEdit + кнопки Фильтровать / Показать все)
QTableView (центр)
кнопки Добавить, Удалить, Экспорт CSV (внизу)
TableTab должен создавать QSqlTableModel и устанавливать view.setModel(model).
6. QSqlTableModel — основные операции
```python
model = QSqlTableModel(db=db)
model.setTable("bookings")
model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit) # удобно для админки
model.select()
view.setModel(model)
# Добавить
model.insertRow(model.rowCount())
model.submitAll() # если OnManualSubmit - вызвать для сохранения
# Удалить (с подтверждением)
model.removeRow(row)
model.submitAll()
# Фильтр (bookings: checkin / checkout)
model.setFilter("DATE(checkin) >= '2025-01-01' AND DATE(checkout) <= '2025-01-10'")
model.select()
```
7. Частые ошибки (и их исправления)
```
QSqlDatabase.drivers(): QSqlDatabase requires a QCoreApplication
— вызовите QApplication([]) (или QCoreApplication) до использования QSqlDatabase.
Lambda в цикле захватывает переменную → все слоты привязаны к последнему tab.
Используйте lambda _, t=tab: handler(t) или functools.partial(handler, tab).
Опечатки: self_current → self._current. QMesssageBox → QMessageBox. StandartButton → StandardButton.
QMessageBox: QMessageBox.information(parent, title, text); QMessageBox.question(...) возвращает StandardButton.
SQL сравнения: используйте >=, <=, не =>.
Если модель не отображает изменения — вызовите model.select() или view.repaint().
Не дублировать подключение DB: создавайте QSqlDatabase.addDatabase(...) один раз, передавайте объект db в TableTab или делайте это в BaseScreen._setup_qsql().
```
8. Экспорт CSV (быстро)
```python
with open("bookings.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
headers = [model.headerData(i, Qt.Orientation.Horizontal) for i in range(model.columnCount())]
writer.writerow(headers)
for r in range(model.rowCount()):
row = [model.data(model.index(r,c)) for c in range(model.columnCount())]
writer.writerow(row)
```
Показываем QMessageBox.information(...) по завершении.
9. Загрузка тестовых xlsx → DB (Windows, быстро, <10 строчек)
Вариант GUI (рекомендуемый на экзамене):
Открыть xlsx в Excel.
File -> Save As → CSV для каждого листа.
В pgAdmin открыть таблицу → Import/Export → выбрать CSV и импортировать.
Вариант psql:
\copy guests(name, surname, patronym, passport_seria, passport_number, phone) FROM 'guests.csv' DELIMITER ',' CSV HEADER;
(исполнить в psql)
10. Если MySQL (pymysql) вместо PostgreSQL
db.core: замените psycopg2 на pymysql:
```python
import pymysql
def get_connection():
return pymysql.connect(host='localhost', port=3306, user='user', password='pw', db='dbname', charset='utf8mb4', cursorclass=pymysql.cursors.Cursor)
```
Важно: QSqlTableModel использует Qt SQL драйверы (QPSQL, QMYSQL).
Если вы хотите, чтобы QSqlTableModel работал с MySQL, нужен Qt-плагин qsqlmysql (в комплекте с Qt).
Если плагина нет, либо используйте QSqlDatabase.addDatabase("QMYSQL") (и установите/расположите плагин), либо не используйте QSqlTableModel: читайте данные через pymysql и реализуйте QAbstractTableModel вручную (сложнее).
Если вы только используете pymysql в db.core и все UI модели по-прежнему QSqlTableModel с QSqlDatabase("QPSQL"), это НЕ прокатит — нужно согласовать.
11. Ошибка QSqlDatabase.drivers() — решение
Создайте QApplication([]) перед вызовом. Пример:
from PyQt6.QtWidgets import QApplication
from PyQt6.QtSql import QSqlDatabase
app = QApplication([])
print(QSqlDatabase.drivers()) # OK
12. Полезные сниппеты (на экзамене — запомнить)
```
lambda capture
for tab in tabs:
tab.btn.clicked.connect(lambda _, t=tab: do_something(t))
submitAll
if not model.submitAll():
print(model.lastError().text())
open DB in BaseScreen
db = QSqlDatabase.addDatabase("QPSQL")
db.setHostName(...)
db.open()
```
13. Тайм-менеджмент (чек-лист перед экзаменом)
05 мин: открыть проект, убедиться, что main.py запускается.
525 мин: реализовать/проверить LoginWindow + LoginDriver + DB authenticate.
2555 мин: реализовать AdminScreen (TableTab) и подключить QSqlTableModel (bookings/payments first).
5575 мин: добавить кнопки Add/Delete/Export CSV для текущей вкладки.
7590 мин: протестировать фильтр по датам (bookings/payments).
90120 мин: мелкие фиксы, подготовка к демонстрации (порядок запуска, что сказать).
14. Что говорить на защите (3060 сек)
"Архитектура: UI — presentation, драйверы — логика виджетов, Composer — собирает приложение и управляет переходами."
"DB: использую PostgreSQL, таблицы: guests, rooms, bookings, payments, staff, users. QSqlTableModel для отображения и редактирования."
"Вкладки: bookings/payments — фильтр по дате; остальные — интерактивный редактор и экспорт CSV."
"Безопасность: подтверждение удаления; ошибки DB показываются через диалоги."
15. Быстрые исправления при баге
nothing happens on click → проверь: (1) слот подключён? (2) метод _setup_db не перезаписывает модель? (3) lambda closure bug?
QSqlDatabase.drivers() error → убедись, что QApplication создан.
filter не срабатывает → проверь SQL-условие и формат даты (YYYY-MM-DD).
16. Последние советы (психология)
Если устал — 2030 мин сна/отдыха дадут больше, чем бессонная борьба.
На экзамене — работая стабильно и объясняя логику, можно получить высокий балл даже без всех «крутых» фич.
Делай минимально рабочую вещь первым, потом дополняй.
Удачи на экзамене — ты уже сделал огромную работу.
Если хочешь, могу за 1015 минут сжато подготовить короткую шпаргалку именно по запуску проекта (команды, быстрые проверки).