通过 QTableView 编辑 QSqlTableModel 时行 creation/deletion 出现问题

Issues with row creation/deletion when editing QSqlTableModel via QTableView

我将 PySide6 与 Python 3.10 一起使用来创建一个使用 SQLite 数据库记录一些数据点的应用程序。

我在使用 QtSql 模块时遇到困难,特别是关于 QTableView-QSqlTableModel 交互以及 create/edit/delete 数据库中存在的记录的能力。

这是我要实现的最小可重现示例:

import logging
import sys

from PySide6.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMainWindow,
    QPushButton,
    QTableView,
    QVBoxLayout,
    QWidget,
)


class CustomSqlModel(QSqlTableModel):
    def __init__(self, parent=None):
        QSqlTableModel.__init__(self, parent=parent)
        self.setTable("records")
        self.setEditStrategy(QSqlTableModel.OnFieldChange)
        self.select()


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        main_layout = QVBoxLayout()
        buttons_layout = QHBoxLayout()

        self.view = QTableView()
        main_layout.addWidget(self.view)
        main_layout.addLayout(buttons_layout)

        self.add_button = QPushButton("+")
        self.add_button.pressed.connect(self.add_action)

        self.remove_button = QPushButton("-")
        self.remove_button.pressed.connect(self.remove_action)

        buttons_layout.addWidget(self.add_button)
        buttons_layout.addWidget(self.remove_button)

        widget = QWidget()
        widget.setLayout(main_layout)
        self.setCentralWidget(widget)

        self.connect_to_db()

    def add_action(self):
        new_record = self.model.record()
        new_record.setValue("point", 0)
        self.model.insertRecord(self.model.rowCount(), new_record)
        self.model.submitAll()
        self.model.select()

    def remove_action(self):
        selected = self.view.currentIndex()
        self.model.removeRow(selected.row())
        self.model.submitAll()
        self.model.select()


    def connect_to_db(self):
        self.database = QSqlDatabase.database()
        if not self.database.isValid():
            self.database = QSqlDatabase.addDatabase("QSQLITE")
        self.database.setDatabaseName(f"test.db")
        if not self.database.open():
            logging.error("Failed to open db")
            self.database = None
            return

        self.database.open()

        if "records" not in self.database.tables():
            query = QSqlQuery()
            if not query.exec(
                """
                CREATE TABLE IF NOT EXISTS records(
                point REAL
                )
                """
            ):
                logging.error("Failed to query db")

            if not query.exec(
                """
                INSERT INTO records VALUES (0);
                """
            ):
                logging.error("Failed to create initial data in db")

        self.model = CustomSqlModel()
        self.view.setModel(self.model)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

当应用程序启动时,我寻找一个现有的 SQLite 数据库并加载它。如果它不存在,我创建它并向其添加一个 table records(一个包含浮点值的列)。

界面中间的 QTableView 小部件显示加载的数据库,可以从那里直接编辑记录。

小部件底部有两个按钮:add_buttonremove_button,用于向 records table 添加一行和删除当前选择的一个。

我的问题来自 QTableView 的行为,当 creating/removing 行与我的 UI 时:添加行一开始似乎工作正常,但删除其中一个新创建的行会删除所有行只是一个选择。此外,编辑其中一行然后删除其中任何一行都不会导致任何删除,并且所有行都将获得与编辑后的行相同的值。

问题似乎与我的实现有关,因为如果我使用 SQL 编辑器(例如 DBeaver 或 DB Browser for Sqlite)编辑 table,则 created/edited 行会正确显示在申请中。

我做错了什么?

此处的问题与您的数据库结构有关。虽然创建没有主键的数据库是有效的,但这通常会导致问题,因为它可能会强制 Qt 在通过视图编辑 SQL 模型时尝试使用所有值的组合来识别行。如果每一行都没有唯一的值组合,这几乎肯定会产生不可预测的行为。

在您的示例中,每一行都使用值 0 进行了初始化。如果保持不变,删除具有该值的任何行将删除它们,因为(就 Qt 而言)它们实际上都具有相同的键。

要修复您的示例,您应该简单地添加一个主键,如下所示:

class MainWindow(QMainWindow):
    ...
    def connect_to_db(self):
        ...
        if "records" not in self.database.tables():
            query = QSqlQuery()
            if not query.exec(
                """
                CREATE TABLE IF NOT EXISTS records(
                    ID INTEGER PRIMARY KEY,
                    point REAL
                )
                """
            ):
                logging.error("Failed to query db")

            if not query.exec(
                """
                INSERT INTO records VALUES (NULL,0);
                """
            ):
                logging.error("Failed to create initial data in db")