Ошибка с многоколоночной фильтрацией в QTableView, загруженном из кадра данных pandas - PullRequest
1 голос
/ 20 марта 2019

Я пытаюсь отфильтровать несколько столбцов с 1 блоком QLineEdit в QTableView.Я использую QAbstractTableModel для выполнения фильтрации, и я подкласс QSortFilterProxyModel для обработки нескольких столбцов.Данные извлекаются из файла xlsx и загружаются в фрейм данных, используя pandas.

Так что мой код работает, пока заголовки в файле excel являются числовыми (0, 1, 2..etc).В функции панд to_excel есть флаг, запрещающий пандам добавлять свои собственные метки 'header' и index 'header = none и index = none соответственно.Поэтому, когда я устанавливаю заголовки в none, я получаю эту ошибку при заполнении QTableView

TypeError: 'PySide2.QtCore.QRegExp.indexIn' called with wrong argument types:
  PySide2.QtCore.QRegExp.indexIn(int)
Supported signatures:
  PySide2.QtCore.QRegExp.indexIn(str, int = 0, PySide2.QtCore.QRegExp.CaretMode = PySide2.QtCore.QRegExp.CaretMode.CaretAtZero)

Вот моя пользовательская функция 'filterAcceptsRow':

def filterAcceptsRow(self, sourceRow, sourceParent):
    index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
    index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
    index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
    # The following line causes the error
    return ((self.filterRegExp().indexIn(self.sourceModel().data(index0, self.role)) >= 0
             or self.filterRegExp().indexIn(self.sourceModel().data(index1, self.role)) >= 0
            or self.filterRegExp().indexIn(self.sourceModel().data(index2, self.role)) >= 0))

Вот рабочий пример моего кода

from pandas import DataFrame, ExcelFile, Series
from PySide2.QtWidgets import QApplication, QTabWidget, QVBoxLayout, QAbstractItemView, QSizePolicy, QAbstractScrollArea, QHeaderView, QTableView, QMainWindow, QGridLayout, QLineEdit, QWidget
from PySide2.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, QModelIndex, QRegExp, QDate, QDateTime


class PandasModel(QAbstractTableModel):

    def __init__(self, data, parent=None):
        QAbstractTableModel.__init__(self, parent)
        self._filters = {}
        self._sortBy = []
        self._sortDirection = []
        self._dfSource = data
        self._dfDisplay = data
        self._dfDisplay = self._dfDisplay.fillna("")

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[0]

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[1]

    def data(self, index, role):
        if index.isValid() and role == Qt.DisplayRole:
            return self._dfDisplay.values[index.row()][index.column()]
        return None

    def headerData(self, col, orientation=Qt.Horizontal, role=Qt.DisplayRole):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return str(self._dfDisplay.columns[col])
        return None

    def setupModel(self, header, data):
        self._dfSource = DataFrame(data, columns=header)
        self._sortBy = []
        self._sortDirection = []
        self.setFilters({})

    def setFilters(self, filters):
        self.modelAboutToBeReset.emit()
        self._filters = filters
        print(filters)
        self.updateDisplay()
        self.modelReset.emit()

    def sort(self, col, order=Qt.AscendingOrder):
        # Storing persistent indexes
        self.layoutAboutToBeChanged.emit()
        oldIndexList = self.persistentIndexList()
        oldIds = self._dfDisplay.index.copy()
        # Sorting data
        column = self._dfDisplay.columns[col]
        ascending = (order == Qt.AscendingOrder)
        if column in self._sortBy:
            i = self._sortBy.index(column)
            self._sortBy.pop(i)
            self._sortDirection.pop(i)
        self._sortBy.insert(0, column)
        self._sortDirection.insert(0, ascending)
        self.updateDisplay()
        # Updating persistent indexes
        newIds = self._dfDisplay.index
        newIndexList = []
        for index in oldIndexList:
            id = oldIds[index.row()]
            newRow = newIds.get_loc(id)
            newIndexList.append(self.index(newRow, index.column(), index.parent()))
        self.changePersistentIndexList(oldIndexList, newIndexList)
        self.layoutChanged.emit()
        self.dataChanged.emit(QModelIndex(), QModelIndex())

    def updateDisplay(self):
        dfDisplay = self._dfSource.copy()
        # Filtering
        cond = Series(True, index=dfDisplay.index)
        for column, value in self._filters.items():
            cond = cond & \
                   (dfDisplay[column].str.lower().str.find(str(value).lower()) >= 0)
        dfDisplay = dfDisplay[cond]
        # Sorting
        if len(self._sortBy) != 0:
            dfDisplay.sort_values(by=self._sortBy, ascending=self._sortDirection, inplace=True)
        # Updating
        self._dfDisplay = dfDisplay

