diff --git a/README.md b/README.md deleted file mode 100644 index ea06b59..0000000 --- a/README.md +++ /dev/null @@ -1,241 +0,0 @@ -# Шпаргалка — экзамен (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