Search code examples
qtpyqtqsqltablemodelqsortfilterproxymodelqstyleditemdelegate

Having A Working QSqlRelationalDelegate With QSortFilterProxyModel


I am using QSortFilterProxyModels all the time. However, if a QSqlRelation is setup on the source model, along with a QSqlRelationalDelegate on the view, whenever the view is switched to the proxy model, the QSqlRelationalDelegate disappears, leaving the basic QLineEdit or QSpinBox.

How can I make columns in a view work with both a QSortFilterProxyModel and QSqlRelationalDelegate, giving the expected QCombobox drop down?


Solution

  • By default, QSqlRelationalDelegate can't handle proxy models, so you have to subclass it. The below is probably far from perfect, so comments/tweaks are welcome, but has been working well on views that have a mixture of QSqlRelations/straight data, without glitches.

    class ProxyDelegate(QSqlRelationalDelegate):
        def __init__(self):
            QSqlRelationalDelegate.__init__(self)
    
        def createEditor(self, p, o, i):                                                # parent, option, index
            if i.model().sourceModel().relation(i.column()).isValid():                  # if the column has a QSqlRelation, then make the expected QComboBox
                e = QComboBox(p)
                return e      
            else:
                return QStyledItemDelegate(p).createEditor(p, o, i)
    
        def setEditorData(self, e, i):
            m = i.model()
            sM = m.sourceModel()
            relation = sM.relation(i.column()) 
            if relation.isValid():                                                              
                m = i.model()
                sM = m.sourceModel()
                relation = sM.relation(i.column())
                pModel = QSqlTableModel()                                                # pModel means populate model.  Because I've aimed for generic use, it makes a new QSqlTableModel, even if one already exists elsewhere for that SQL table
                pModel.setTable(relation.tableName())
                pModel.select()
                e.setModel(pModel)
                pModel.sort(pModel.fieldIndex(relation.displayColumn()), Qt.AscendingOrder)  # default sorting.  A custom attribute would need adding to each source model class, in order for this line to know the desired sorting order for this QComboBox delegate   
                e.setModelColumn(pModel.fieldIndex(relation.displayColumn()))            
                e.setCurrentIndex(e.findText(m.data(i).toString()))       
            else:
                return QStyledItemDelegate().setEditorData(e, i)
    
        def setModelData(self, e, m, i):
            m = i.model()                                                                # this could probably be written more elegantly so you don't need to create another SqlModel
            sM = m.sourceModel()
            relation = sM.relation(i.column())
            table = relation.tableName()
            indexColumn = relation.indexColumn()
            indexColumnId = sM.fieldIndex(indexColumn)
            displayColumn = relation.displayColumn() 
            if relation.isValid():            
                pModel = QSqlTableModel()
                pModel.setTable(relation.tableName())
                pModel.select()
                displayColumnId = pModel.fieldIndex(displayColumn)
                chosenRowInPModel = pModel.match(pModel.index(0, displayColumnId), Qt.DisplayRole, e.currentText())[0].row()            
                chosenIdInPModel = pModel.data(pModel.index(chosenRowInPModel, indexColumnId)).toString()
                m.setData(i, chosenIdInPModel)
                self.closeEditor.emit(e, QAbstractItemDelegate.NoHint)
            else:
                QStyledItemDelegate().setModelData(e, m, i)