Sorting & filtering Qt Model Views

Using QSortFilterProxyModel to filter and sort data in PyQt5
Heads up! You've already completed this tutorial.

Qt provides a built-in mechanism to sort and filter model data using QSortFilterProxyModel. This proxy model sits between your source model and the view, allowing you to reorder and filter rows without modifying the underlying data. In this tutorial, you'll learn how to use QSortFilterProxyModel to add sorting and searching to a QTableView in PyQt5.

Sorting a QTableView with QSortFilterProxyModel

To sort data in a QTableView, you create a QSortFilterProxyModel, set your source model on it, and then call .sort() with the column index and sort order. The proxy model handles reordering the rows for display while leaving your original data untouched.

python
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QSortFilterProxyModel


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            # See below for the nested-list data structure.
            # .row() indexes into the outer list,
            # .column() indexes into the sub-list
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        # The length of the outer list.
        return len(self._data)

    def columnCount(self, index):
        # The following takes the first sub-list, and returns
        # the length (only works if all rows are an equal length)
        return len(self._data[0])


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()

        data = [
            [4, 9, 2],
            [1, 0, 0],
            [3, 5, 0],
            [3, 3, 2],
            [7, 8, 9],
        ]

        self.model = TableModel(data)
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setSourceModel(self.model)

        self.proxy_model.sort(0, Qt.AscendingOrder)

        self.table.setModel(self.proxy_model)

        self.setCentralWidget(self.table)


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Searching and Filtering with QSortFilterProxyModel

As the name suggests, QSortFilterProxyModel can also be used to filter and search through model data. With the proxy model in place, you can enable searching by setting a filter key column and then passing in a string to filter rows.

For example, to search for a specific, fixed string across all columns, set the key column to -1 and pass the search string to .setFilterFixedString().

python
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setFilterFixedString('<string to search>')

The proxy model supports a number of different filter types through its public slot methods — including a basic string search, regular expressions and wildcard searches.

Filter function (slot) Parameters
setFilterFixedString str simple string to search
setFilterRegularExpression str or QRegularExpression regular expression
setFilterWildcard str wildcard expression

These public slots allow you to connect other signals to the proxy filter model and have the table filtered automatically each time the signal fires. A common use case is to connect a QLineEdit.textChanged signal to setFilterFixedString — this lets users search and filter the table in real time by typing in a text field.

Complete Example: Filtering a QTableView with a Search Bar

Below is a complete working example using a fixed string search against sample table data stored in a Python list of lists. The QLineEdit search bar is connected directly to the proxy model's filter slot, so typing in the box immediately filters the QTableView.

python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QMainWindow, QVBoxLayout, QLineEdit
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            # See below for the nested-list data structure.
            # .row() indexes into the outer list,
            # .column() indexes into the sub-list
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        # The length of the outer list.
        return len(self._data)

    def columnCount(self, index):
        # The following takes the first sub-list, and returns
        # the length (only works if all rows are an equal length)
        return len(self._data[0])


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = [
            [4, 9, 2],
            [1, "hello", 0],
            [3, 5, 0],
            [3, 3, "what"],
            ["this", 8, 9],
        ]

        self.model = TableModel(data)
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
        self.proxy_model.setSourceModel(self.model)

        self.proxy_model.sort(0, Qt.AscendingOrder)

        self.table.setModel(self.proxy_model)

        self.searchbar = QLineEdit()

        # You can choose the type of search by connecting to a different slot here.
        # see https://doc.qt.io/qt-5/qsortfilterproxymodel.html#public-slots
        self.searchbar.textChanged.connect(self.proxy_model.setFilterFixedString)

        layout = QVBoxLayout()

        layout.addWidget(self.searchbar)
        layout.addWidget(self.table)


        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)



app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

If you run this and start typing in the box, you'll see the table view being filtered automatically.

It doesn't matter what data structure you are using. As long as your model supports the standard interface the QSortFilterProxyModel will correctly search and filter it into the view.

Using Wildcard Filters

You can also replace the fixed string search with a wildcard search slot for more flexible pattern matching.

python
self.searchbar.textChanged.connect(self.proxy_model.setFilterWildcard)

This allows users to use * and ? wildcard characters in their search queries, making it easy to match partial patterns across your table data.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Packaging Python Applications with PyInstaller by Martin Fitzpatrick

This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Martin Fitzpatrick

Sorting & filtering Qt Model Views was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.