pythonpyqtqtableviewqsortfilterproxymodelqsqlrelationaltablemodel

QSortFilterProxyModel headerData


I have 2 QTableViews connected to 2 QSortFilterProxyModels which are connected to only 1 source model (QSqlRelationalTableModel).

On sorting or filtering any of the proxy models, it is reflected in the respective table view. BUT When I modify the header data (Decoration Role) of one of the proxy models, it appears on both table views.

My question, is the header data applied directly to the source model or only the proxy?

I used this to create an sqlite sample database:

conn = sqlite3.connect('customers.db')
c = conn.cursor()
c.execute("PRAGMA foreign_keys=on;")

c.execute("""CREATE TABLE IF NOT EXISTS provinces (
        ProvinceId TEXT PRIMARY KEY, 
        Name TEXT NOT NULL
        )""")

c.execute("""CREATE TABLE IF NOT EXISTS customers (
        CustomerId TEXT PRIMARY KEY, 
        Name TEXT NOT NULL,
        ProvinceId TEXT,
        FOREIGN KEY (ProvinceId) REFERENCES provinces (ProvinceId) 
                ON UPDATE CASCADE
                ON DELETE RESTRICT
        )""")

c.execute("INSERT INTO provinces VALUES ('N', 'Northern')")
c.execute("INSERT INTO provinces VALUES ('E', 'Eastern')")
c.execute("INSERT INTO provinces VALUES ('W', 'Western')")
c.execute("INSERT INTO provinces VALUES ('S', 'Southern')")
c.execute("INSERT INTO provinces VALUES ('C', 'Central')")

c.execute("INSERT INTO customers VALUES ('1', 'customer1', 'N')")
c.execute("INSERT INTO customers VALUES ('2', 'customer2', 'E')")
c.execute("INSERT INTO customers VALUES ('3', 'customer3', 'W')")
c.execute("INSERT INTO customers VALUES ('4', 'customer4', 'S')")
c.execute("INSERT INTO customers VALUES ('5', 'customer5', 'C')")

conn.commit()
conn.close()

and this is the window that show my problem when applying a decoration for the proxy model:

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.db = QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName("customers.db")
        self.db.open()

        self.model = QSqlRelationalTableModel(self, self.db)
        self.model.setTable("customers")
        self.model.select()

        self.proxy1 = QSortFilterProxyModel()
        self.proxy1.setSourceModel(self.model)

        self.proxy2 = QSortFilterProxyModel()
        self.proxy2.setSourceModel(self.model)

        hBox = QHBoxLayout()

        self.tblView1 = QTableView()
        self.tblView1.setModel(self.proxy1)

        self.tblView2 = QTableView()
        self.tblView2.setModel(self.proxy2)

        hBox.addWidget(self.tblView1)
        hBox.addWidget(self.tblView2)

        icon = QIcon(QPixmap("Resources/icon.png"))

        self.proxy1.setHeaderData(
            1, Qt.Orientation.Horizontal, icon, Qt.ItemDataRole.DecorationRole)

        widget = QWidget()
        widget.setLayout(hBox)
        self.setLayout(hBox)
        self.show()


def main():
    App = QApplication(sys.argv)
    window = Window()
    sys.exit(App.exec_())


if __name__ == '__main__':
    main()

Solution

  • The problem is caused because the proxymodel's setHeaderData method invokes the sourcemodel's setHeaderData, that is, it is equivalent to calling the sourceModel's method causing it to also propagate to the other proxymodels. A possible solution is to override the headerData method of the proxymodel so that it returns the desired value only in that proxymodel.

    import sys
    
    from PyQt5.QtCore import QSortFilterProxyModel, Qt
    from PyQt5.QtGui import QIcon, QPixmap
    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableView, QApplication
    from PyQt5.QtSql import QSqlDatabase, QSqlRelationalTableModel
    
    
    class SortFilterProxyModel(QSortFilterProxyModel):
        def headerData(self, section, orientation, role=Qt.DisplayRole):
            if (
                section == 1
                and orientation == Qt.Horizontal
                and role == Qt.ItemDataRole.DecorationRole
            ):
                return QIcon(QPixmap("Resources/icon.png"))
            return super().headerData(section, orientation, role)
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.db = QSqlDatabase.addDatabase("QSQLITE")
            self.db.setDatabaseName("customers.db")
            self.db.open()
    
            self.model = QSqlRelationalTableModel(self, self.db)
            self.model.setTable("customers")
            self.model.select()
    
            self.proxy1 = SortFilterProxyModel()
            self.proxy1.setSourceModel(self.model)
    
            self.proxy2 = QSortFilterProxyModel()
            self.proxy2.setSourceModel(self.model)
    
            hBox = QHBoxLayout(self)
    
            self.tblView1 = QTableView()
            self.tblView1.setModel(self.proxy1)
    
            self.tblView2 = QTableView()
            self.tblView2.setModel(self.proxy2)
    
            hBox.addWidget(self.tblView1)
            hBox.addWidget(self.tblView2)
    
    
    def main():
        App = QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(App.exec_())
    
    
    if __name__ == "__main__":
        main()