Fernando Ortega
Fernando Ortega

Reputation: 130

QListView filter by data attribute (not text)

I'd like to have views that display different subsets of the same data based on internal attributes of that data, having the views share the same model of course.

Here's an example with screenshots:

1 - A table view that shows a list of items with "name" and "status"

2 - A list view that shows items filtered by "status", but without showing the status (ideally I'd like this list view to have the status color next to the name, but that can be a different question).

My goal is to create 2 ways to view the data: as a global (unfiltered) table, and also as a sort of kanban board with a list for each "status" (or any other attribute).

Table view showing all

enter image description here

List view showing filtered data

enter image description here

Here's the code of my model and how I'm constructing the data.

I'm comfortable with Qt, but I'm new to its Model-View classes, so if there are any suggestions on how to do this better they are more than welcome!


import sys
from PySide2.QtWidgets import *
from PySide2 import QtCore, QtGui
from PySide2.QtCore import Qt


class TestModel(QtCore.QAbstractTableModel):
    def __init__(self, items=None, parent=None):
        super(TestModel, self).__init__(parent)
        self.__items = items or []

    def headerData(self, section, orientation, role):
        headers = ('Name', 'Status')
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
                return headers[section]

    def columnCount(self, parent):
        return 2

    def rowCount(self, parent):
        return len(self.__items)

    def data(self, index, role):
        column = index.column()
        item = self.__items[index.row()]
        # Name
        if column == 0:
            if role == Qt.DisplayRole:
                return item.name
            if role == Qt.EditRole:
                return item.name
        # Status
        elif column == 1:
            if role == Qt.DisplayRole:
                return item.status
            if role == Qt.EditRole:
                return item.status
            if role == Qt.DecorationRole:
                pixmap = QtGui.QPixmap(26, 26)
                pixmap.fill(item.status_color)
                icon = QtGui.QIcon(pixmap)
                return icon

        if role == Qt.ToolTipRole:
            return item.__repr__()


class Item(object):
    def __init__(self, name='', status=None):
        self._valid_statuses = ('active', 'hold', 'closed', 'done')
        self._color_map = {self._valid_statuses[0]: '#56FF51',  # valid
                           self._valid_statuses[1]: '#B8E5EC', # hold
                           self._valid_statuses[2]: '#F42525',
                           self._valid_statuses[3]: '#E7F64D'
                           } # closed
        self.name = name
        self._status = status or self._valid_statuses[0]

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, status):
        if status not in self._valid_statuses:
            status = self._valid_statuses[0]
        self._status = status

    @property
    def status_color(self):
        # return black if invalid
        return self._color_map.get(self._status, '#000000')


app = QApplication([])

data = [
    Item(name='one', status='active'),
    Item(name='two', status='active'),
    Item(name='three', status='hold'),
    Item(name='four', status='closed'),
    Item(name='five', status='done'),
]

model = TestModel(data)

# Table
table_view = QTableView()
table_view.show()
table_view.setModel(model)

# List with the wanted filters i.e.: by 'approved' status
list_view = QListView()
list_view.show()
list_view.setModel(model)

sys.exit(app.exec_())

Upvotes: 1

Views: 525

Answers (1)

eyllanesc
eyllanesc

Reputation: 243965

In this case the solution is to use QSortFilterProxyModel and make a custom filter:

class StatusFilterModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(StatusFilterModel, self).__init__(parent)
        self._status = None

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, status):
        self._status = status
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        if self.status is None:
            return True
        source_index = self.sourceModel().index(source_row, 1, source_parent)
        status = source_index.data()
        return self.status == status
list_view = QListView()
list_view.show()
proxy_model = StatusFilterModel()
proxy_model.setSourceModel(model)
proxy_model.status = "active"
list_view.setModel(proxy_model)

Upvotes: 2

Related Questions