Implementing setEditStrategy in editable QSqlQuery

2019-04-29 01:18发布

问题:

This is a follow-up to this question. In there, we created an editable subclass of QSqlQueryModel, to use with complex queries. Now I need to add a functionality like QTableModel's setEditStrategy so I can cache all changes and accept or revert them using buttons. PyQt apparently doesn't allow multiple inheritance and I cannot find sufficient documentation to re-implement this method in my custom model, therefor here's the question:

How can I re-implement QSqlTableModel.setEditStragety (or something like it) including RevertAll() and SubmitAll() in an editable QSqlQueryModel?

Here's a CVME: (I have out-commented the parts of of my Example class I would like to get working)

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel, QSqlTableModel
from PyQt5.QtWidgets import QApplication, QTableView, QWidget, QGridLayout
from PyQt5.Qt import QPushButton

db_file = "test.db"


def create_connection(file_path):
    db = QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName(file_path)
    if not db.open():
        print("Cannot establish a database connection to {}!".format(file_path))
        return False
    return True


def fill_tables():
    q = QSqlQuery()
    q.exec_("DROP TABLE IF EXISTS Manufacturers;")
    q.exec_("CREATE TABLE Manufacturers (Company TEXT, Country TEXT);")
    q.exec_("INSERT INTO Manufacturers VALUES ('VW', 'Germany');")
    q.exec_("INSERT INTO Manufacturers VALUES ('Honda' , 'Japan');")

    q.exec_("DROP TABLE IF EXISTS Cars;")
    q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year INT);")
    q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")


class SqlQueryModel_editable(QSqlQueryModel):
    """a subclass of QSqlQueryModel where individual columns can be defined as editable
    """
    def __init__(self, editables):
        """editables should be a dict of format: 
        {INT editable_column_nr : (STR update query to be performed when changes are made on this column
                                   INT model's column number for the filter-column (used in the where-clause),
                                   )} 
        """
        super().__init__()
        self.editables = editables

    def flags(self, index):
        fl = QSqlQueryModel.flags(self, index)
        if index.column() in self.editables:
            fl |= Qt.ItemIsEditable
        return fl

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            mycolumn = index.column()
            if mycolumn in self.editables:
                (query, filter_col) = self.editables[mycolumn]
                filter_value = self.index(index.row(), filter_col).data()
                q = QSqlQuery(query.format(value, filter_value))
                result = q.exec_()
                if result:
                    self.query().exec_()
                else:
                    print(self.query().lastError().text())
                return result
        return QSqlQueryModel.setData(self, index, value, role)

    def setFilter(self, myfilter):
        text = (self.query().lastQuery() + " WHERE " + myfilter)
        self.setQuery(text)


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(400, 150)

        self.createModel()
        self.initUI()

    def createModel(self):
        editables = {1 : ("UPDATE Manufacturers SET Country = '{}' WHERE Company = '{}'", 2)}
        self.model = SqlQueryModel_editable(editables)
        query = '''
            SELECT (comp.company || " " || cars.model) as Car,
                    comp.Country,
                    cars.company,
                    (CASE WHEN cars.Year > 2000 THEN 'yes' ELSE 'no' END) as this_century
            from manufacturers comp left join cars
                on comp.company = cars.company
            '''
        q = QSqlQuery(query)
        self.model.setQuery(q)
        self.model.setFilter("cars.Company = 'VW'")
#         self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)

    def initUI(self):
        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.view = QTableView()
        self.view.setModel(self.model)
        self.view.hideColumn(2)
        self.layout.addWidget(self.view,0,0,1,2)

        self.accept_btn = QPushButton("Accept Changes")
#         self.accept_btn.clicked.connect(self.model.submitAll)
        self.layout.addWidget(self.accept_btn, 1,0)
        self.reject_btn = QPushButton("Reject Changes")
#         self.reject_btn.clicked.connect(self.model.revertAll)
        self.layout.addWidget(self.reject_btn, 1,1)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    if not create_connection(db_file):
        sys.exit(-1)

    fill_tables()

    ex = Example()
    ex.show()
    sys.exit(app.exec_())

Edit to clarify:

I need an editable QSqlQueryModel, on which I can use submitAll() and revertAll(), so that changes to the model's data are only accepted after an Accept-button is clicked, or can be reverted using a "Reject" button.