Reputation: 579
How can I use pyqt5's model-view concept to allow a user access to a 'hidden' column in a view?
I often subclass QAbstractTableModel to show a Pandas DataFrame in PyQt5. (h/t to posts on this site). See a MRE below. In using that model (but I am sure the principle/answer will apply to other models too), I am wondering if there is a Model-View concept I am missing that can help me.
For cleanliness of the display for the majority of users/use cases, I have removed a column from the model. In the MRE I do that implicitly by not including column 'd'. I include only the 'normalized_columns', because in most use cases the data relegated to column d is worthless/unpredictable/ugly/not-normalized/etc. But what do I do in the rare case a user wants to 'drill down' into that column d data?
Is the solution to pass ALL the data to QAbstractTableModel, maybe storing an attribute self._all_data_df ?, but then set self._df to the 'normalized only' data, and then, if the user right clicks and asks to 'show me column d for this row', (I could add support for a context menu), THEN check which row he's clicked on in self._df, associate that row to the corresponding row in self._all_data_df, and then serve it to him? But that intuitively feels like an implementation that doesn't get the benefit Model/View.
I sense there is a more general model-view best practice question here around either how to show just a portion of the data while keeping all the data available in memory, or how to keep a reference to 'the real/full data' while only showing the user a user-friendly version, etc, but I am still too much a novice to know the obvious answer.
import builtins
import pandas as pd
from PyQt5.QtWidgets import QApplication, QTableView
from PyQt5.QtCore import QAbstractTableModel, Qt
class DfModelLight(QAbstractTableModel):
def __init__(self, data=pd.DataFrame()):
QAbstractTableModel.__init__(self)
self._df = data
def rowCount(self, parent=None):
return self._df.shape[0]
def columnCount(self, parent=None):
return self._df.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
val = self._df.iloc[index.row()][index.column()]
if role == Qt.DisplayRole:
return str(val)
return None
def headerData(
self,
section: int,
orientation: Qt.Orientation,
role: int = Qt.DisplayRole
):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
col_object = self._df.columns[section]
if isinstance(col_object, builtins.str):
return col_object
elif isinstance(col_object, builtins.tuple):
return ' '.join(col_object)
else:
raise ValueError
else:
return str(self._df.index[section])
return None
def set_df(self, df):
self.beginResetModel()
self._df = df.copy()
self.endResetModel()
def get_df(self):
return self._df
if __name__ == '__main__':
app = QApplication(sys.argv)
data = {
'a': ['Mary', 'Jim', 'John'],
'b': [100, 200, 300],
'c': ['a', 'b', 'c'],
'd':[{'unwieldy_data':'randomness','unknown_data':None,'uncontrolled_key':[0,1,0,1]},
{'unwieldy_data':'more_randomness','other_data':3.14,'mykey':[]},
{'unwieldy_data':'even_more_randomness','foreign_data':9999999,'id':[0,1,2,3]}
]
}
normalized_columns = ['a', 'b', 'c']
df = pd.DataFrame(data)
df = df[normalized_columns]
model = DfModelLight(df)
view = QTableView()
view.setModel(model)
view.resize(800, 600)
view.show()
sys.exit(app.exec_())
Upvotes: 0
Views: 29