How to debug: QAbstractTableModel subclass does not call flags()

Understanding why flags() isn't called when using QDataWidgetMapper with standard widgets
Heads up! You've already completed this tutorial.

If you've subclassed QAbstractTableModel and noticed that your data() and setData() methods are being called correctly, but flags() never seems to fire, you're not alone. This is a common source of confusion, especially when you're using QDataWidgetMapper to connect your model to standard form widgets like QLineEdit.

Let's walk through why this happens and what you can do about it.

How flags() works in Qt's Model/View architecture

In Qt's model/view framework, the flags() method on a model tells the view what a user is allowed to do with each item — whether it's selectable, editable, checkable, drag-enabled, and so on. Here's a typical implementation:

python
from PyQt5.QtCore import Qt

def flags(self, index):
    return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

When you use a model/view widget like QTableView, the view queries flags() for every visible cell. It uses the returned flags to decide things like: should the cell be grayed out? Should double-clicking open an editor? Should a checkbox appear? That's why, with a QTableView, you'll see flags() called constantly — even as you move the mouse around.

Why flags() isn't called with QDataWidgetMapper

When you use QDataWidgetMapper to map model columns to standard widgets like QLineEdit, QComboBox, or QSpinBox, the situation is different. These standard widgets don't know anything about the model/view flags system. A QLineEdit has its own setReadOnly() method and its own properties — it doesn't ask a model whether it should be editable.

The QDataWidgetMapper acts as a bridge between your model and these form widgets. It reads data from the model (calling data()) and writes data back (calling setData()), but it doesn't query flags() and apply them to the widgets. The mapper simply doesn't have a built-in mechanism to translate model flags into widget properties.

So if your model's flags() method is never called, and you're using QDataWidgetMapper with standard widgets, that's expected behavior. There's nothing wrong with your model or your initialization.

Applying flags-like behavior to standard widgets

If you were hoping to use flags() to control widget behavior — for example, making a QLineEdit read-only based on some model state — you'll need to handle that manually. Here's a practical approach:

python
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QLineEdit,
    QPushButton, QLabel, QFormLayout
)
from PyQt5.QtCore import (
    QAbstractTableModel, Qt, QModelIndex
)
from PyQt5.QtWidgets import QDataWidgetMapper
import sys


class MyTableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        # Track which columns are editable
        self._editable_columns = {0, 1}  # columns 0 and 1 are editable

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def columnCount(self, parent=QModelIndex()):
        if self._data:
            return len(self._data[0])
        return 0

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        if role in (Qt.DisplayRole, Qt.EditRole):
            return self._data[index.row()][index.column()]
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if not index.isValid() or role != Qt.EditRole:
            return False
        self._data[index.row()][index.column()] = value
        self.dataChanged.emit(index, index, [role])
        return True

    def flags(self, index):
        base_flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled
        if index.column() in self._editable_columns:
            base_flags |= Qt.ItemIsEditable
        return base_flags

    def is_column_editable(self, column):
        """Helper method to check if a column is editable."""
        return column in self._editable_columns


class MyForm(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QDataWidgetMapper with Manual Flags")

        # Sample data: [name, email, id]
        data = [
            ["Alice", "alice@example.com", "1001"],
            ["Bob", "bob@example.com", "1002"],
            ["Charlie", "charlie@example.com", "1003"],
        ]

        self.model = MyTableModel(data)

        # Create form widgets
        self.name_edit = QLineEdit()
        self.email_edit = QLineEdit()
        self.id_edit = QLineEdit()

        # Set up the mapper
        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(self.model)
        self.mapper.addMapping(self.name_edit, 0)    # column 0: name
        self.mapper.addMapping(self.email_edit, 1)    # column 1: email
        self.mapper.addMapping(self.id_edit, 2)       # column 2: id

        # Apply flags-like behavior to widgets manually
        self.apply_widget_flags()

        # Navigate to the first row
        self.mapper.toFirst()

        # Navigation buttons
        prev_button = QPushButton("Previous")
        next_button = QPushButton("Next")
        prev_button.clicked.connect(self.mapper.toPrevious)
        next_button.clicked.connect(self.mapper.toNext)

        # Layout
        form_layout = QFormLayout()
        form_layout.addRow("Name:", self.name_edit)
        form_layout.addRow("Email:", self.email_edit)
        form_layout.addRow("ID:", self.id_edit)

        layout = QVBoxLayout()
        layout.addLayout(form_layout)
        layout.addWidget(QLabel("(ID field is read-only, based on model flags)"))
        layout.addWidget(prev_button)
        layout.addWidget(next_button)
        self.setLayout(layout)

    def apply_widget_flags(self):
        """
        Manually apply model flags to mapped widgets.
        The QDataWidgetMapper doesn't do this for us, so we
        check the model and configure each widget accordingly.
        """
        widget_column_map = {
            self.name_edit: 0,
            self.email_edit: 1,
            self.id_edit: 2,
        }

        for widget, column in widget_column_map.items():
            if isinstance(widget, QLineEdit):
                editable = self.model.is_column_editable(column)
                widget.setReadOnly(not editable)
                if not editable:
                    # Visual cue that the field isn't editable
                    widget.setStyleSheet(
                        "QLineEdit { background-color: #f0f0f0; }"
                    )


app = QApplication(sys.argv)
window = MyForm()
window.show()
sys.exit(app.exec_())

In this example, column 2 (the ID field) is not in the _editable_columns set, so flags() would return flags without Qt.ItemIsEditable for that column. The apply_widget_flags() method reads this information from the model and sets the QLineEdit to read-only, giving it a gray background as a visual hint.

Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.

Find out More

This manual step bridges the gap between the model's flags() and the form widgets.

Recap

When you use QDataWidgetMapper with standard widgets like QLineEdit, the mapper calls data() and setData() on your model but does not call flags(). This is because standard widgets have their own properties for controlling editability, visibility, and so on — they don't participate in Qt's model/view flags system.

If you need flags()-like behavior on your form widgets, you can add a small helper method that reads from your model and configures each widget appropriately. This way, your model remains the single source of truth for what's editable, and your form stays in sync.

If you want to see flags() in action, try displaying your model in a QTableView with numpy and pandas or explore editing data in a QTableView — in those contexts, the view actively queries flags() for every visible cell.

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.

Get the book

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

How to debug: QAbstractTableModel subclass does not call flags() 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.