QSqlRelationalTableModel, пример из книги не работает

Не получается запустить пример из книги Прохоренок.

Строки не добавляются и не удаляются.

Может, кто-нибудь подскажет или свое напишет, буду очень благодарен.

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("QRelationalSqlTableModel")
con = QtSql.QSqlDatabase.addDatabase('QSQLITE')
con.setDatabaseName('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(430, 250)
window.show()
sys.exit(app.exec_())

Должен быть результат: Результат


Ответы (1 шт):

Автор решения: S. Nick

Я не читал данную книгу. То что ваш пример написан не в ООП, немного настораживает.
Вы не привели скрипт, которые создает БД и необходимые таблицы.

Я попробовал представить для вас свой взгляд на вашу задачу.
Это примерный - вариант возможного решения, т.е. направление куда двигаться.

Обратите внимание:

  • БД называется test_1318914.db;

  • в методе fillTable() создаются две таблицы - category и good.
    Название полей возможно не совпадает с вашими. После первого входа/выхода из приложения, закомментируйте вызов данного метода, чтобы таблицы не создавались при каждом входе в приложение:

    # self.fillTable()

  • в таблице category две записи с id=1 и id=2.
    Если таблица good содержит ключи, которые относятся к несуществующим строкам в таблице category, строки, содержащие недопустимые ключи, не будут отображаться в модели.
    Пользователь или база данных несут ответственность за поддержание ссылочной целостности.

Класс QSqlRelationalTableModel предоставляет редактируемую модель данных для одной таблицы базы данных с поддержкой внешнего ключа.
Больше https://doc.qt.io/qt-5/qsqlrelationaltablemodel.html

void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation)

Позволяет указанному столбцу быть внешним индексом, заданным отношением.

    self.model.setRelation(2, QtSql.QSqlRelation(
        "category", 
        "id", 
        "catname")) 

Вызов setRelation() указывает, что столбец 2 в таблице good является внешним ключом, который сопоставляется с полем id таблицы category, и что представление должно представлять пользователю поле catname (наименование категории).


main.py

import sys
from PyQt5 import QtSql
from PyQt5.Qt import *
 

class Dialog(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Input Dialog')
        self.line_edit_name = QLineEdit()
        self.line_edit_quantity = QLineEdit()
        self.line_edit_category = QLineEdit()

        form_layout = QFormLayout()
        form_layout.addRow('Name:', self.line_edit_name)
        form_layout.addRow('quantity:', self.line_edit_quantity)
        form_layout.addRow('category:', self.line_edit_category)

        button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

        main_layout = QVBoxLayout(self)
        main_layout.addLayout(form_layout)
        main_layout.addWidget(button_box)
        

class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.createConnection()
        self.fillTable()                                        # !!!
        self.createModel()
        self.initUI()
        
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        btnAdd = QPushButton("&Добавить запись")
        btnAdd.clicked.connect(self.addRecord)
        btnDel = QPushButton("&Удалить запись")
        btnDel.clicked.connect(self.delRecord)
        
        layout = QVBoxLayout(self.centralWidget)
        layout.addWidget(self.view)
        layout.addWidget(btnAdd)
        layout.addWidget(btnDel)

    def createConnection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName("test_1318914.db")              # !!! .db
        if not self.db.open():
            print("Cannot establish a database connection")
            return False

    def fillTable(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()
        #                             vvvvvvvv
        q.exec_("DROP TABLE IF EXISTS category;")
        q.exec_("CREATE TABLE category (id INT PRIMARY KEY, catname TEXT);") 
        q.exec_("INSERT INTO category VALUES (1, 'Расходники');")
        q.exec_("INSERT INTO category VALUES (2, 'Носители');")

        #                             vvvv
        q.exec_("DROP TABLE IF EXISTS good;")
        q.exec_("CREATE TABLE good (Name TEXT, Quantity INT, Category INT);")
        q.exec_("INSERT INTO good VALUES ('Барабан для принтера', 8, 1);")
        q.exec_("INSERT INTO good VALUES ('Бумага для принтера', 3, 1);")
        q.exec_("INSERT INTO good VALUES ('Дискета', 10, 2);")
        self.db.commit()

    def createModel(self):
        self.model = QtSql.QSqlRelationalTableModel()
        self.model.setTable("good")
        self.model.setHeaderData(0, Qt.Horizontal, "Название")
        self.model.setHeaderData(1, Qt.Horizontal, "Кол-во")
        self.model.setHeaderData(2, Qt.Horizontal, "Категория")
        self.set_relation()
        self.model.select()

    def initUI(self):
        self.view = QTableView()
        self.view.setModel(self.model)
        self.view.setColumnWidth(0, 150)
        mode = QAbstractItemView.SingleSelection
        self.view.setSelectionMode(mode)

    def closeEvent(self, event):
        if (self.db.open()):
            self.db.close()
            
    def set_relation(self):
        self.model.setRelation(2, QtSql.QSqlRelation(
            "category", 
            "id", 
            "catname"
        ))       

    def addRecord(self):
        inputDialog = Dialog()
        rez = inputDialog.exec()
        if not rez:
            msg = QMessageBox.information(self, 'Внимание', 'Диалог сброшен.')
            return  
            
        name = inputDialog.line_edit_name.text()
        quantity = inputDialog.line_edit_quantity.text()
        category = inputDialog.line_edit_category.text()
        if (not name) or (not quantity) or (not category):
            msg = QMessageBox.information(self, 
                'Внимание', 'Заполните пожалуйста все поля.')
            return     

        r = self.model.record()
        r.setValue(0, name)
        r.setValue(1, int(quantity))
        r.setValue(2, int(category))
        
        self.model.insertRecord(-1, r)
        self.model.select()

    def delRecord(self):
        row = self.view.currentIndex().row()
        if row == -1:
            msg = QMessageBox.information(self, 
                'Внимание', 'Выберите запись для удаления.')
            return          

        name = self.model.record(row).value(0)
        quantity = self.model.record(row).value(1)
        category = self.model.record(row).value(2)
        
        inputDialog = Dialog()
        inputDialog.setWindowTitle('Удалить запись ???')
        inputDialog.line_edit_name.setText(name)
        inputDialog.line_edit_quantity.setText(str(quantity))
        inputDialog.line_edit_category.setText(str(category))       
        rez = inputDialog.exec()
        if not rez:
            msg = QMessageBox.information(self, 'Внимание', 'Диалог сброшен.')
            return  

        self.model.setRelation(2, QtSql.QSqlRelation())
        self.model.select()
        self.model.removeRow(row)
        self.set_relation()        
        self.model.select()  
        
        msg = QMessageBox.information(self, 'Успех', 'Запись удалена.')        
  

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Example()
    w.setWindowTitle("QRelationalSqlTableModel")
    w.resize(430, 250)
    w.show()
    sys.exit(app.exec_())

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

→ Ссылка