10mjg
10mjg

Reputation: 579

PyQt5 - Model/View - allow user to request undisplayed data

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

Answers (0)

Related Questions