I will briefly describe my program first
Program Description
Below are the 2 widows used in the program:
Main Window (Group List)
Input Window (Student Table)
The Main Window allows users to create/edit/delete entries in Groups list (QListWidget), which shows title of each entry read from a Sqlite database(DB). The 'New' button opens a new Input Window, which allows users to insert entries in Student table (QTableWidget). '+' button adds new row to table, and '-' button removes selected row. 'Name' and 'Age' of each entry can be edited directly by clicking on a cell in the table. The 'Save' button saves the title and the table inputs(entries of students: 'Name' and 'Age') to DB and updates the 'Groups' list in the Main Window. The 'Cancel' button closes the Input Window without saving the changes made in the window.
Database
Group list (QListView)
Student table (QTableWidget)
I am using SQLite using PyQt5's Qtsql class for database. 'group_id' in Student table is FK to Group list's 'id'.
My Problem
I would like to be able to select an entry in 'Groups' list, press 'Edit' button, and show Input Window's Student table filled with data from DB, just as shown above. I tried using QDataWidgetMapper, as it worked well with populating QTextEdit and QLineEdit, but I am having trouble populating QTableWidget with it.
How to populate QTableWidget from database in this context. Is there a method I am missing in QDataWidgetMapper?
Codes
Relevant snippets of my code where I attempt to map Student table in DB to PyQt5 (Most of this is my adapted version of eyllanesc's answer to Accessing SQL data from a list entry)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._groups_model = QtSql.QSqlTableModel(self)
self.groups_model.setTable("Groups")
self.groups_model.select()
self._student_model = QtSql.QSqlTableModel(self)
self.student_model.setTable("Student")
self.student_model.select()
self.sql_list_view = QtWidgets.QListView()
self.sql_list_view.setModel(self.groups_model)
self.sql_list_view.setModelColumn(self.groups_model.record().indexOf("group_name"))
...
#property
def macro_model(self):
return self._macro_model
#property
def sheets_model(self):
return self._sheets_model
#QtCore.pyqtSlot()
def edit(self):
ixs = self.listView_macros.selectionModel().selectedIndexes()
if ixs:
print(ixs)
d = EditDialog(self.groups_model, ixs[0].row(), self.student_model)
d.exec_()
class EditDialog(QtWidgets.QDialog):
def __init__(self, gr_model, gr_idx, std_model, parent=None):
super().__init__(parent)
self.title_le = QtWidgets.QLineEdit()
self.student_table = QtWidgets.QTableWidget(self)
groups_mapper = QtWidgets.QDataWidgetMapper(
self, submitPolicy=QtWidgets.QDataWidgetMapper.ManualSubmit
)
groups_mapper.setModel(gr_model)
groups_mapper.addMapping(self.title_le, gr_model.record().indexOf("group_name"))
groups_mapper.setCurrentIndex(gr_idx)
student_mapper = QtWidgets.QDataWidgetMapper(
self, submitPolicy=QtWidgets.QDataWidgetMapper.ManualSubmit
)
student_mapper.setModel(std_model)
student_mapper.addMapping(self.student_table, ????) # << I am having trouble here
You are confusing the concepts(I recommend you review the official Qt documentation and test the PyQt5 examples that are in its source code). QDataWidgetMapper is used to map a single row of a model so it will not be useful to handle several rows. You should not use QTableWidget but a QTableView with a QSqlTableModel with a filter based on the FK. Then applying the same logic that was implemented to add groups to add students.
from PyQt5 import QtCore, QtGui, QtWidgets, QtSql
def create_connection(database):
db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(database)
if not db.open():
print("Cannot open database")
print(
"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."
)
return False
query = QtSql.QSqlQuery()
if not query.exec_(
"""CREATE TABLE IF NOT EXISTS Groups (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"group_name" TEXT)"""
):
print(query.lastError().text())
return False
if not query.exec_(
"""CREATE TABLE IF NOT EXISTS Student (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"group_id" INTEGER,
"name" TEXT,
"age" INTEGER,
FOREIGN KEY(group_id) REFERENCES Groups(id))"""
):
print(query.lastError().text())
return False
return True
class AddGroupDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.title_le = QtWidgets.QLineEdit()
button_box = QtWidgets.QDialogButtonBox(self)
button_box.setOrientation(QtCore.Qt.Horizontal)
button_box.setStandardButtons(
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.title_le)
lay.addWidget(button_box)
#property
def title(self):
return self.title_le.text()
class EditMacroDialog(QtWidgets.QDialog):
def __init__(self, model, index, parent=None):
super().__init__(parent)
self._group_id = model.record(index).value("id")
self.title_le = QtWidgets.QLineEdit()
self.student_table_model = QtSql.QSqlTableModel()
self.student_table_model.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)
self.student_table_model.setTable("Student")
self.student_table_model.setFilter("group_id={}".format(self.group_id))
self.student_table_model.select()
self.table_view = QtWidgets.QTableView(selectionBehavior=QtWidgets.QAbstractItemView.SelectRows)
self.table_view.setModel(self.student_table_model)
self.table_view.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
for name in ("group_id", "id"):
self.table_view.hideColumn(self.student_table_model.record().indexOf(name))
self.table_view.verticalHeader().hide()
self.plus_button = QtWidgets.QPushButton(self.tr("+"))
self.minus_button = QtWidgets.QPushButton(self.tr("-"))
self.save_button = QtWidgets.QPushButton(self.tr("Save"))
mapper = QtWidgets.QDataWidgetMapper(
self, submitPolicy=QtWidgets.QDataWidgetMapper.ManualSubmit
)
mapper.setModel(model)
mapper.addMapping(self.title_le, model.record().indexOf("group_name"))
mapper.setCurrentIndex(index)
self.plus_button.clicked.connect(self.addRow)
self.minus_button.clicked.connect(self.removeRow)
self.save_button.clicked.connect(mapper.submit)
self.save_button.clicked.connect(self.accept)
hlay = QtWidgets.QHBoxLayout(self)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.title_le)
vlay.addWidget(self.table_view)
hlay.addLayout(vlay)
vlay2 = QtWidgets.QVBoxLayout()
vlay2.addWidget(self.plus_button)
vlay2.addWidget(self.minus_button)
vlay2.addWidget(self.save_button)
hlay.addLayout(vlay2)
#property
def group_id(self):
return self._group_id
#QtCore.pyqtSlot()
def addRow(self):
r = self.student_table_model.record()
r.setValue("group_id", self.group_id)
if self.student_table_model.insertRecord(
self.student_table_model.rowCount(), r
):
self.student_table_model.select()
#QtCore.pyqtSlot()
def removeRow(self):
ixs = self.table_view.selectionModel().selectedIndexes()
if ixs:
self.student_table_model.removeRow(ixs[0].row())
self.student_table_model.select()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QtSql.QSqlTableModel(self)
self.model.setTable("Groups")
self.model.select()
self.sql_list_view = QtWidgets.QListView()
self.sql_list_view.setModel(self.model)
self.sql_list_view.setModelColumn(self.model.record().indexOf("group_name"))
self.new_button = QtWidgets.QPushButton(self.tr("New"))
self.edit_button = QtWidgets.QPushButton(self.tr("Edit"))
self.remove_button = QtWidgets.QPushButton(self.tr("Remove"))
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
grid_layout = QtWidgets.QGridLayout(central_widget)
grid_layout.addWidget(
QtWidgets.QLabel(self.tr("Groups"), alignment=QtCore.Qt.AlignCenter)
)
grid_layout.addWidget(self.sql_list_view, 1, 0)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.new_button)
vlay.addWidget(self.edit_button)
vlay.addWidget(self.remove_button)
grid_layout.addLayout(vlay, 1, 1)
self.resize(640, 480)
self.new_button.clicked.connect(self.new)
self.edit_button.clicked.connect(self.edit)
self.remove_button.clicked.connect(self.remove)
self.sql_list_view.selectionModel().selectionChanged.connect(
self.onSelectionChanged
)
self.onSelectionChanged()
#property
def model(self):
return self._model
#QtCore.pyqtSlot()
def new(self):
d = AddGroupDialog()
if d.exec_() == QtWidgets.QDialog.Accepted:
r = self.model.record()
r.setValue("group_name", d.title)
if self.model.insertRecord(self.model.rowCount(), r):
self.model.select()
#QtCore.pyqtSlot()
def edit(self):
ixs = self.sql_list_view.selectionModel().selectedIndexes()
if ixs:
d = EditMacroDialog(self.model, ixs[0].row())
d.exec_()
#QtCore.pyqtSlot()
def remove(self):
ixs = self.sql_list_view.selectionModel().selectedIndexes()
if ixs:
row = ixs[0].row()
id_ = self.model.record(row).value("id")
query = QtSql.QSqlQuery()
query.prepare("DELETE FROM Student WHERE group_id = ?")
query.addBindValue(id_)
if not query.exec_():
print(query.lastError().text())
return
self.model.removeRow(row)
self.model.select()
#QtCore.pyqtSlot()
def onSelectionChanged(self):
state = bool(self.sql_list_view.selectionModel().selectedIndexes())
self.edit_button.setEnabled(state)
self.remove_button.setEnabled(state)
if __name__ == "__main__":
import sys
database = "database.db" # ":memory:"
app = QtWidgets.QApplication(sys.argv)
if not create_connection(database):
sys.exit(app.exec_())
w = MainWindow()
w.show()
sys.exit(app.exec_())
Related
I am writing a program using python 3.4 to allow a user to enter their Name and Surname and once they hit save the data should save in the db an display on screen. The Data saves to the db fine( I know this because every time I close the program and run it the previous entry displays). My problem here is that when I enter the data and hit save, it does not automatically display on the screen in the tabelView widget. I used QT designer 4 to create the widget and my database table only contains two columns, Full_Name and Surname. How can I get my entry to display immediately after hitting the save button?
Here's my code:
import sys
from Nametest import *
from PyQt4 import QtSql, QtGui
import mysql.connector
conn=mysql.connector.connect(host="localhost", user="root", passwd="shotokan", db="name")
cursor=conn.cursor()
def createConnection():
db = QtSql.QSqlDatabase.addDatabase('QMYSQL')
db.setHostName('localhost')
db.setDatabaseName('name')
db.setUserName('root')
db.setPassword('shotokan')
db.open()
print (db.lastError().text())
return True
class MyForm(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.model = QtSql.QSqlTableModel(self)
self.model.setTable("fullname")
self.model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.model.select()
self.ui.tableView.setModel(self.model)
QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()' ),self.InsertRecords)
def InsertRecords(self):
cursor.execute( """INSERT INTO fullname (First_Name, Surname)VALUES('%s','%s')""" %(self.ui.lineEdit.text(),self.ui.lineEdit_2.text()))
conn.commit()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
if not createConnection():
sys.exit(1)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
Here is an image of my Widget:
Don't use 2 technologies to do the same task, if you have used QSqlTableModel to display the information then use that model to do the insert:
class MyForm(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.model = QtSql.QSqlTableModel(self)
self.model.setTable("fullname")
self.model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.model.select()
self.ui.tableView.setModel(self.model)
self.ui.pushButton.clicked.connect(self.InsertRecords)
def InsertRecords(self):
db = self.model.database()
db.transaction()
record = self.model.record()
record.setValue("First_Name", self.ui.lineEdit.text())
record.setValue("Surname", self.ui.lineEdit_2.text())
if self.model.insertRecord(-1, record):
print("successful insertion")
self.model.submitAll()
else:
db.rollback()
Note: Do not use concatenation to create a query since your application is susceptible to SQL Injection.
I have two QTableViews inside a QMainWindow and I want to create a context menu on one of them and later another context menu on the other one.
Creating the context menu and define actions works so far. But the context menu pops up everywhere inside the whole application. I don't know how to limit it to only one specific table. I think it has to do with contextMenuEvent() which is a member of QMainWindow, but I don't know how to change this part. Trying to create a custom class that inherits QTableView didn't work, because I'm not sure where to start.
Here's what I tried:
The populate_table_1() and populate_table_2() methods are only for filling some data into the tables. The get_selected_item_TV1() method gets the necessary data from a row of table_1. The delete_file() method is an example for what I want do when calling the delete action from the contextMenuEvent() method. This code works so far, but I want the context menu to popup only if I right-click on a row of table_1 and that it don't appear at all when right-clicking elsewhere.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
data_1 = ["file_name", "file_infos"]
data_2 = ["other_stuff_1", "other_stuff_2"]
class Ui_MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("MyApp")
self.resize(450, 280)
centralwidget = QWidget(self)
#]===================================================================[#
# table_1
table_1 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers
)
# table_1 models
self.modelTV1 = QStandardItemModel(0, 2, centralwidget)
self.modelTV1.setHorizontalHeaderLabels(["column 1", "column 2"])
table_1.setModel(self.modelTV1)
self.selectionModelTV1 = table_1.selectionModel()
#]===================================================================[#
# table_2
table_2 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers,
)
# table_2 models
self.modelTV2 = QStandardItemModel(0, 2, centralwidget)
self.modelTV2.setHorizontalHeaderLabels(["column 1", "column 2"])
table_2.setModel(self.modelTV2)
self.selectionModelTV2 = table_2.selectionModel()
v_Layout1 = QVBoxLayout()
v_Layout1.addWidget(table_1)
v_Layout1.addWidget(table_2)
gridLayout = QGridLayout(centralwidget)
gridLayout.addLayout(v_Layout1, 0, 0, 1, 1)
self.setCentralWidget(centralwidget)
def populate_table_1(self):
self.modelTV1.setRowCount(0)
for item in data_1:
self.modelTV1.insertRow(0)
for i, text in enumerate(data_1):
self.modelTV1.setItem(0, i, QStandardItem(text))
def populate_table_2(self):
self.modelTV2.setRowCount(0)
for item in data_2:
self.modelTV2.insertRow(0)
for i, text in enumerate(data_2):
self.modelTV2.setItem(0, i, QStandardItem(text))
def contextMenuEvent(self, event):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
def get_selected_item_TV1(self):
# get the row's text from the first column in table_1
listed_items = self.selectionModelTV1.selectedRows()
for index in listed_items:
selected_item = index.data()
return f"table_1 - row_{index.row()} - {selected_item}"
def delete_file(self, event):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
mainUI = Ui_MainWindow()
mainUI.populate_table_1()
mainUI.populate_table_2()
mainUI.show()
sys.exit(app.exec_())
There are many alternatives:
Detect that when the mouse is pressed it is in a certain area, for example the first QTableView. In this case the widget must be accessible in the contextMenuEvent() so you must change table_1 to self.table_1 and then use underMouse():
def contextMenuEvent(self, event):
if self.table_1.underMouse():
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
Implement the contextMenuEvent method for each QTableView:
class TableView(QTableView):
def contextMenuEvent(self, event):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
def get_selected_item_TV1(self):
# get the row's text from the first column in table_1
listed_items = self.selectionModel().selectedRows()
for index in listed_items:
selected_item = index.data()
return f"table_1 - row_{index.row()} - {selected_item}"
def delete_file(self, event):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")
and then you must change table_1 = QTableView(... for table_1 = TableView(...
Another alternative is to use the customContextMenuRequested signal, for this you must enable the Qt::CustomContextMenu flag:
table_1 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers,
contextMenuPolicy=Qt.CustomContextMenu
)
table_1.customContextMenuRequested.connect(self.on_customContextMenuRequested)
def on_customContextMenuRequested(self):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file())
self.contextMenu.popup(QCursor.pos())
def delete_file(self):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")
I am using PyQt5, where I am trying to create teams and work on a league system.
I have created Action Buttons which open Dialog boxes.
I want to populate certain lists from the database based on a team name I choose from my dialog window.
I think I am stuck, because I cannot understand how to communicate between the two.
When I try to add a new team, I want that all lists in my main window get appropriately filled. But how do I pass this information from the dialog box to the main window and also close the dialog box immediately after that?
Here is the code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QInputDialog, QLineEdit, QDialog, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QApplication, QComboBox
import sqlite3
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
#removed because too big
#full code in link
def setupEvents(self, MainWindow):
self.actionNew_Team.triggered.connect(self.newTeam)
self.actionOpen_Team.triggered.connect(self.openTeam)
def newTeam(self, MainWindow):
n = NewTeamDialog()
n.exec_()
def openTeam(self, MainWindow):
o = OpenTeamDialog()
o.exec_()
def saveTeam(self, MainWindow):
pass
class NewTeamDialog(QDialog):
def __init__(self):
super(NewTeamDialog, self).__init__()
self.setWindowTitle("Create New Team")
self.setFixedWidth(300)
self.setFixedHeight(100)
self.nameInput = QLineEdit()
self.nameInput.setPlaceholderText("Enter Team Name")
self.addBtn = QPushButton()
self.addBtn.setText("Add Team")
self.addBtn.clicked.connect(self.addTeam)
layout = QVBoxLayout()
layout.addWidget(self.nameInput)
layout.addWidget(self.addBtn)
self.setLayout(layout)
def addTeam(self):
name = self.nameInput.text()
conn = sqlite3.connect("example.db")
c = conn.cursor()
c.execute('SELECT * FROM batsmen')
print(c.fetchall())
conn.close()
self.close()
LINK: https://www.paste.org/99817
Is this what you are looking for?
You are creating the OpenTeamDialog and NewTeamDialog classes through methods, but the dialogs don't know anything about the Window class so you must pass it as a parameter when initializing them so that you can access all of its widgets.
This is assuming that your databse is the same meaning there are the following tables:
ALLROUNDERS, BATSMEN,BOWLERS, WICKETKEEPER and a field column called TEAM:
Also I am setting up the UI in another class so I would delete any thing besides the UI part that you got from QtDesigner in the Ui_MainWindow class.
There is most likely a better way to implement this but this is just a crude design based on your needs.
class Window(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self, parent = None):
super().__init__(parent)
self.setupUi(self)
self.setupEvents()
def setupEvents(self):
self.actionNew_Team.triggered.connect(self.newTeam)
self.actionOpen_Team.triggered.connect(self.openTeam)
def newTeam(self):
n = NewTeamDialog(self)
n.exec_()
def openTeam(self):
o = OpenTeamDialog(self)
o.exec_()
def saveTeam(self):
pass
class NewTeamDialog(QtWidgets.QDialog):
def __init__(self,window,parent = None):
super(NewTeamDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.window = window
self.setWindowTitle("Create New Team")
self.setFixedWidth(300)
self.setFixedHeight(100)
self.dropDown_TeamType = QtWidgets.QComboBox()
#Added dropdown to choose which team goes in the team type
self.dropDown_TeamType.addItems(["ALLROUNDERS", "BATSMEN", "BOWLERS","WICKETKEEPER" ])
self.nameInput = QtWidgets.QLineEdit()
self.nameInput.setPlaceholderText("Enter Team Name")
self.addBtn = QtWidgets.QPushButton()
self.addBtn.setText("Add Team")
self.addBtn.clicked.connect(self.addTeam)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.dropDown_TeamType)
layout.addWidget(self.nameInput)
layout.addWidget(self.addBtn)
self.setLayout(layout)
def addTeam(self):
name = self.nameInput.text()
team_type = self.dropDown_TeamType.currentText()
conn = sqlite3.connect("example.db")
c = conn.cursor()
#adds team to the database using the current selected dropdown item
c.execute("SELECT TEAM FROM {0} WHERE TEAM=?;".format(team_type),(name.title(),))
exists = c.fetchall()
if not exists:
c.execute("INSERT INTO {0} VALUES('{1}');".format(team_type,name.title()))
conn.commit()
conn.close()
conn.close()
self.close()
if team_type == "BATSMEN":
item = QtWidgets.QListWidgetItem(name)
self.window.listWidget_3.addItem(item)
elif team_type == "ALLROUNDERS":
item = QtWidgets.QListWidgetItem(name)
self.window.listWidget_4.addItem(item)
elif team_type == "BOWLERS":
item = QtWidgets.QListWidgetItem(name)
self.window.listWidget_5.addItem(item)
elif team_type == "WICKETKEEPER":
item = QtWidgets.QListWidgetItem(name)
self.window.listWidget_6.addItem(item)
class OpenTeamDialog(QtWidgets.QDialog):
def __init__(self, window, parent = None):
super(OpenTeamDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.window = window
self.setWindowTitle("Open Saved Team")
self.setFixedWidth(300)
self.setFixedHeight(100)
self.dropDown_TeamType = QtWidgets.QComboBox()
self.dropDown_TeamType.addItems(["ALLROUNDERS", "BATSMEN", "BOWLERS","WICKETKEEPER" ])
self.dropDown = QtWidgets.QComboBox()
self.dropDown_TeamType.currentIndexChanged.connect(self.itemChanged)
self.addBtn = QtWidgets.QPushButton()
self.addBtn.setText("Add Team")
self.addBtn.clicked.connect(self.openTeam)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.dropDown_TeamType)
layout.addWidget(self.dropDown)
layout.addWidget(self.addBtn)
self.setLayout(layout)
conn = sqlite3.connect("example.db")
conn.row_factory = lambda cursor, row: row[0]
c = conn.cursor()
c.execute("SELECT TEAM FROM ALLROUNDERS")
result = c.fetchall()
self.dropDown.addItems(result)
def itemChanged(self):
#adds all items from the database 'Team' column to drop down whenever it changes
team_type = self.dropDown_TeamType.currentText()
self.dropDown.clear()
conn = sqlite3.connect("example.db")
conn.row_factory = lambda cursor, row: row[0]
c = conn.cursor()
c.execute("SELECT TEAM FROM {0}".format(team_type))
result = c.fetchall()
self.dropDown.addItems(result)
conn.close()
def openTeam(self):
team_type = self.dropDown_TeamType.currentText()
team_name = self.dropDown.currentText()
self.close()
if team_type == "BATSMEN":
item = QtWidgets.QListWidgetItem(team_name)
self.window.listWidget_3.addItem(item)
elif team_type == "ALLROUNDERS":
item = QtWidgets.QListWidgetItem(team_name)
self.window.listWidget_4.addItem(item)
elif team_type == "BOWLERS":
item = QtWidgets.QListWidgetItem(team_name)
self.window.listWidget_5.addItem(item)
elif team_type == "WICKETKEEPER":
item = QtWidgets.QListWidgetItem(team_name)
self.window.listWidget_6.addItem(item)
class EvaluateDialog(QtWidgets.QDialog):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = Window()
MainWindow.show()
sys.exit(app.exec_())
Here is a .py file you can use to create the same database:
import sqlite3
def createTables():
connection = sqlite3.connect("example.db")
connection.execute("CREATE TABLE ALLROUNDERS(TEAM TEXT NOT NULL)")
connection.execute("CREATE TABLE BATSMEN(TEAM TEXT NOT NULL)")
connection.execute("CREATE TABLE BOWLERS(TEAM TEXT NOT NULL)")
connection.execute("CREATE TABLE WICKETKEEPER(TEAM TEXT NOT NULL)")
connection.execute("INSERT INTO ALLROUNDERS VALUES(?)",('Empty',))
connection.execute("INSERT INTO BATSMEN VALUES(?)",('Empty',))
connection.execute("INSERT INTO BOWLERS VALUES(?)",('Empty',))
connection.execute("INSERT INTO WICKETKEEPER VALUES(?)",('Empty',))
connection.commit()
result = connection.execute("SELECT * FROM BATSMEN")
connection.close()
createTables()
I'm creating a GUI in PyQT5 that should provide multiple views on data from multiple related SQLite tables. I have implemented these views to be displayed via a QStackedWidget.
Now, some of these views are for overview purposes, others for more detailled views on a subset of the data displayed in the overviews. I want to access the detailled views from the overviews via rightclick.
I have included a minimal example with cars below. (Sorry, it's a bit long, but that was needed to provide a full working example. I have reduced it as much as I could.) The goal is to show the DetailledView with the cars of the company selected in the Overview.
This already provides access from the Overview to the DetailledView on Rightclick, but the company information is not passed along. So even when accessing the DetailledView from 'VW', self.mycompany gets updated but car_widget doesn't, so the DetailledView shows the info about 'Honda' cars (the default).
Is there a way to update car_widgit with the right company? Or do I need to create the DetailledView at runtime? (Does that even work? But I would prefer not to, anyway, as it could make the index of the Stack unreliable...)
How can I update the QTableModel of one view in a QStackedWidget according to what was selected in another view?
Here's the code:
#!/usr/bin/python3
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QMainWindow, QWidget, QApplication, QGridLayout,
QStackedWidget, QTableView, QMenu)
from PyQt5.QtCore import Qt, QModelIndex
import sys
class MainGUI(QMainWindow):
def __init__(self):
super().__init__()
self.mycompany = "Honda"
self.create_connection()
self.fill_tables()
self.init_UI()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def fill_tables(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Manufacturers;")
q.exec_("CREATE TABLE Manufacturers (CompanyId INT PRIMARY KEY, Name TEXT, Country TEXT);")
q.exec_("INSERT INTO Manufacturers VALUES (1, 'VW', 'Germany');")
q.exec_("INSERT INTO Manufacturers VALUES (2, 'Honda' , 'Japan');")
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Model TEXT, Year INT, Company INT);")
q.exec_("INSERT INTO Cars VALUES ('Civic', 2009, 'Honda');")
q.exec_("INSERT INTO Cars VALUES ('Golf', 2013, 'VW');")
q.exec_("INSERT INTO Cars VALUES ('Polo', 1999, 'VW');")
self.db.commit()
def init_UI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.grid = QGridLayout()
self.central_widget.setLayout(self.grid)
self.setLayout(self.grid)
self.make_stack()
self.show()
def make_stack(self):
self.Stack = QStackedWidget(self)
company_view = QWidget()
layout = QGridLayout()
company_view.setLayout(layout)
self.company_widget = Overview()
layout.addWidget(self.company_widget, 0, 0)
self.company_widget.table.customContextMenuRequested.connect(self.open_menu)
self.Stack.addWidget(company_view)
car_view = QWidget()
layout2 = QGridLayout()
car_view.setLayout(layout2)
car_widget = DetailedView(self.mycompany)
layout2.addWidget(car_widget, 0, 0)
self.Stack.addWidget(car_view)
self.grid.addWidget(self.Stack, 0,1)
def open_menu(self, pos):
menu = QMenu()
show_act = menu.addAction("Show cars")
action = menu.exec_(self.company_widget.table.mapToGlobal(pos))
if action == show_act:
row = self.company_widget.table.indexAt(pos).row()
myindex = self.company_widget.model.index(row, 1, QModelIndex())
company = self.company_widget.model.data(myindex)
self.mycompany = company
self.Stack.setCurrentIndex(1)
class MyTable(QWidget):
def __init__(self):
super().__init__()
self.create_connection()
self.create_model()
self.init_UI()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def create_model(self):
self.model = None
def init_UI(self):
self.grid = QGridLayout()
self.setLayout(self.grid)
self.table = QTableView()
self.table.setModel(self.model)
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.grid.addWidget(self.table, 0, 0)
def closeEvent(self, e):
if (self.db.open()):
self.db.close()
def check_error(self, q):
lasterr = q.lastError()
if lasterr.isValid():
print(lasterr.text())
self.db.close()
exit(1)
class Overview(MyTable):
def __init__(self):
super().__init__()
def create_model(self):
self.model = QtSql.QSqlTableModel()
q = QtSql.QSqlQuery()
query = "SELECT * from Manufacturers"
q.exec_(query)
self.model.setQuery(q)
class DetailedView(MyTable):
def __init__(self, company):
self.company = company
super().__init__()
def create_model(self):
self.model = QtSql.QSqlTableModel()
q = QtSql.QSqlQuery()
query = "SELECT * from cars where company = '{}'".format(self.company)
q.exec_(query)
self.model.setQuery(q)
def main():
app = QApplication(sys.argv)
ex = MainGUI()
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()
One of the objectives of the inheritance is that the class implements the common tasks that the children can do, and in your case you do not observe those tasks, for example the creation of the model must be done in the father since all the children will do it.
On the other hand the goal of using QSqlTableModel is not to use the QSqlQuery but more friendly requests like setTable(), select() and setFilter(), but it is simply a waste because you could use QSqlQueryModel.
On the other hand I see that you are assuming that the self.mycompany of MainGui is the same as the self.company of DetailedView for what you are going through in the creation of the DetailView object, and the truth is that they are not the same, in the creation of the object only the value of that moment has been copied, so if you change the self.company of MainGui it will not change the self.company of DetailView.
Restructuring your project you get the following:
#!/usr/bin/python3
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
from PyQt5.QtWidgets import (QMainWindow, QWidget, QApplication, QVBoxLayout,
QStackedWidget, QTableView, QMenu)
from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal
import sys
DB_PATH = "test.db"
def create_connection():
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(DB_PATH)
if not db.open():
print("Cannot establish a database connection to {}!".format(DB_PATH))
return False
return True
def fill_tables():
q = QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Manufacturers;")
q.exec_("CREATE TABLE Manufacturers (CompanyId INT PRIMARY KEY, Name TEXT, Country TEXT);")
q.exec_("INSERT INTO Manufacturers VALUES (1, 'VW', 'Germany');")
q.exec_("INSERT INTO Manufacturers VALUES (2, 'Honda' , 'Japan');")
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Model TEXT, Year INT, Company INT);")
q.exec_("INSERT INTO Cars VALUES ('Civic', 2009, 'Honda');")
q.exec_("INSERT INTO Cars VALUES ('Golf', 2013, 'VW');")
q.exec_("INSERT INTO Cars VALUES ('Polo', 1999, 'VW');")
class MainGUI(QMainWindow):
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.lay = QVBoxLayout(self.central_widget)
self.make_stack()
def make_stack(self):
self.stack = QStackedWidget()
self.lay.addWidget(self.stack)
self.company_widget = Overview()
self.car_widget = DetailedView()
self.stack.addWidget(self.company_widget)
self.stack.addWidget(self.car_widget)
self.company_widget.changedCompany.connect(self.changedCompany)
self.car_widget.backSignal.connect(lambda: self.stack.setCurrentIndex(0))
def changedCompany(self, company):
self.car_widget.filter(company)
self.stack.setCurrentIndex(1)
class SQLTable(QTableView):
def __init__(self, table):
super().__init__()
self.init_UI()
self.create_model(table)
def create_model(self, table):
self.model.setTable(table)
self.model.select()
def init_UI(self):
self.model = QSqlTableModel()
self.setModel(self.model)
self.setContextMenuPolicy(Qt.CustomContextMenu)
class Overview(SQLTable):
changedCompany = pyqtSignal(str)
def __init__(self):
SQLTable.__init__(self, "Manufacturers")
self.customContextMenuRequested.connect(self.open_menu)
def open_menu(self, pos):
menu = QMenu()
show_act = menu.addAction("Show cars")
action = menu.exec_(self.mapToGlobal(pos))
if action == show_act:
row = self.indexAt(pos).row()
ix = myindex = self.model.index(row, 1)
company = self.model.data(ix)
self.changedCompany.emit(company)
class DetailedView(SQLTable):
backSignal = pyqtSignal()
def __init__(self):
SQLTable.__init__(self, "cars")
self.customContextMenuRequested.connect(self.open_menu)
def open_menu(self, pos):
menu = QMenu()
back_act = menu.addAction("Show Manufacturers")
action = menu.exec_(self.mapToGlobal(pos))
if action == back_act:
self.backSignal.emit()
def filter(self, company):
self.model.setFilter("company='{}'".format(company))
def main():
app = QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
fill_tables()
ex = MainGUI()
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()
For a PyQT5 widget, I need to display data from an SQL-query to an SQLite database with columns and rows inverted/rotated. Ideally, in a QTableView. (This table will only have 2 columns, one for the previous column names and one for their values. The table is meant to show stats which will be aggregated in the SQL query, which will return only one row. So I want to go from one row with multiple columns, to 2 columns with multiple rows.)
I have come up with a workaround that does the right thing using a QFormLayout instead, but it looks ugly and seems very inelegant. (See the display_data(self) method.)
#!/usr/bin/python3
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QFormLayout, QWidget,
QLabel, QLineEdit, QApplication)
import sys
class InvertedTable(QWidget):
def __init__(self, company):
super().__init__()
self.db_file = "test.db"
self.company = company
self.create_connection()
self.fill_table()
self.init_UI()
self.display_data()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName(self.db_file)
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def fill_table(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""")
q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)")
q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
self.db.commit()
def init_UI(self):
self.resize(300,100)
self.layout = QFormLayout()
self.setLayout(self.layout)
def display_data(self):
query = "select * from cars where company = '{}'".format(self.company)
q = QtSql.QSqlQuery()
q.exec_(query)
self.check_error(q)
record = q.record()
columns = record.count()
q.next()
for i in range(columns):
column_name = record.field(i).name()
col_field = QLabel(column_name, self)
value = q.value(i)
value_field = QLineEdit(self)
value_field.setText(value)
self.layout.addRow(col_field, value_field)
def closeEvent(self, e):
if (self.db.open()):
self.db.close()
def check_error(self, q):
lasterr = q.lastError()
if lasterr.isValid():
print(lasterr.text())
self.db.close()
exit(1)
def main():
app = QApplication(sys.argv)
ex = InvertedTable("Honda")
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()
What is the proper way to accomplish this using QTableView?
The proper way to work with QTableView would be to have a QTableModel.
As luck would have it, there is a QSqlTableModel that allows you to build a table model against a SQL table.
Funt answered a similar question by pointing to QIdentityProxyModel that can be used "on top of that" to change the representation of a data model by redefining mapToSource and mapFromSource methods.
There are also ways to transpose the result of a SQL request directly from the SQL command. See here.
Also worth reading : Model-View Programming with Qt. It's the C++ version but PyQt follows the same principles (and the classes have the same name).
Hope that helps.
After some more searching and reading up on the helpful pointers left by #PlikPlok, I found a solution here:
Apparently, this functionality is not provided by any Qt-classes out of the box, so you have to subclass both QAbstractProxyModel and QSqlRelationalDelegate, and then use these on your table:
#!/usr/bin/python3
import sys
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QApplication,
QGridLayout, QTableView)
from PyQt5.Qt import (QModelIndex, QAbstractProxyModel, QSqlRelationalDelegate)
from PyQt5.QtCore import Qt
class FlippedProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
def mapFromSource(self, index):
return self.createIndex(index.column(), index.row())
def mapToSource(self, index):
return self.sourceModel().index(index.column(), index.row(), QModelIndex())
def columnCount(self, parent):
return self.sourceModel().rowCount(QModelIndex())
def rowCount(self, parent):
return self.sourceModel().columnCount(QModelIndex())
def index(self, row, column, parent):
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
def data(self, index, role):
return self.sourceModel().data(self.mapToSource(index), role)
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal:
return self.sourceModel().headerData(section, Qt.Vertical, role)
if orientation == Qt.Vertical:
return self.sourceModel().headerData(section, Qt.Horizontal, role)
class FlippedProxyDelegate(QSqlRelationalDelegate):
def createEditor(self, parent, option, index):
proxy = index.model()
base_index = proxy.mapToSource(index)
return super().createEditor(parent, option, base_index)
def setEditorData(self, editor, index):
proxy = index.model()
base_index = proxy.mapToSource(index)
return super().setEditorData(editor, base_index)
def setModelData(self, editor, model, index):
base_model = model.sourceModel()
base_index = model.mapToSource(index)
return super().setModelData(editor, base_model, base_index)
class InvertedTable(QWidget):
def __init__(self, company):
super().__init__()
self.db_file = "test.db"
self.company = company
self.create_connection()
self.fill_table()
self.create_model()
self.init_UI()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName(self.db_file)
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def fill_table(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""")
q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)")
q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
self.db.commit()
def create_model(self):
self.model = QtSql.QSqlTableModel()
q = QtSql.QSqlQuery()
query = """SELECT * from cars where company = 'Honda'
"""
q.exec_(query)
self.model.setQuery(q)
self.proxy = FlippedProxyModel() # use flipped proxy model
self.proxy.setSourceModel(self.model)
def init_UI(self):
self.grid = QGridLayout()
self.setLayout(self.grid)
self.table = QTableView()
self.table.setModel(self.proxy)
self.table.setItemDelegate(FlippedProxyDelegate(self.table)) # use flipped proxy delegate
self.table.horizontalHeader().hide()
self.grid.addWidget(self.table, 0, 0)
def closeEvent(self, e):
if (self.db.open()):
self.db.close()
def check_error(self, q):
lasterr = q.lastError()
if lasterr.isValid():
print(lasterr.text())
self.db.close()
exit(1)
def main():
app = QApplication(sys.argv)
ex = InvertedTable("Honda")
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()