На этом шаге мы рассмотрим реализацию такой модели.
Предположим, что мы решили расширить наше простенькое складское приложение, введя разбиение товаров на категории. В базе данных data.sqlite мы создали таблицу category с полями id и catname, а в таблицу good добавили поле category, где будут храниться идентификаторы категорий. Реализовать это можно, например, с помощью следующего приложения:
from PyQt5 import QtWidgets, QtSql import sys # Создаем объект приложения, иначе поддержка баз данных не будет работать арр = QtWidgets.QApplication(sys.argv) con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('c:\\temp\\data.sqlite') if con.open(): # Работаем с базой данных if 'category' not in con.tables(): query = QtSql.QSqlQuery() query.exec("create table category (id integer primary key autoincrement, catname text)") query.prepare("insert into category values(null, :name)") lst = ['Носители', 'Расходники'] query.bindValue(':name', lst) query.execBatch() query.exec("alter table good add column category integer default 2") query.exec("update good set category=1 where id=1") else: # Выводим текст описания ошибки print(con.lastError().text())
Теперь попытаемся вывести содержимое таблицы good на экран с помощью модели QSqlTableModel и компонента таблицы QTableView. И сразу увидим, что в колонке, где показывается содержимое поля category, выводятся числовые идентификаторы категорий (рисунок 1).
Рис.1. Пример складского приложения после доработки: вместо названий категорий выводятся их числовые идентификаторы
Это вполне понятно, но хотелось бы видеть там наименования категорий вместо непонятных цифр.
Исправить это поможет класс QSqlRelationalTableModel, добавляющий уже известной нам модели QSqlTableModel возможность связывать таблицы. Мы указываем поле внешнего ключа, первичную таблицу и в ней - поле первичного ключа и поле, откуда будет взято значение для вывода на экран.
Иерархия наследования класса QSqlRelationalTableModel:
QObject - QAbstractItemModel - QAbstractTableModel - QSqlQueryModel - QSqlTableModel - QSqlRelationalTableModel
Конструктор класса:
<Объект> = QSqlRelationalQueryModel([parent=None][, db = QSqlDatabase()])
Необязательный параметр db задает соединение с базой данных, запрос к которой следует выполнить, - если он не указан, будет использоваться соединение по умолчанию.
Класс QSqlRelationalTableModel наследует все методы из класса QSqlTableModel (см. 172 шаг) и в дополнение к ним определяет следующие полезные для нас методы (полный их список доступен на странице https://doc.qt.io/qt-5/qsqlrelationaltablemodel.html):
Теперь о классе QSqlRelation. Он представляет связь, устанавливаемую между полями таблиц. Конструктор этого класса имеет такой формат:
<Объект> = QSqlRelation(<Имя первичной таблицы>, <Имя поля первичного ключа>, <Имя поля, выводящегося на экран>)
Поля, чьи имена указываются во втором и третьем параметрах, относятся к первичной таблице.
Класс QSqlRelation поддерживает несколько методов, но они не очень нам интересны (полное описание этого класса доступно на странице https://doc.qt.io/qt-5/qsqlrelation.html):
В примере ниже приведен код исправленного складского приложения, а на рисунке 2 - результат работы приложения.
from PyQt5 import QtCore, QtWidgets, QtSql import sys def addRecord(): # Вставляем пустую запись, в которую пользователь сможет # ввести нужные данные stm.insertRow(stm.rowCount()) def delRecord(): # Удаляем запись из модели stm.removeRow(tv.currentIndex().row()) # Выполняем повторное считывание данных в модель, # чтобы убрать пустую "мусорную" запись stm.select() app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("QSqlRelationalTableModel") # Устанавливаем соединение с базой данных con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('c:\\temp\\data.sqlite') con.open() # Создаем модель stm = QtSql.QSqlRelationalTableModel(parent = window) stm.setTable('good') stm.setSort(1, QtCore.Qt.AscendingOrder) # Задаем для поля категории связь с таблицей списка категорий stm.setRelation(3, QtSql.QSqlRelation('category', 'id', 'catname')) stm.select() stm.setHeaderData(1, QtCore.Qt.Horizontal, 'Название') stm.setHeaderData(2, QtCore.Qt.Horizontal, 'Кол-во') stm.setHeaderData(3, QtCore.Qt.Horizontal, 'Категория') vbox = QtWidgets.QVBoxLayout() tv = QtWidgets.QTableView() tv.setModel(stm) tv.hideColumn(0) tv.setColumnWidth(1, 150) tv.setColumnWidth(2, 60) tv.setColumnWidth(3, 150) vbox.addWidget(tv) btnAdd = QtWidgets.QPushButton("&Добавить запись") btnAdd.clicked.connect(addRecord) vbox.addWidget(btnAdd) btnDel = QtWidgets.QPushButton("&Удалить запись") btnDel.clicked.connect(delRecord) vbox.addWidget(btnDel) window.setLayout(vbox) window.resize(400, 250) window.show() sys.exit(app.exec_())
Рис.2. Пример складского приложения, использующего модель QSqlRelationalTableModel:
на экран выводятся названия категорий
На следующем шаге мы рассмотрим использование связанных делегатов.