diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea06b59 --- /dev/null +++ b/README.md @@ -0,0 +1,241 @@ +# Шпаргалка — экзамен (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. Тайм-менеджмент (чек-лист перед экзаменом) + +0–5 мин: открыть проект, убедиться, что main.py запускается. + +5–25 мин: реализовать/проверить LoginWindow + LoginDriver + DB authenticate. + +25–55 мин: реализовать AdminScreen (TableTab) и подключить QSqlTableModel (bookings/payments first). + +55–75 мин: добавить кнопки Add/Delete/Export CSV для текущей вкладки. + +75–90 мин: протестировать фильтр по датам (bookings/payments). + +90–120 мин: мелкие фиксы, подготовка к демонстрации (порядок запуска, что сказать). + +14. Что говорить на защите (30–60 сек) + +"Архитектура: 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. Последние советы (психология) + +Если устал — 20–30 мин сна/отдыха дадут больше, чем бессонная борьба. + +На экзамене — работая стабильно и объясняя логику, можно получить высокий балл даже без всех «крутых» фич. + +Делай минимально рабочую вещь первым, потом дополняй. + +Удачи на экзамене — ты уже сделал огромную работу. +Если хочешь, могу за 10–15 минут сжато подготовить короткую шпаргалку именно по запуску проекта (команды, быстрые проверки). \ No newline at end of file