super(AssetDelegate, self).__init__(parent)
The initializer is typical of most delegate subclasses, simply calling the
base class.
def paint(self, painter, option, index):
myoption = QStyleOptionViewItem(option)
if index.column() == ROOM:
myoption.displayAlignment |= Qt.AlignRight|Qt.AlignVCenter
QSqlRelationalDelegate.paint(self, painter, myoption, index)
We have reimplemented the paint() method only to right-align the room
numbers. We do this by changing the QStyleOptionViewItem, and we leave the
painting itself to be done by the base class.
def createEditor(self, parent, option, index):
if index.column() == ROOM:
editor = QLineEdit(parent)
regex = QRegExp(r"(?:0[1-9]|1[0124-9]|2[0-7])"
r"(?:0[1-9]|[1-5][0-9]|6[012])")
validator = QRegExpValidator(regex, parent)
editor.setValidator(validator)
editor.setInputMask("9999")
editor.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
return editor
else:
return QSqlRelationalDelegate.createEditor(self, parent,
option, index)
The heart of the createEditor() method is the code that sets up the QLineEdit
for entering room numbers. Room numbers are four digits long, made up of
a floor number, in the range 01–27 (but excluding 13), and a room number on
the floor in the range 01–62. For example, 0231 is floor 2, room 31, but 0364 is
invalid. The regular expression is sufficient for specifying valid room numbers,
but it cannot set a minimum number of digits, since one, two, or three digits
may be a valid prefix for a valid four digit room number. We have solved this
by using an input mask that requires exactly four digits to be entered. For the
other fields, we pass the work on to the base class.
def setEditorData(self, editor, index):
if index.column() == ROOM:
text = index.model().data(index, Qt.DisplayRole).toString()
editor.setText(text)
else:
QSqlRelationalDelegate.setEditorData(self, editor, index)462
Chapter 15. Databases
Once the editor has been created, the view will call setEditorData() so that it
can be populated with the current value. In this case, we care only about the
room column, passing on the work for the other fields to the base class.
def setModelData(self, editor, model, index):
if index.column() == ROOM:
model.setData(index, QVariant(editor.text()))
else:
QSqlRelationalDelegate.setModelData(self, editor, model,
index)
We have taken a similar approach to the previous method, handling the room
field and leaving the others to be handled by the base class. As a matter of
fact, we could have omitted reimplementing this method, and PyQt would have
been smart enough to retrieve the value from our QLineEdit. However, it is a
better practice to take full responsibility for our own customizations.
We have now finished the detour and can return to the MainForm.__init__()
method, beginning with the bottom table that shows the log records that are
applicable to the current asset.
self.logModel = QSqlRelationalTableModel(self)
self.logModel.setTable("logs")
self.logModel.setRelation(ACTIONID,
QSqlRelation("actions", "id", "name"))
self.logModel.setSort(DATE, Qt.AscendingOrder)
self.logModel.setHeaderData(DATE, Qt.Horizontal,
QVariant("Date"))
self.logModel.setHeaderData(ACTIONID, Qt.Horizontal,
QVariant("Action"))
self.logModel.select()
The code for creating the log model is almost the same as the code we used for
the asset model. We use a QSqlRelationalTableModel because we have a foreign
key field, and we provide our own column titles.
self.logView.setModel(self.logModel)
self.logView.setItemDelegate(LogDelegate(self))
self.logView.setSelectionMode(QTableView.SingleSelection)
self.logView.setSelectionBehavior(QTableView.SelectRows)
self.logView.setColumnHidden(ID, True)
self.logView.setColumnHidden(ASSETID, True)
self.logView.resizeColumnsToContents()
self.logView.horizontalHeader().setStretchLastSection(True)
The initializer is typical of most delegate subclasses, simply calling the
base class.
def paint(self, painter, option, index):
myoption = QStyleOptionViewItem(option)
if index.column() == ROOM:
myoption.displayAlignment |= Qt.AlignRight|Qt.AlignVCenter
QSqlRelationalDelegate.paint(self, painter, myoption, index)
We have reimplemented the paint() method only to right-align the room
numbers. We do this by changing the QStyleOptionViewItem, and we leave the
painting itself to be done by the base class.
def createEditor(self, parent, option, index):
if index.column() == ROOM:
editor = QLineEdit(parent)
regex = QRegExp(r"(?:0[1-9]|1[0124-9]|2[0-7])"
r"(?:0[1-9]|[1-5][0-9]|6[012])")
validator = QRegExpValidator(regex, parent)
editor.setValidator(validator)
editor.setInputMask("9999")
editor.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
return editor
else:
return QSqlRelationalDelegate.createEditor(self, parent,
option, index)
The heart of the createEditor() method is the code that sets up the QLineEdit
for entering room numbers. Room numbers are four digits long, made up of
a floor number, in the range 01–27 (but excluding 13), and a room number on
the floor in the range 01–62. For example, 0231 is floor 2, room 31, but 0364 is
invalid. The regular expression is sufficient for specifying valid room numbers,
but it cannot set a minimum number of digits, since one, two, or three digits
may be a valid prefix for a valid four digit room number. We have solved this
by using an input mask that requires exactly four digits to be entered. For the
other fields, we pass the work on to the base class.
def setEditorData(self, editor, index):
if index.column() == ROOM:
text = index.model().data(index, Qt.DisplayRole).toString()
editor.setText(text)
else:
QSqlRelationalDelegate.setEditorData(self, editor, index)462
Chapter 15. Databases
Once the editor has been created, the view will call setEditorData() so that it
can be populated with the current value. In this case, we care only about the
room column, passing on the work for the other fields to the base class.
def setModelData(self, editor, model, index):
if index.column() == ROOM:
model.setData(index, QVariant(editor.text()))
else:
QSqlRelationalDelegate.setModelData(self, editor, model,
index)
We have taken a similar approach to the previous method, handling the room
field and leaving the others to be handled by the base class. As a matter of
fact, we could have omitted reimplementing this method, and PyQt would have
been smart enough to retrieve the value from our QLineEdit. However, it is a
better practice to take full responsibility for our own customizations.
We have now finished the detour and can return to the MainForm.__init__()
method, beginning with the bottom table that shows the log records that are
applicable to the current asset.
self.logModel = QSqlRelationalTableModel(self)
self.logModel.setTable("logs")
self.logModel.setRelation(ACTIONID,
QSqlRelation("actions", "id", "name"))
self.logModel.setSort(DATE, Qt.AscendingOrder)
self.logModel.setHeaderData(DATE, Qt.Horizontal,
QVariant("Date"))
self.logModel.setHeaderData(ACTIONID, Qt.Horizontal,
QVariant("Action"))
self.logModel.select()
The code for creating the log model is almost the same as the code we used for
the asset model. We use a QSqlRelationalTableModel because we have a foreign
key field, and we provide our own column titles.
self.logView = QTableView()
self.logView.setModel(self.logModel)
self.logView.setItemDelegate(LogDelegate(self))
self.logView.setSelectionMode(QTableView.SingleSelection)
self.logView.setSelectionBehavior(QTableView.SelectRows)
self.logView.setColumnHidden(ID, True)
self.logView.setColumnHidden(ASSETID, True)
self.logView.resizeColumnsToContents()
self.logView.horizontalHeader().setStretchLastSection(True)
This code is also similar to what we did for the assets table, but with three dif-
ferences. Here we have used a custom LogDelegate class—we won’t review itUsing Database Table Views
463
because it is structurally very similar to the AssetDelegate. It provides custom
editing of the date field. We also hide both the log record’s ID field and the
assetid foreign key—there’s no need to show which asset the log records are for
because we are using master–detail, so the only log records that are visible are
those that apply to the current asset. (We will see how the master–detail rela-
tionship is coded shortly.) The last difference is that we have set the last col-
umn to stretch to fill all the available space. The QTableView.horizontalHeader()
method returns a QHeaderView, and this is what controls some aspects of the
table view’s columns, including their widths.
self.connect(self.assetView.selectionModel(),
SIGNAL("currentRowChanged(QModelIndex,QModelIndex)"),
self.assetChanged)
self.connect(addAssetButton, SIGNAL("clicked()"),
self.addAsset)
If the user navigates to a different row we must update the log view to show
the log records for the right asset. This is achieved by the first connection in
conjunction with the assetChanged() method that we will review in a moment.
Every view has at least one selection model that is used to keep track of
which items in the view’s model (if any) are selected. We connect the view’s
selection model’s currentRowChanged() signal so that we can update the log view
depending on the current asset.
All the other connections are button-clicked connections like the second one
shown here. We will cover all the methods the buttons connect to as we
progress through this section.
self.assetChanged(self.assetView.currentIndex())
self.setMinimumWidth(650)
self.setWindowTitle("Asset Manager")
The initializer ends by calling the assetChanged() method with the asset view’s
current model index—this will result in the log view showing the relevant
asset’s records.
def assetChanged(self, index):
if index.isValid():
record = self.assetModel.record(index.row())
id = record.value("id").toInt()[0]
self.logModel.setFilter(QString("assetid = %1").arg(
0 comments:
Post a Comment