Документация — 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 пикселей и ничего не будет видно.

Сообщения пользователю

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
Это всё. Любое окно в проекте — это этот шаблон + логика внутри методов.