After hanging around with some posts with C++ answers which don't fit to my question and which are more confusing than explaining I try to ask here.
I'm trying to subclass QSqlTableModel because I need some boolean columns with checkboxes. The complete working program is:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
import sys
class ImportFilter (QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
print("Welcome to StandardDialog")
# Load the Window
#self.ui = uic.loadUi("ImportFilter.ui",self)
#self.ui.setModal(True)
self.buttonBox = QDialogButtonBox()
self.tableView_typeOfValues = QTableView()
layout = QVBoxLayout()
layout.addWidget(self.buttonBox)
layout.addWidget(self.tableView_typeOfValues)
self.setLayout(layout)
# Init Environmment
self.db = createConnection()
# create Models
self.setupModel()
#setup Views
self.setupView()
# connect Signals
self.buttonBox.clicked.connect(self.pushButton_Box_clicked)
self.show()
print("<- initUI")
def setupView(self):
print("-> setupView")
self.tableView_typeOfValues.setModel(self.tableView_typeOfValues_Model)
self.tableView_typeOfValues.setColumnWidth(0,10)
self.tableView_typeOfValues.setColumnWidth(1,130)
self.tableView_typeOfValues.setColumnWidth(2,130)
self.tableView_typeOfValues.setColumnWidth(3,60)
self.tableView_typeOfValues.setColumnWidth(4,60)
self.tableView_typeOfValues.setColumnWidth(5,60)
self.tableView_typeOfValues.setColumnWidth(6,60)
self.tableView_typeOfValues.hideColumn(0)
self.tableView_typeOfValues.hideColumn(3)
print("<- setupView")
def setupModel(self):
print("-> setupModel")
# own model
self.tableView_typeOfValues_Model = ImportSqlTableModel()
print(" tables:", self.db.tables())
print(' Before .setTable("typeOfValue") and select()')
self.tableView_typeOfValues_Model.info()
self.tableView_typeOfValues_Model.setTable("typeOfValue")
self.tableView_typeOfValues_Model.setEditStrategy(QSqlTableModel.OnFieldChange)
self.tableView_typeOfValues_Model.select()
print(' After .setTable("typeOfValue") and select()')
self.tableView_typeOfValues_Model.info()
self.headerData()
print(" Table:",self.tableView_typeOfValues_Model.tableName())
print(" count:",self.tableView_typeOfValues_Model.rowCount())
self.tableView_typeOfValues_Model.info()
print("<- setupModel")
def setupModelQRY(self):
print("-> setupModel with Query")
# works so far
#self.tableView_typeOfValues_Model = QSqlTableModel() # edit but no checkboxes
self.tableView_typeOfValues_Model = ImportSqlTableModel(self.db) # no edit
# SET query
qry = QSqlQuery(self.db)
sql = "SELECT ID, name, unit, source, Import, ImportIfZero, visible FROM typeOfValue"
qry.prepare(sql)
qry.exec_(sql)
self.tableView_typeOfValues_Model.setQuery(qry)
self.tableView_typeOfValues_Model.select()
print(" Filter:",self.tableView_typeOfValues_Model.filter())
print(" SELECT:", self.tableView_typeOfValues_Model.selectStatement())
self.tableView_typeOfValues_Model.setEditStrategy(QSqlTableModel.OnFieldChange)
print("<- setupModel")
def headerData(self):
print("-> headerData")
self.tableView_typeOfValues_Model.setHeaderData(0,Qt.Horizontal, "ID")
self.tableView_typeOfValues_Model.setHeaderData(1,Qt.Horizontal, "name")
self.tableView_typeOfValues_Model.setHeaderData(2,Qt.Horizontal, "unit")
self.tableView_typeOfValues_Model.setHeaderData(3,Qt.Horizontal, "source")
self.tableView_typeOfValues_Model.setHeaderData(4,Qt.Horizontal, "Import")
self.tableView_typeOfValues_Model.setHeaderData(5,Qt.Horizontal, "ImportIfZero")
self.tableView_typeOfValues_Model.setHeaderData(6,Qt.Horizontal, "visible")
print("<- headerData")
###################################################################################################
# functions
###################################################################################################
def pushButton_Box_clicked(self,signal):
print("okButtonClicked")
print("buttonBox_clicked",signal)
self.tableView_typeOfValues_Model.submitAll()
self.exitcode = "ok, but not implemented"
sys.exit()
def returnCode(self):
return self.exitcode
#######################################################################################################################
# C L A S S
#######################################################################################################################
class ImportSqlTableModel(QSqlTableModel):
def __init__(self):
super(ImportSqlTableModel, self).__init__()
print("-> ImportSqlTableModel.__init__:")
self.booleanSet =[4,5,6]
self.readOnlySet = [1]
print(" Inside:")
self.info()
print("<- ImportSqlTableModel.__init__:")
def info(self):
print("-> info")
print(" ImportSqlTableModel tables inside :", self.database().tables())
print(" ImportSqlTableModel self.db :", self.database())
print(" ImportSqlTableModel self.Table :", self.tableName())
print(" ImportSqlTableModel self.rowCount :", self.rowCount())
print(" ImportSqlTableModel self.lastEror :", self.lastError().text())
print("<- info")
def columnCount(self, index):
count = QSqlTableModel.columnCount(self, index)
return count
def dataChanged(self, QModelIndex, QModelIndex_1, Iterable, p_int=None, *args, **kwargs):
print("-> Datachanged")
def data(self, index, role=Qt.DisplayRole):
print("-> ImportSqlModel.data",index, role)
print(" 1row :", index.row())
print(" col :", index.column())
print(" data :", self.record().fieldName(index.column()))
value = super(ImportSqlTableModel, self).data(index)
print(" value2:",value)
if index.column() in self.booleanSet:
if role == Qt.CheckStateRole:
if value == 2:
return QVariant(Qt.Unchecked)
else:
return QVariant(Qt.Checked)
else:
QSqlTableModel.data(self, index, role)
else:
return QSqlTableModel.data(self, index, role)
def setData(self, index, value, role=Qt.EditRole):
# works with changing value, but not saving
print("-> ImportSqlModel.setData",index,value,role)
print(" value:", value)
if not index.isValid():
return False
if role == Qt.EditRole:
print(" = Qt.Editrole")
QVariant(value)
print(" Update table")
self.select()
if index.column() in self.booleanSet and role == Qt.CheckStateRole:
print(" checkbox changed!")
if value == Qt.Checked:
print(" Qt.Checked")
return QSqlTableModel.setData(self, index, 2 , Qt.EditRole)
else:
print(" not Qt.Checked")
return QSqlTableModel.setData(self, index, 0 , Qt.EditRole)
else:
return QSqlTableModel.setData(self, index, value, role)
def flags(self, index):
print("-> ImportSqlModel.flags")
print(" index.isValid()",index.isValid())
if not index.isValid():
return Qt.ItemIsEnabled
if index.column() in self.booleanSet:
return Qt.ItemIsUserCheckable | Qt.ItemIsEnabled # | Qt.ItemIsSelectable | Qt.ItemIsEditable
elif index.column() in self.readOnlySet:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
else:
return QSqlTableModel.flags(self, index)
print("<- ImportSqlModel.flags")
#######################################################################################################################
# D E M O F U N C T I O N
#######################################################################################################################
def createConnection():
db = QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName('memory')
if not db.open():
QMessageBox.critical(None, qApp.tr("Cannot open database"),
qApp.tr("Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information "
"how to build it.\n\n"
"Click Cancel to exit."),
QMessageBox.Cancel)
return False
query = QSqlQuery()
query.exec_("CREATE TABLE `typeOfValue` (`ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, "\
"`name` TEXT NOT NULL, `unit` TEXT NOT NULL,`source` TEXT,`import` INTEGER,`importIfZero` INTEGER,"\
"`visible` INTEGER);")
query.exec_('insert into typeOfValue (name, unit, source, import, importIfZero, visible) values '\
'("Sound", "dB", "live", 0,0,2)')
query.exec_('insert into typeOfValue (name, unit, source, import, importIfZero, visible) values '\
'("Flow", "m/min", "live", 0,2,2)')
query.exec_('insert into typeOfValue (name, unit, source, import, importIfZero, visible) values '\
'("Vibration", "mm/s", "live", 2,2,2)')
query.exec_('insert into typeOfValue (name, unit, source, import, importIfZero, visible) values '\
'("Voltage", "V", "live", 0,0,0)')
query.exec_('insert into typeOfValue (name, unit, source, import, importIfZero, visible) values '\
'("Ampere", "A", "live", 2,0,2)')
return db
#######################################################################################################################
# M A I N
#######################################################################################################################
if __name__ == '__main__':
createConnection()
app = QApplication(sys.argv)
prog = ImportFilter()
prog.show()
sys.exit(app.exec_())
The problem I was running in, is that I am able to view the table when I use a query to fill it:
def setupModel(self):
print("-> setupModel")
# SET query
qry = QSqlQuery(self.gVar.db)
sql = "SELECT ID, name, unit, source, Import, ImportIfZero, visible FROM typeOfValue"
qry.prepare(sql)
qry.exec_(sql)
self.tableView_typeOfValues_Model.setQuery(qry)
But I found the fault, that this leads to read-only tables. It is described in the manual that it should not be done: See here
So I changed to .setTable("typeOfValue") # = Tablename
self.tableView_typeOfValues_Model = ImportSqlTableModel(self.gVar.db)
self.tableView_typeOfValues_Model.setTable("typeOfValue")
self.tableView_typeOfValues_Model.setEditStrategy(QSqlTableModel.OnFieldChange)
self.tableView_typeOfValues_Model.select()
But now I have an empty view and if I call self.lastError().text() I got the message that the table will not be found because self.database().tables() (called inside the model) brings an empty list. That means to me, that the database is not initialized correctly, but self.database() brings
PyQt5.QtSql.QSqlDatabase object at 0x042C4D30 as result.
Please, can someone give me the hint to correct subclassing QSqlTableModel. Thanks!
The problem is caused by the Qt.ItemIsEditable flag that is not enabled for the Boolean type column, this is necessary in the instruction:
if index.column() in self.booleanSet and role == Qt.CheckStateRole:
print(" checkbox changed!")
if value == Qt.Checked:
print(" Qt.Checked")
return QSqlTableModel.setData(self, index, 2 , Qt.EditRole)
else:
print(" not Qt.Checked")
return QSqlTableModel.setData(self, index, 0 , Qt.EditRole)
since it verifies that the field can be edited.
So the solution is to enable this flag, but to avoid that you can write some text we will disable the Editor through a delegate:
class ReadOnlyDelegate(QItemDelegate):
def createEditor(self, parent, option, index):
lb = QLabel(parent)
return lb
Complete Example:
import sys
from PyQt5.QtCore import QVariant, Qt
from PyQt5.QtSql import QSqlTableModel, QSqlDatabase
from PyQt5.QtWidgets import QApplication, QTableView, QLabel, QItemDelegate
class ImportSqlTableModel(QSqlTableModel):
def __init__(self, *args, **kwargs):
super(ImportSqlTableModel, self).__init__(*args, **kwargs)
self.booleanSet = [4, 5, 6] # column with checkboxes
self.readOnlySet = [1] # columns which must not be changed
self.setTable("typeOfValue")
self.setEditStrategy(QSqlTableModel.OnFieldChange)
self.select()
def data(self, index, role=Qt.DisplayRole):
value = super(ImportSqlTableModel, self).data(index)
if index.column() in self.booleanSet:
if role == Qt.CheckStateRole:
return Qt.Unchecked if value == 2 else Qt.Checked
else:
return QVariant()
return QSqlTableModel.data(self, index, role)
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if index.column() in self.booleanSet:
if role == Qt.CheckStateRole:
val = 2 if value == Qt.Unchecked else 0
return QSqlTableModel.setData(self, index, val, Qt.EditRole)
else:
return False
else:
return QSqlTableModel.setData(self, index, value, role)
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags
if index.column() in self.booleanSet:
return Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsEditable
elif index.column() in self.readOnlySet:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
else:
return QSqlTableModel.flags(self, index)
class ReadOnlyDelegate(QItemDelegate):
def createEditor(self, parent, option, index):
lb = QLabel(parent)
return lb
if __name__ == '__main__':
app = QApplication(sys.argv)
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("/path/of/your_database.db")
if not db.open():
sys.exit(-1)
model = ImportSqlTableModel()
w = QTableView()
w.setModel(model)
for col in model.booleanSet:
w.setItemDelegateForColumn(col, ReadOnlyDelegate(w))
w.show()
sys.exit(app.exec_())
The fault regarding the different QSqlDatabase object's was in the call of the subclassed QSqlTableModel. It must be assignedModel = YourSubclassedTableModel(db=your_open_database_object) if the source is a physical db. If the database is in memory db.setDatabaseName(':memory:') then this db is the default db and will be used inside the model. The docs don't mention that. In the Qt manual you will find:
QSqlTableModel::QSqlTableModel(QObject *parent = Q_NULLPTR, QSqlDatabase db = QSqlDatabase())
Creates an empty QSqlTableModel and sets the parent to parent and the database connection to db. If db is not valid, the default database connection will be used.
Related
How to make QAbstractTableModel 's data checkable
I want to make each cell in the following code can be checked or unchecked by the user ,how to modify the code ?
according to the Qt documentation :Qt::CheckStateRole and set the Qt::ItemIsUserCheckable might be used ,so anyone can give a little sample ?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MyModel(QAbstractTableModel):
def __init__(self, parent=None):
super(MyModel, self).__init__(parent)
def rowCount(self, parent = QModelIndex()):
return 2
def columnCount(self,parent = QModelIndex()) :
return 3
def data(self,index, role = Qt.DisplayRole) :
if (role == Qt.DisplayRole):
return "Row{}, Column{}".format(index.row() + 1, index.column() +1)
return None
if __name__ == '__main__':
app =QApplication(sys.argv)
tableView=QTableView()
myModel = MyModel (None);
tableView.setModel( myModel );
tableView.show();
sys.exit(app.exec_())
Override the flags function in MyModel.
def flags(self, index)
return super(MyModel, self).flags(index)|QtCore.Qt.ItemIsUserCheckable
This says that the index in your model is checkable.
Then override the data function.
def data(self,index, role = Qt.DisplayRole) :
if (role == Qt.DisplayRole):
return "Row{}, Column{}".format(index.row() + 1, index.column() +1)
elif (role==Qt.CheckStateRole):
# read from your data and return Qt.Checked or Unchecked
return None
Finally, you need to implement the setData function.
def setData(self, index, value, role = Qt.EditRole):
if (role==Qt.CheckStateRole):
# Modify your data.
I have a working drag and drop example below for reordering rows of the same column length for a qtableview using PyQt5 (with help from this StackOverflow question here). However I am looking to perform the same operation on a qtableview table where one or two rows have merged cells spanning the total number of columns (like the second row in the picture below).
How would be the best way to go about this? Should I remove the merge (clearSpans) at the point of drag/drop and then do a remerge based on the cell value (though when I tried this it did not work), or is there a way to drag/drop reorder with the cell merging intact?
Here's the code which works for row data of equal columns, but fails when a row is merged
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class myModel(QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data or []
self._headers = ['Type', 'result', 'count']
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < 0 or section >= len(self._headers):
return ""
else:
return self._headers[section]
return None
def data(self, index, role=None):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter
if role == Qt.ForegroundRole:
return QBrush(Qt.black)
if role == Qt.BackgroundRole:
if (self.index(index.row(), 0).data().startswith('second')):
return QBrush(Qt.green)
else:
if (self.index(index.row(), 1).data()) == 'abc':
return QBrush(Qt.yellow)
if (self.index(index.row(), 1).data()) == 'def':
return QBrush(Qt.blue)
if (self.index(index.row(), 1).data()) == 'ghi':
return QBrush(Qt.magenta)
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsDropEnabled | Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
def supportedDropActions(self) -> bool:
return Qt.MoveAction | Qt.CopyAction
def relocateRow(self, row_source, row_target) -> None:
row_a, row_b = max(row_source, row_target), min(row_source, row_target)
self.beginMoveRows(QModelIndex(), row_a, row_a, QModelIndex(), row_b)
self._data.insert(row_target, self._data.pop(row_source))
self.endMoveRows()
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
#self.clearSpans()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
self.model().relocateRow(from_index, to_index)
event.accept()
super().dropEvent(event)
class sample_data(QMainWindow):
def __init__(self):
super().__init__()
tv = myTableView(self)
tv.setModel(myModel([
["first", 'abc', 123],
["second"],
["third", 'def', 456],
["fourth", 'ghi', 789],
]))
self.setCentralWidget(tv)
tv.setSpan(1, 0, 1, 3)
self.show()
if __name__ == '__main__':
app = QApplication([])
test = sample_data()
raise SystemExit(app.exec_())
The sections of the vertical header can be made movable, so there's no need to implement this functionality yourself. It obviously means the vertical header will be visible, but that can be mitigated by making the sections blank, which will result in a relatively narrow header:
Note that moving sections around (rather than rows) is purely visual - the underlying model is never modified. That shouldn't really matter in practice, though, since the header provides methods to translate from logical to visual indices. And it does bring some additional benefits - for example, it's very easy to return to a previous state (i.e. by using the header's saveState and restoreState methods).
Below is a working demo based on your example. The rows can be re-ordered by dragging and dropping the section headers, or by pressing Alt+Up / Alt+Down when a row is selected. The vertical header can be toggled by pressing F6. The logical rows can be printed by pressing F7.
UPDATE:
I also added support for moving sections around by dragging and dropping the rows themselves.
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class myModel(QAbstractTableModel):
def __init__(self, data, parent=None, *args):
super().__init__(parent, *args)
self._data = data or []
self._headers = ['Type', 'result', 'count']
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < 0 or section >= len(self._headers):
return ""
else:
return self._headers[section]
else:
return ''
return None
def data(self, index, role=None):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter
if role == Qt.ForegroundRole:
return QBrush(Qt.black)
if role == Qt.BackgroundRole:
if (self.index(index.row(), 0).data().startswith('second')):
return QBrush(Qt.green)
else:
if (self.index(index.row(), 1).data()) == 'abc':
return QBrush(Qt.yellow)
if (self.index(index.row(), 1).data()) == 'def':
return QBrush(Qt.blue)
if (self.index(index.row(), 1).data()) == 'ghi':
return QBrush(Qt.magenta)
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsDropEnabled | Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
def supportedDropActions(self) -> bool:
return Qt.MoveAction | Qt.CopyAction
class myTableView(QTableView):
def __init__(self, parent):
super().__init__(parent)
header = self.verticalHeader()
header.setSectionsMovable(True)
header.setSectionResizeMode(QHeaderView.Fixed)
header.setFixedWidth(10)
QShortcut('F7', self, self.getLogicalRows)
QShortcut('F6', self, self.toggleVerticalHeader)
QShortcut('Alt+Up', self, lambda: self.moveRow(True))
QShortcut('Alt+Down', self, lambda: self.moveRow(False))
self.setSelectionBehavior(self.SelectRows)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
def dropEvent(self, event):
if (event.source() is not self or
(event.dropAction() != Qt.MoveAction and
self.dragDropMode() != QAbstractItemView.InternalMove)):
super().dropEvent(event)
selection = self.selectedIndexes()
from_index = selection[0].row() if selection else -1
to_index = self.indexAt(event.pos()).row()
if (0 <= from_index < self.model().rowCount() and
0 <= to_index < self.model().rowCount() and
from_index != to_index):
header = self.verticalHeader()
from_index = header.visualIndex(from_index)
to_index = header.visualIndex(to_index)
header.moveSection(from_index, to_index)
event.accept()
super().dropEvent(event)
def toggleVerticalHeader(self):
self.verticalHeader().setHidden(self.verticalHeader().isVisible())
def moveRow(self, up=True):
selection = self.selectedIndexes()
if selection:
header = self.verticalHeader()
row = header.visualIndex(selection[0].row())
if up and row > 0:
header.moveSection(row, row - 1)
elif not up and row < header.count() - 1:
header.moveSection(row, row + 1)
def getLogicalRows(self):
header = self.verticalHeader()
for vrow in range(header.count()):
lrow = header.logicalIndex(vrow)
index = self.model().index(lrow, 0)
print(index.data())
class sample_data(QMainWindow):
def __init__(self):
super().__init__()
tv = myTableView(self)
tv.setModel(myModel([
["first", 'abc', 123],
["second"],
["third", 'def', 456],
["fourth", 'ghi', 789],
]))
self.setCentralWidget(tv)
tv.setSpan(1, 0, 1, 3)
if __name__ == '__main__':
app = QApplication(['Test'])
test = sample_data()
test.setGeometry(600, 100, 350, 185)
test.show()
app.exec_()
Is it possible to conditionally change the background color of items in a QTableView, using PySide2?
I've read a lot on the model view framework . I cannot figure out if it is necessary to use a Delegate or not. Recently I was able to get a column of checkboxes without a Delegate. I believe that the virtual methods setItemData(index, roles) and itemData(index) could be what I need. However, there is no QMap in PySide2. My model must need somewhere to store the extra information to be used by QtCore.Qt.BackgroundRole (that enum, btw, says "the background brush used for items rendered with the default delegate") If I don't specify a delegate, is the "default delegate" used?. Should I be using QStandardItemModel instead?
In the example code below, how would I get a particular column's background color to be red based on some thresholds (the min and max column are the thresholds?
from PySide2.QtWidgets import (QWidget, QApplication, QTableView,QVBoxLayout)
import sys
from PandasModel2 import PandasModel2
import numpy as np
import pandas as pd
class Example(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(300, 300, 700, 300)
self.setWindowTitle("QTableView")
self.initData()
self.initUI()
def initData(self):
data = pd.DataFrame(np.random.randint(1,10,size=(6,4)), columns=['Test#','MIN', 'MAX','MEASURED'])
data['Test#'] = [1,2,3,4,5,6]
#add the checkable column to the DataFrame
data['Check'] = True
self.model = PandasModel2(data)
def initUI(self):
self.tv = QTableView(self)
self.tv.setModel(self.model)
vbox = QVBoxLayout()
vbox.addWidget(self.tv)
self.setLayout(vbox)
app = QApplication([])
ex = Example()
ex.show()
sys.exit(app.exec_())
And I have a custom model using a pandas dataFrame:
import PySide2.QtCore as QtCore
class PandasModel2(QtCore.QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe.
This model is non-hierachical.
"""
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=QtCore.Qt.DisplayRole):
if role==QtCore.Qt.DisplayRole:
if index.column() != 4:
#don't want what determines check state to be shown as a string
if index.isValid():
if index.column() in [1,2,3]:
return '{:.3f}'.format(self._data.iloc[index.row(), index.column()])
if index.column() == 0:
return '{:.2f}'.format(self._data.iloc[index.row(), index.column()])
return str(self._data.iloc[index.row(), index.column()])
if role==QtCore.Qt.CheckStateRole:
if index.column()==4:#had to add this check to get the check boxes only in column 10
if self._data.iloc[index.row(), index.column()] == True:
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
def getMinimum(self, row):
return self._data.iloc[row, self.getColumnNumber('MIN')]
def getMaximum(self, row):
return self._data.iloc[row, self.getColumnNumber('MAX')]
def getColumnNumber(self, string):
'''
Given a string that identifies a label/column,
return the location of that label/column.
This enables the config file columns to be moved around.
'''
return self._data.columns.get_loc(string)
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[col]
return None
def flags(self, index):
'''
The returned enums indicate which columns are editable, selectable,
checkable, etc.
The index is a QModelIndex.
'''
if index.column() == self.getColumnNumber('Check'):
#print(index.column())
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
else:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
return QtCore.Qt.ItemIsEnabled
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
"""Set the value to the index position depending on Qt::ItemDataRole and data type of the column
Args:
index (QtCore.QModelIndex): Index to define column and row.
value (object): new value.
role (Qt::ItemDataRole): Use this role to specify what you want to do.
Raises:
TypeError: If the value could not be converted to a known datatype.
Returns:
True if value is changed. Calls layoutChanged after update.
False if value is not different from original value.
"""
if not index.isValid():
return False
if role == QtCore.Qt.DisplayRole: #why not edit role?
self._data.iat[index.row(),index.column()]= value
self.layoutChanged.emit()
return True
elif role == (QtCore.Qt.CheckStateRole | QtCore.Qt.DisplayRole):
#this block does get executed when toggling the check boxes,
#verified with debugger. Although the action is the same
#as the block above!
self._data.iat[index.row(),index.column()]= value
self.layoutChanged.emit()
return True
else:
return False
The delegate by default uses the BackgroundRole information if it is available so the solution is just to return a QColor, QBrush or similar.
from PySide2 import QtCore, QtGui
class PandasModel2(QtCore.QAbstractTableModel):
# ...
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
if not (0 <= index.row() < self.rowCount() and 0 <= index.column() <= self.columnCount()):
return
value = self._data.iloc[index.row(), index.column()]
if role == QtCore.Qt.DisplayRole:
if index.column() != 4:
if index.column() in [1,2,3]:
return '{:.3f}'.format(value)
if index.column() == 0:
return '{:.2f}'.format(value)
return str(value)
elif role == QtCore.Qt.CheckStateRole:
if index.column() == 4:
return QtCore.Qt.Checked if value else QtCore.Qt.Unchecked
elif index.column() == self.getColumnNumber('MEASURED'):
if role == QtCore.Qt.BackgroundRole:
if self.getMinimum(index.row()) <= value <= self.getMaximum(index.row()):
return QtGui.QColor("red")
I am trying to add a new row to my QtableView and thus a record to a Postgresql database. The idea is to add the record and permit the user to edit it in place in a form which looks like this:
Adding the row seems to go fine but when I try to edit it, the vertical header shows an exclamation point and the two fields which I have set data for disappear.
The question is, how do I add a row for in place editing? If the row already exists, I have no problem editing it.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
import sys
from datetime import datetime, timedelta, time
#form modules
from main import *
class Main(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.ui = Ui_Nextock()
self.ui.setupUi(self)
currentdate = QDate.currentDate()
self.ui.weekending.setDate(currentdate.addDays(6-currentdate.dayOfWeek()))
#self.ui.weekending.setDate(QDate(2017, 6, 10))
self.emp_model = QSqlRelationalTableModel(self)
self.emp_model.setTable('employees')
self.emp_model.setSort(int(self.emp_model.fieldIndex("empid")), Qt.AscendingOrder)
self.emp_model.setRelation(self.emp_model.fieldIndex("department"), QSqlRelation('departments', 'department', 'department'))
self.emp_model.setFilter("termination_date IS NULL")
self.emp_model.select()
self.ui.department.setModel(self.emp_model.relationModel(self.emp_model.fieldIndex("department")))
self.emp_mapper = QDataWidgetMapper(self)
self.emp_mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)
self.emp_mapper.setModel(self.emp_model)
self.emp_mapper.addMapping(self.ui.title, self.emp_model.fieldIndex("salutation"))
self.emp_mapper.addMapping(self.ui.first_name, self.emp_model.fieldIndex("first_name"))
self.emp_mapper.addMapping(self.ui.last_name, self.emp_model.fieldIndex("surname"))
self.emp_mapper.addMapping(self.ui.address, self.emp_model.fieldIndex("Address"))
self.emp_mapper.addMapping(self.ui.email, self.emp_model.fieldIndex('email'))
self.emp_mapper.addMapping(self.ui.phone, self.emp_model.fieldIndex('phone'))
self.emp_mapper.addMapping(self.ui.mobile, self.emp_model.fieldIndex('mobile'))
self.emp_mapper.addMapping(self.ui.birthdate, self.emp_model.fieldIndex('birth_date'))
self.emp_mapper.addMapping(self.ui.start_date, self.emp_model.fieldIndex('start_date'))
self.emp_mapper.addMapping(self.ui.contracted_hours, self.emp_model.fieldIndex('contracted_hours'))
self.emp_mapper.addMapping(self.ui.payrate, self.emp_model.fieldIndex('pay_rate'))
self.emp_mapper.addMapping(self.ui.username, self.emp_model.fieldIndex('loginid'))
self.emp_mapper.addMapping(self.ui.password, self.emp_model.fieldIndex('pwd'))
self.emp_mapper.addMapping(self.ui.department, self.emp_model.fieldIndex('department'))
self.emp_mapper.setItemDelegate(QSqlRelationalDelegate(self.emp_mapper))
self.emp_mapper.toFirst()
self.hours_model = Tc_relational_model(self)
self.hours_model.setTable('emphours')
self.hours_model.setSort(self.hours_model.fieldIndex('id'), Qt.AscendingOrder)
self.hours_model.setRelation(self.hours_model.fieldIndex("department"), QSqlRelation('departments', 'department', 'department'))
self.hours_model.setRelation(self.hours_model.fieldIndex("paytype"), QSqlRelation('pay_types', 'pay_type', 'pay_type'))
self.hours_model.select()
self.hours_view = self.ui.emp_hours
self.hours_view.setModel(self.hours_model)
self.hours_view.setItemDelegate(QSqlRelationalDelegate(self.hours_view))
self.hours_view.setColumnHidden(self.hours_model.fieldIndex('id'), True)
self.hours_view.setColumnHidden(self.hours_model.fieldIndex('empyear'), True)
self.hours_view.setColumnHidden(self.hours_model.fieldIndex('empweek'), True)
self.hours_view.setColumnHidden(self.hours_model.fieldIndex('empmonth'), True)
self.hours_view.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.hours_model.dataChanged.connect(self.dta_chng)
self.ui.weekending.dateChanged.connect(lambda: self.record_changed(self.emp_mapper.currentIndex()))
self.ui.new_line.clicked.connect(self.new_line)
self.ui.current_index.setText('Record ' + str(self.emp_mapper.currentIndex()+1) + ' of ' + str(self.emp_model.rowCount()) + ' records')
def dta_chng(self):
print(self.hours_model.lastError().text())
def new_line(self):
row = self.hours_model.rowCount()
self.hours_model.insertRow(row)
self.idx_empid = QModelIndex(self.hours_model.index(row, self.hours_model.fieldIndex('empid')))
self.hours_model.setData(self.idx_empid, int(self.ui.emp_id.text()), Qt.EditRole)
self.idx_weekending = QModelIndex(self.hours_model.index(row, self.hours_model.fieldIndex('weekending')))
#self.hours_model.setData(self.idx_weekending, self.ui.weekending.date(), Qt.EditRole)
print('In new_line')
def record_changed(self, index):
self.emp_mapper.submit()
if index >= 0:
total_hours = timedelta(0)
the_id = self.emp_model.record(index).value('empid')
self.hours_model.setFilter("empid = '{0}' and weekending = '{1}'".format(the_id,
self.ui.weekending.date().toString(Qt.ISODate)))
self.ui.name_label.setText(self.emp_model.record(index).value('surname')
+ ", " + self.emp_model.record(index).value('first_name'))
self.ui.terminationdate.setText(self.emp_model.record(index).value('termination_date').toString("dd/MM/yyyy"))
self.ui.emp_id.setText(str(self.emp_model.record(index).value('empid')))
for row in range(self.hours_model.rowCount()):
#elapsed = self.data(self.index(index.row(), 9)).toPyDateTime() - self.data(self.index(index.row(), 8)).toPyDateTime()
elapsed = self.hours_model.index(row, 9).data().toPyDateTime() - self.hours_model.index(row, 8).data().toPyDateTime()
if elapsed.total_seconds() > 0:
total_hours += elapsed
self.ui.total_hours.setText(str(total_hours))
else:
self.hours_model.setFilter("empid = -1")
class Tc_relational_model(QSqlRelationalTableModel):
def __init__(self, parent=None):
super(Tc_relational_model, self).__init__(parent)
self.setEditStrategy(QSqlRelationalTableModel.OnFieldChange)
self.setTable("emphours")
self.select()
def columnCount(self, parent=QtCore.QModelIndex()):
return super(Tc_relational_model, self).columnCount()+1
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole and index.column()==10:
if self.data(self.index(index.row(), 9)) is not None and self.data(self.index(index.row(), 8)) is not None:
elapsed = self.data(self.index(index.row(), 9)).toPyDateTime() - self.data(self.index(index.row(), 8)).toPyDateTime()
else:
elapsed = timedelta()
if elapsed.total_seconds() < 0:
elapsed = ''
else:
elapsed = str(elapsed)
return elapsed
if index.column() > 10:
index = self.index(index.row(), index.column()-1)
return super(Tc_relational_model, self).data(index, role)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if section==10 and orientation==QtCore.Qt.Horizontal and role==QtCore.Qt.DisplayRole:
return 'Elapsed time'
if section > 10 and orientation==QtCore.Qt.Horizontal:
section -= 1
return super(Tc_relational_model, self).headerData(section, orientation, role)
def flags(self, index):
if index.column()==10:
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
def setData(self, index, data, role):
if index.column() > 10:
index = self.index(index.row(), index.column()-1)
return super(Tc_relational_model, self).setData(index, data, role)
if __name__=="__main__":
app=QApplication(sys.argv)
pword = 'pword'
dbase = 'db'
server = 'server'
user = 'dave'
db = QSqlDatabase.addDatabase("QPSQL");
db.setHostName(server)
db.setDatabaseName(dbase)
db.setUserName(user)
db.setPassword(pword)
if (db.open()==False):
QMessageBox.critical(None, "Database Error", db.lastError().text())
myapp = Main()
myapp.show()
sys.exit(app.exec_())
Well, I took an entirely different approach to the problem. Below is the adjustment to the code:
def new_line(self):
row = self.hours_model.rowCount()
record = self.hours_model.record()
record.setGenerated('id', False)
record.setValue('empid', self.ui.emp_id.text())
record.setValue('weekending', self.ui.weekending.date())
record.setValue('department', self.ui.department.currentText())
record.setValue('pay_type', 'Regular')
record.setValue('starttime', QDateTime.currentDateTime())
record.setValue('endtime', QDateTime.currentDateTime())
self.hours_model.insertRecord(row, record)
self.hours_view.edit(QModelIndex(self.hours_model.index(row, self.hours_model.fieldIndex('department'))))
This seems to work. However, it begets more questions than it answers. It seems to me that the approach in my question, although more verbose, is more model/view. So I am still stumped.
i use Python 2.7, MariaDB and Qt4. I have a table in which i want to insert rows from a query. I tried self.model = QtSql.QSqlQueryModel() but i found that this is only read only. Then i moved to something else.
My Model() class looks like this:
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
query = QtSql.QSqlQuery()
query.prepare("SELECT denumire,pret_in,pret_out,cantitate,unitate_masura,tax FROM produse WHERE denumire='"+str(self.run_denumire())+"';")
query.exec_()
while(query.next()):
nume_produs = str(query.value(0).toString())
pret_in = str(query.value(1).toString())
pret_out = str(query.value(2).toString())
cantitate = str(query.value(3).toString())
unitate_masura = str(query.value(4).toString())
tax_tva = str(query.value(5).toString())
self.items = [nume_produs,pret_in,pret_out,cantitate,unitate_masura]
def rowCount(self, parent=QModelIndex()):
return 1
def columnCount(self, parent=QModelIndex()):
return len(self.items)
def data(self, index, role):
if not index.isValid(): return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
column=index.column()
if column<len(self.items):
return QVariant(self.items[column])
else:
return QVariant()
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
I am new to Qt and i don't know how to insert rows one by one to appear on table. What you see here is a code found by me here on stackoverflow and i modified it with a query . I need some help on this .
My main code is like this:
import time,os.path, os,module
from PyQt4 import QtGui, uic,QtSql,QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class dialog_receptie(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
file_path = os.path.abspath("ui/receptie.ui")
uic.loadUi(file_path, self)
self.move(QtGui.QApplication.desktop().screen().rect().center() - self.rect().center())
My ui file is:file
The file was made by Qt Designer.
Thank you.
UPDATE 1:
I inserted in class dialog_receptie a function. It looks like this:
def show_lista_receptie(self):
# self.model = QtSql.QSqlQueryModel()
# self.model.setQuery("SELECT den_produs,concat(pret_in,' Lei'),concat(pret_out,' Lei'),val_tva,cantitate,um FROM receptie_temp;")
self.model = QtSql.QSqlTableModel()
self.model.setTable("produse")
self.model.setQuery("SELECT * FROM receptie_temp;")
self.model.setHeaderData(1, QtCore.Qt.Horizontal, self.tr("Produs "))
self.model.setHeaderData(4, QtCore.Qt.Horizontal, self.tr("Pret cumparare "))
self.model.setHeaderData(5, QtCore.Qt.Horizontal, self.tr("Pret vanzare "))
self.model.setHeaderData(6, QtCore.Qt.Horizontal, self.tr("TVA Produs "))
self.model.setHeaderData(7, QtCore.Qt.Horizontal, self.tr("Cantitate "))
self.model.setHeaderData(11, QtCore.Qt.Horizontal, self.tr("UM "))
self.produse_view.setModel(self.model)
self.produse_view.hideColumn(0) # hide id column
self.produse_view.hideColumn(2)
self.produse_view.hideColumn(3)
self.produse_view.hideColumn(8)
self.produse_view.hideColumn(9)
self.produse_view.hideColumn(10)
self.produse_view.hideColumn(12)
If i use this line self.model.setQuery("SELECT * FROM receptie_temp;") i get an error message:
File "E:\onedrive\Documents\optimpos\module\receptie.py", line 64, in show_lista_receptie
self.model.setQuery("SELECT * FROM receptie_temp;")
TypeError: setQuery(self, QSqlQuery): argument 1 has unexpected type 'str'
How can query data from receptie_temp table without using an array? And how are the edited values in the table updated to the sql table?
Thank You.
I recommend using the QSqlTableModel class:
The QSqlTableModel class provides an editable data model for a single
database table.
In your case:
import os
import sys
from PyQt4 import QtGui, uic, QtSql
from PyQt4.QtGui import *
class dialog_receptie(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
file_path = os.path.abspath("ui/receptie.ui")
uic.loadUi(file_path, self)
# self.move(QtGui.QApplication.desktop().screen().rect().center() - self.rect().center())
db = QtSql.QSqlDatabase.addDatabase('QMYSQL')
db.setHostName(HOSTNAME);
db.setDatabaseName(DATABASE);
db.setUserName(USER);
db.setPassword(PASSWORD)
self.model = QtSql.QSqlTableModel()
self.model.setTable("produse")
self.produse_view.setModel(self.model)
self.produse_view.hideColumn(0) # hide id column
self.addData(["a", "b", "c", "d", "e", "f"])
def addData(self, data):
rec = self.model.record()
for i in range(6):
rec.setValue(rec.field(i+1).name(), data[i])
self.model.insertRecord(-1, rec)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = dialog_receptie()
w.show()
sys.exit(app.exec_())