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:
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:
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.
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.