Документация — PyQt5 + MySQL
Магазин обуви | Экзамен
Как устроен проект
main.py — главный файл, здесь вся логика auth.py — сгенерированная форма авторизации (не трогаем) reg.py — сгенерированная форма регистрации (не трогаем) window.py — сгенерированная главная форма (не трогаем) card.py — сгенерированная карточка товара (не трогаем) photo/ — папка с фотографиями товаров logo.JPG — логотип
Файлы auth.py, reg.py, window.py, card.py — это просто описание того как выглядит форма (кнопки, поля, лейблы). Мы их импортируем и используем, но всю логику пишем только в main.py.
Импорты — что и зачем
import sys # нужен для запуска приложения (sys.exit) from PyQt5 import QtGui, QtWidgets, QtCore # всё от PyQt: окна, кнопки, картинки from auth import Ui_Form as Authform # берём форму из auth.py, даём ей имя Authform from reg import Ui_Form as Regform # то же для регистрации from window import Ui_MainWindow as Mainform # главное окно (тут класс Ui_MainWindow, не Ui_Form) from card import Ui_Form as CardForm # карточка товара import mysql.connector # библиотека для работы с MySQL
Подключение к базе данных
# словарь с настройками подключения DB = { "host": "localhost", # адрес сервера БД (обычно localhost) "user": "root", # имя пользователя MySQL "password": "9110", # пароль "database": "store" # название базы данных } def get_connection(): return mysql.connector.connect(**DB) # ** означает "распакуй словарь как аргументы" # это то же самое что написать: # mysql.connector.connect(host="localhost", user="root", ...)
Алгоритм работы с БД — всегда одинаковый:
conn = get_connection() # 1. открываем подключение cursor = conn.cursor() # 2. создаём курсор (через него пишем запросы) cursor.execute("SELECT ...") # 3. выполняем SQL запрос result = cursor.fetchone() # 4. получаем результат conn.close() # 5. закрываем подключение (важно!)
fetchone vs fetchall:
cursor.fetchone() # одна строка → возвращает кортеж (1, "Иван", "admin") cursor.fetchall() # все строки → список кортежей [(1,"Иван"), (2,"Мария")]
Параметры в запрос — защита от SQL-инъекций:
# ПРАВИЛЬНО — параметры через %s и кортеж cursor.execute( "SELECT * FROM users WHERE login = %s AND password = %s", (login, password) ) # НЕПРАВИЛЬНО — никогда так не делай cursor.execute(f"SELECT * FROM users WHERE login = '{login}'")
f-строки в SQL — опасно! Пользователь может написать любой SQL в поле ввода. Всегда используй
%s с кортежем.Класс-окно — основа всего
class Authwindow(QtWidgets.QWidget): # наследуем QWidget — пустое окно от PyQt def __init__(self): super().__init__() # инициализируем родителя (QWidget) self.ui = Authform() # создаём объект формы из auth.py self.ui.setupUi(self) # натягиваем форму на текущее окно (self) # подключаем кнопки к методам # формула: self.ui.ИМЯ_КНОПКИ.clicked.connect(self.ИМЯ_МЕТОДА) self.ui.togeg_button.clicked.connect(self.go_to_reg) self.ui.autorize.clicked.connect(self.go_to_main) self.ui.guest_button.clicked.connect(self.go_as_guest)
QWidget vs QMainWindow:
class Authwindow(QtWidgets.QWidget): # обычные окна (формы) — QWidget class Mainwindow(QtWidgets.QMainWindow): # главное окно с меню и статусбаром — QMainWindow
Смотри на файл формы: если там
QtWidgets.QMainWindow() — наследуй QMainWindow. Если QtWidgets.QWidget() — наследуй QWidget.self — почему везде
НЕПРАВИЛЬНО
def go_to_reg(self): reg_window = Regwindow() reg_window.show() # функция закончилась → # reg_window удалилась → # окно мгновенно закрылось!
ПРАВИЛЬНО
def go_to_reg(self): self.reg_window = Regwindow() self.reg_window.show() # переменная объекта — живёт # пока живёт Authwindow
Правило: всё что должно жить дольше одной функции — пиши через
self.Переключение окон
def go_to_reg(self): self.reg_window = Regwindow() # создаём объект нового окна self.reg_window.show() # показываем его self.close() # закрываем текущее def go_to_main(self): login = self.ui.login_autorize.text().strip() password = self.ui.password_autorize.text().strip() try: conn = get_connection() cursor = conn.cursor() cursor.execute( "SELECT full_name FROM users WHERE login = %s AND password = %s", (login, password) ) user = cursor.fetchone() conn.close() if user: full_name = user[0] # user = ("Иван Иванов",) → user[0] = "Иван Иванов" self.main_window = Mainwindow(full_name) self.main_window.show() self.close() else: QtWidgets.QMessageBox.warning(self, "Ошибка", "Неверный логин или пароль") except Exception as e: print(e) QtWidgets.QMessageBox.critical(self, "Ошибка БД", str(e)) def go_as_guest(self): self.main_window = Mainwindow("Гость") self.main_window.show() self.close()
Главное окно — логотип и имя пользователя
class Mainwindow(QtWidgets.QMainWindow): def __init__(self, full_name): # принимаем full_name из окна авторизации super().__init__() self.ui = Mainform() self.ui.setupUi(self) # вставляем имя в лейбл self.ui.full_name_label.setText(full_name) # загружаем логотип pixmap = QtGui.QPixmap("logo.JPG") pixmap = pixmap.scaled( self.ui.photo_label.width(), # ширина лейбла self.ui.photo_label.height(), # высота лейбла QtCore.Qt.KeepAspectRatio, # сохраняем пропорции QtCore.Qt.SmoothTransformation # плавное масштабирование ) self.ui.photo_label.setPixmap(pixmap)
Карточки товаров в ScrollArea
# создаём вертикальный layout self.scroll_layout = QtWidgets.QVBoxLayout() self.scroll_layout.setAlignment(QtCore.Qt.AlignTop) # прижимаем к верху self.ui.scrollAreaWidgetContents.setLayout(self.scroll_layout) self.all_cards = [] # список для хранения карточек (нужен для поиска) self.load_products() # загружаем товары из БД
def load_products(self): try: conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT name, unit, price, sup, manuf, cat, disc, stock, descr, photo FROM products") products = cursor.fetchall() conn.close() for product in products: name, unit, price, sup, manuf, cat, disc, stock, descr, photo = product card_widget = QtWidgets.QWidget() card_widget.setFixedHeight(166) # фиксируем высоту, иначе схлопнется в 0! card_ui = CardForm() card_ui.setupUi(card_widget) card_ui.category_card.setText(f"Категория: {cat}") card_ui.description_card.setText(f"Описание: {descr or ''}") card_ui.sup_card.setText(f"Поставщик: {sup}") card_ui.manuf_card.setText(f"Производитель: {manuf}") card_ui.price_card.setText(f"Цена: {price} руб.") card_ui.unit_card.setText(f"Ед. изм.: {unit}") card_ui.stock_card.setText(f"Остаток: {stock}") card_ui.discount_card.setText(f"{disc}%" if disc else "") if photo: pix = QtGui.QPixmap(f"photo/{photo}").scaled( 141, 141, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation ) card_ui.card_photo.setPixmap(pix) self.scroll_layout.addWidget(card_widget) search_text = f"{name} {cat} {sup} {manuf}".lower() self.all_cards.append((card_widget, search_text)) except Exception as e: print(e) QtWidgets.QMessageBox.critical(self, "Ошибка БД", str(e))
setFixedHeight обязателен! Без него
card_widget схлопнется в 0 пикселей и ничего не будет видно.Поиск в реальном времени
# подключаем: каждый раз когда меняется текст — вызываем filter_products self.ui.search.textChanged.connect(self.filter_products) def filter_products(self): query = self.ui.search.text().lower().strip() for card_widget, search_text in self.all_cards: if query in search_text: card_widget.show() else: card_widget.hide()
Сообщения пользователю
QtWidgets.QMessageBox.warning(self, "Заголовок", "Текст") # жёлтый значок QtWidgets.QMessageBox.critical(self, "Заголовок", "Текст") # красный значок QtWidgets.QMessageBox.information(self, "Заголовок", "Текст") # синий значок
Точка входа
if __name__ == "__main__": # этот блок выполняется только если запускаем main.py напрямую # если main.py импортируют — не выполняется app = QtWidgets.QApplication(sys.argv) # создаём приложение (одно на всю программу!) window = Authwindow() # создаём первое окно window.show() # показываем его sys.exit(app.exec_()) # запускаем цикл событий # sys.exit завершает процесс при закрытии окна
Частые ошибки и как их исправить
| Ошибка | Причина | Решение |
|---|---|---|
| Окно мигает и закрывается | Переменная окна без self |
Писать self.window = ... |
SyntaxError: Qt::AlignCenter |
Старый синтаксис от pyuic | Заменить на QtCore.Qt.AlignCenter |
SyntaxError: QFrame::StyledPanel |
Старый синтаксис от pyuic | Заменить на QtWidgets.QFrame.StyledPanel |
| Карточки не отображаются | Виджет схлопнулся в 0px | Добавить card_widget.setFixedHeight(166) |
| Фото не загружается | Неверный путь | Проверить путь, добавить photo/ перед именем файла |
Access denied к БД |
Неверный пароль | Проверить пароль в словаре DB |
SQL запросы которые нужно знать
-- получить всё из таблицы SELECT * FROM products; -- получить конкретные колонки SELECT name, price, stock FROM products; -- условие SELECT full_name FROM users WHERE login = 'admin' AND password = '123'; -- вставить запись INSERT INTO users (role, full_name, login, password) VALUES ('client', 'Иван', 'ivan', '123'); -- обновить запись UPDATE products SET price = 9999 WHERE id = 1; -- удалить запись DELETE FROM orders WHERE id = 5; -- количество записей SELECT COUNT(*) FROM products;
Минимальный рабочий шаблон окна
class MyWindow(QtWidgets.QWidget): def __init__(self): super().__init__() self.ui = SomeForm() # форма из .py файла self.ui.setupUi(self) # натягиваем на окно self.ui.btn.clicked.connect(self.on_click) # кнопка → метод def on_click(self): pass
Это всё. Любое окно в проекте — это этот шаблон + логика внутри методов.