class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, data, parent=None):
        super(SortFilterProxyModel, self).__init__(parent)
        self.role = Qt.DisplayRole
        self.minDate = QDate()
        self.maxDate = QDate()
        self.__data = data

    def setFilterMinimumDate(self, date):
        self.minDate = date
        self.invalidateFilter()

    def filterMinimumDate(self):
        return self.minDate

    def setFilterMaximumDate(self, date):
        self.maxDate = date
        self.invalidateFilter()

    def filterMaximumDate(self):
        return self.maxDate

    def filterAcceptsRow(self, sourceRow, sourceParent):
        index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
        index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
        index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
        return ((self.filterRegExp().indexIn(self.sourceModel().data(index0, self.role)) >= 0 or self.filterRegExp().indexIn(self.sourceModel().data(index1, self.role)) >= 0 or self.filterRegExp().indexIn(self.sourceModel().data(index2, self.role)) >= 0))

    def lessThan(self, left, right):
        leftData = self.sourceModel().data(left, self.role)
        rightData = self.sourceModel().data(right, self.role)

        if not isinstance(leftData, QDate):
            emailPattern = QRegExp("([\\w\\.]*@[\\w\\.]*)")

            if left.column() == 1 and emailPattern.indexIn(leftData) != -1:
                leftData = emailPattern.cap(1)

            if right.column() == 1 and emailPattern.indexIn(rightData) != -1:
                rightData = emailPattern.cap(1)

        return leftData < rightData

    def dateInRange(self, date):
        if isinstance(date, QDateTime):
            date = date.date()

        return ((not self.minDate.isValid() or date >= self.minDate)
                and (not self.maxDate.isValid() or date <= self.maxDate))

class PerfSearchTest(QMainWindow):
    def __init__(self):
        super().__init__()
        self.path_to_local_spreadsheet = "output.xlsx"
        self.spreadsheet_display = {}
        self.searches = {}
        self.current_tab_layout = {}
        self.tab_widget = {}
        self.central_widget = QWidget()
        self.tabs = QTabWidget()
        self.layout_grid = QGridLayout(self)
        self.layout_grid.setSpacing(10)
        self.populate_spreadsheet_table()
        self.layout_grid.addWidget(self.tabs, 0, 0)
        self.central_widget.setLayout(self.layout_grid)
        self.setCentralWidget(self.central_widget)

    def onTextChanged(self, text):
        self.proxyModelContact.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString))

    def populate_spreadsheet_table(self):
        opened_excel_flie_dataframe = ExcelFile(self.path_to_local_spreadsheet)
        opened_excel_file_dataframe_worksheets = []
        for y, sheet in enumerate(opened_excel_flie_dataframe.sheet_names):
            df = opened_excel_flie_dataframe.parse(y)
            self.tab_widget[y] = QWidget()
            self.searches[y] = QLineEdit(sheet)
            self.spreadsheet_display[y] = QTableView()
            self.spreadsheet_display[y].setSortingEnabled(True)
            self.spreadsheet_display[y].setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.spreadsheet_display[y].setAlternatingRowColors(True)
            self.spreadsheet_display[y].horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.spreadsheet_display[y].verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
            self.spreadsheet_display[y].setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
            self.spreadsheet_display[y].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
            self.spreadsheet_display[y].resizeColumnsToContents()
            self.current_tab_layout[y] = QVBoxLayout()
            self.current_tab_layout[y].addWidget(self.searches[y])
            self.current_tab_layout[y].addWidget(self.spreadsheet_display[y])
            self.proxyModelContact = SortFilterProxyModel(df)
            self.proxyModelContact.setSourceModel(PandasModel(df))
            self.searches[y].textChanged.connect(self.onTextChanged)
            self.spreadsheet_display[y].setModel(self.proxyModelContact)
            self.tab_widget[y].setLayout(self.current_tab_layout[y])
            opened_excel_file_dataframe_worksheets.append(df)
            self.tabs.addTab(self.tab_widget[y], sheet)


if __name__ == "__main__":
    app = QApplication()
    w = PerfSearchTest()
    w.show()
    app.exec_()

Вот «сломанная» электронная таблица, которая будет повторять ошибку

Вот «рабочая» электронная таблица, которая не будет воспроизводить ошибку (или она должна't)

Я был бы рад передать весь исходный код на случай, если это будет что-то еще, если потребуется.Я не включил материал, который полагается на загрузку листов в формате pygsheets - ввод в pandas dataframe - затем экспорт в xslx.

1 Ответ

1 голос
/ 20 марта 2019

Проблема вызвана тем, что indexIng ожидает строку и, как вы указываете, вы передаете целое число, поэтому возможное решение заключается в преобразовании его в строку:

def filterAcceptsRow(self, sourceRow, sourceParent):
    index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
    index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
    index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
    value = self.sourceModel().data(index0, self.role) 
    for ix in (index0, index1, index2):
        value = self.sourceModel().data(ix, self.role)
        if self.filterRegExp().indexIn(str(value)) >= 0:
            return True
    return False
...