Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to access source model methods from Proxy model



  • Hello,

    I'm using Pyside2 with python 3.8.

    I'm trying to figure out how to set up a proxy model to my qtableview. At a first glance, I'm just trying to set up the proxy model without overriding the filterAcceptsRow() method. Just to get a first understanding of how the sortfilterproxymodel works.

    Here's my Proxy model class:

    class ProxyModel(QtCore.QSortFilterProxyModel):
    	def __init__(self,parent=None):
    		super(ProxyModel, self).__init__()
    		self.filter = (0,0)
    
    	def mapFromSource(self, index):
    		return self.createIndex(index.row(), index.column())
    
    	def mapToSource(self, index):
    		return self.sourceModel().index(index.row(), index.column(), QtCore.QModelIndex())
    
    	def Filtre(self, column, valeur):
    		self.filter = (column, valeur)
    

    in the MainWindow class, I have did the folliwing lines of code:

    ProxyModel = ProxyModel()
    Model = TableModel() 
    
    ProxyModel.setSourceModel(Model)
    self.MyTableView.setModel(ProxyModel)
    

    I have several methods in my TableModel() class, which subclass from QAbstractTableModel. Let's say now I want to set up my headers, Before Implementing the proxymodel, the following line would work:

    self.MyTableView.model().set_headers(self.headers)
    

    Where set_headers() is my method and self.headers is a list of column's headers.

    After Implementing the ProxyModel() class, This doesn't work: it says that ProxyModel object has no attribute set_headers. Which makes sense because I think now, the algorithm goes in PorxyModel and search for a method called set_headers. I thought that since It's a proxy model, It kind of works automatically that the code would go to the source model looking for the method.

    Definitely, I'm doing it wrong, and all my other source model methods that I'm calling are not going to work. Unless I fix it.

    setting up Proxy models is all new to me, and I'm still missing on several basics even though after reading tons of documentation.

    Can any one give me any more insight on this please ?

    Here's a glimpse of my TableModel class, if ever you need it:

    class TableModel(QtCore.QAbstractTableModel):
    
    	def __init__(self, mlist=None):
    		super(TableModel, self).__init__()
    		self._items = [] if mlist == None else mlist
    		self._header = []
    
    	def rowCount(self, parent = QtCore.QModelIndex):
    		return len(self._items)
    
    	def columnCount(self, parent = QtCore.QModelIndex):
    		return len(self._header)
    
    	def data(self, index, role = QtCore.Qt.DisplayRole):
    		if not index.isValid():
    		   return None
    		if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
    			return self._items[index.row()][index.column()]
    		return None
    	
    	def setData(self, index, value, role = QtCore.Qt.EditRole):
    		if value is not None and role == QtCore.Qt.EditRole:
    			self._items[index.row()-1][index.column()] = value
    			self.dataChanged.emit(index, index)
    			return True
    		return False
    
    	def addRow(self, rowObject):
    		row = self.rowCount()
    		self.beginInsertRows(QtCore.QModelIndex(), row, row)
    		self._items.append(rowObject)
    		self.endInsertRows()
    		self.layoutChanged.emit()
    

    Thanks,



  • @hachbani
    All I think you are missing is that QAbstractProxyModel has a method QAbstractItemModel *QAbstractProxyModel::sourceModel() const. That matches with the ProxyModel.setSourceModel(Model) which you do.

    So to implement your self.MyTableView.model().set_headers(self.headers) you will now want:

    self.MyTableView.model().sourceModel().set_headers(self.headers)
    

    For more advanced scenarios you could use Python's isinstance(self.MyTableView.model(), ProxyModel) if you need to check whether the view's model is a proxy model rather than a direct model.

    Yes, you will have to change other existing code which currently assumes that self.MyTableView.model() == Model because that is no longer the case. Write yourself a method in MyTableView like:

    def tableModel() -> TableModel:
        return self.MyTableView.model().sourceModel()
    

    and use that instead of your self.MyTableView.model(). Or, you could make your Model = TableModel() a member variable and use that directly, depending on your programming preferences.



  • Thanks for the reply, so for everytime I'm calling a sourceModel, I have to call it from .sourceModel().method, I don't need to reimplement those methods into the proxy model right ?



  • @hachbani @hachbani
    I had just augmented my previous post with a suggested convenience method (or member variable) for this.

    You do not need to implement QAbstractItemModel methods on the QAbstractProxyModel wrapper, although you could do. I tend not to, it gets to be a potentially large number of methods/signals/slots etc. I think it's cleaner to go through the suggested method which returns the source model from the proxy model and continue to use that as you did before.



  • This post is deleted!


  • Hi, Thanks a lot, it worked ! I did as you sad and called the methods using model().sourceModel().

    I'm trying to reimplement my FilterAcceptRows(). here's what I did.

    class ProxyModel(QtCore.QSortFilterProxyModel):
    	def __init__(self,parent=None):
    		super(ProxyModel, self).__init__()
    		self.filter = (3,20)
    
    	def filterAcceptsRow(self, sourceRow, sourceParent):
    		print('im here')
    		sourceModel = self.sourceModel()
    		id = sourceModel.index(sourceRow, self.filter[0], sourceParent)
    
    		if sourceModel.data(id) == self.filter[1]: 
    			return True
    		return False
    

    What I'm trying to do is filter on the 4th column, the rows where the cells(row, 4) is equal to 20.
    The output is a bit weird, it does filter but I don't know what it does filter on. Reading my code, how do you think this is working ?



  • @hachbani said in How to access source model methods from Proxy model:

    The output is a bit weird,

    I don't know what this means. Your code looks alright to me, you are picking out column #3 in the source row and seeing if that (Qt.DisplayRole) equals 20. Assuming those numbers are correct, it should work, are you saying it does not?



  • @JonB , Here's my Table without the filterAcceptsRow() reimplementation

    before.PNG

    My Table after filtering on 13 as a value in the 4th column (Num It)

    after.PNG

    I don't understand.. It's like all what it did is keeping the first 6 lines



  • @hachbani
    This is because of your (erroneous) overrides of def mapFromSource/mapToSource back in your ProxyModel.

    Those methods need to map between the different row numbers in your proxy versus source model. Because you are using a QSortFilterProxyModel the row numbers will not match: when filtering the proxy's fist row might be the source's fifth row, and if you add sorting they can all come in a different order anyway.

    QSortFilterProxyModel knows how to do all of this mapping for you, but you have undone all its work in your definitions, which are actually like a QIdentityProxyModel. Hence it is showing the right number of rows (presumably) but the wrong ones --- there are 6 accepted rows, so it picks numbers 0--5 from the source model!

    Get rid of those two methods. You never needed to override them in a QSortFilterProxyModel.



  • @JonB
    Man, thanks again really, the newbie I'm on this sort of things, you're saving me. I've looking all over the documentations for the past 3 days to do this. Thanks. It works.


Log in to reply