i want to display in a QlistView the index and the file name, so i subclassed QSqlQueryModel to override the data() method but i'm always getting None, it seems like i'm displaying the data befor adding it or some thing like that here is the concerned part of my code :
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
import sys, os
import pathlib
CURRENT_PATH = pathlib.Path(__file__).parent
connection = QSqlDatabase.addDatabase("QSQLITE")
connection.setDatabaseName("medias.sqlite")
connection.open()
print(connection.open())
createTableQuery = QSqlQuery()
createTableQuery.exec(
"""
CREATE TABLE fichiers (
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
path VARCHAR(300) NOT NULL
)
"""
)
print(connection.tables())
class PlaylistModel(QSqlQueryModel):
def __init__(self, playlist,*args, **kwargs):
super(PlaylistModel, self).__init__(*args, **kwargs)
self.playlist = playlist or [[]]
def data(self, index, role):
row = index.row()
if role == Qt.DisplayRole:
try:
text = self.playlist[index.row()][1]
except IndexError:
text = None
return text # always getting None
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.play_list = []
self.setGeometry(900,180,800,600)
self.setWindowTitle("Media Display")
self.model = PlaylistModel(self.play_list)
self.model.setQuery("SELECT path FROM fichiers")
self.listview = QListView()
self.listview.setModel(self.model)
self.listview.setModelColumn(1)
self.main_layout()
self.DbConnect()
def DbConnect(self):
self.connection = QSqlDatabase.addDatabase("QSQLITE")
self.connection.setDatabaseName("medias.sqlite")
self.connection.open()
createTableQuery = QSqlQuery()
createTableQuery.exec(
""" CREATE TABLE fichiers (
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
path VARCHAR(300) NOT NULL
)
"""
)
self.model.setQuery("SELECT path FROM fichiers")
return True
def addToPlaylist(self):
self.play_list.clear()
model = self.listview.model()
for row in range(model.rowCount()):
index = model.index(row , 0)
item = model.data(index, Qt.DisplayRole)
self.play_list.append(item)
print('the playlist',self.play_list)
def addImage(self):
fichier_base, _ = QFileDialog.getOpenFileName(self, 'select video', QDir.homePath(),"Images (*.png *.xpm *.jpg *.jpeg)")
if fichier_base:
query = QSqlQuery()
query.prepare("""INSERT INTO fichiers (path) VALUES (?)""")
query.addBindValue(fichier_base)
if query.exec_():
last_query = self.model.query().executedQuery()
self.model.setQuery("")
self.model.setQuery(last_query)
else:
print(query.lastError().text())
def clearDb(self):
query = QSqlQuery(self.connection)
if self.connection.open():
query.exec("DELETE FROM fichiers")
query.clear()
last_query = self.model.query().executedQuery()
self.model.setQuery("")
self.model.setQuery(last_query)
def main_layout(self):
self.add_img_btn = QPushButton("Add image ")
self.add_img_btn.setFixedWidth(150)
self.add_img_btn.clicked.connect(self.addImage)
self.clear_db_btn = QPushButton("clear DB")
self.clear_db_btn.setFixedWidth(150)
self.clear_db_btn.clicked.connect(self.clearDb)
self.refresh_btn = QPushButton("refresh")
self.refresh_btn.setFixedWidth(150)
self.refresh_btn.clicked.connect(self.addToPlaylist)
group_btns = QHBoxLayout()
main_app = QVBoxLayout()
main_app.addWidget(self.listview)
main_app.addLayout(group_btns)
group_btns.addWidget(self.add_img_btn)
group_btns.addWidget(self.clear_db_btn)
group_btns.addWidget(self.refresh_btn)
vboxlay = QHBoxLayout()
vboxlay.addLayout(main_app)
widget = QWidget(self)
self.setCentralWidget(widget)
widget.setLayout(vboxlay)
if __name__ == '__main__':
app= QApplication(sys.argv)
window = MainWindow()
window.setStyleSheet('background-color:#fff;')
window.show()
sys.exit(app.exec_())
on a previous app i subclassed the QAbstractListModel Class and i did it like that
class PlaylistModel(QAbstractListModel):
def __init__(self, playlist, *args, **kwargs):
super(PlaylistModel, self).__init__(*args, **kwargs)
self.playlist = playlist
def data(self, index, role):
if role == Qt.DisplayRole:
media = self.playlist.media(index.row())
print('mediaaaaaaa', media )
print('plaaaaaylist', self.playlist )
name_video = media.canonicalUrl().fileName()
i = index.row() + 1
return f"{i} - {name_video}"
def rowCount(self, index):
return self.playlist.mediaCount()
The following should be taken into account:
If you are going to show information from a table then you must use a QSqlTableModel. QSqlQueryModel is a read-only model whose objective is to show very particular query information. On the other hand, QSqlTableModel has several methods to handle the tables.
If you are going to modify how the information of the model is shown in a view then you must use a delegate. This makes the modifications more flexible since you can have different models applying the same modifications to the views.
Considering the above, the solution is:
import sys
from PyQt5.QtCore import QDir
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery
from PyQt5.QtWidgets import (
QApplication,
QFileDialog,
QHBoxLayout,
QListView,
QMainWindow,
QPushButton,
QStyledItemDelegate,
QVBoxLayout,
QWidget,
)
def create_connection():
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("medias.sqlite")
if not db.open():
print(db.lastError().text())
return False
q = QSqlQuery()
if not q.exec(
"""
CREATE TABLE IF NOT EXISTS fichiers (
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
path VARCHAR(300) NOT NULL
)
"""
):
print(q.lastError().text())
return False
print(db.tables())
return True
class StyledItemDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.text = f"{index.row() + 1} - {index.data()}"
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.play_list = []
self.setGeometry(900, 180, 800, 600)
self.setWindowTitle("Media Display")
self.model = QSqlTableModel()
self.model.setTable("fichiers")
self.model.select()
self.listview = QListView()
delegate = StyledItemDelegate(self.listview)
self.listview.setItemDelegate(delegate)
self.listview.setModel(self.model)
self.listview.setModelColumn(1)
self.init_ui()
def addImage(self):
fichier_base, _ = QFileDialog.getOpenFileName(
self, "select video", QDir.homePath(), "Images (*.png *.xpm *.jpg *.jpeg)"
)
if fichier_base:
rec = self.model.record()
rec.setValue("path", fichier_base)
self.model.insertRecord(-1, rec)
self.model.select()
def clearDb(self):
query = QSqlQuery()
query.exec("DELETE FROM fichiers")
self.model.select()
def init_ui(self):
self.add_img_btn = QPushButton("Add image ")
self.add_img_btn.setFixedWidth(150)
self.add_img_btn.clicked.connect(self.addImage)
self.clear_db_btn = QPushButton("clear DB")
self.clear_db_btn.setFixedWidth(150)
self.clear_db_btn.clicked.connect(self.clearDb)
self.refresh_btn = QPushButton("refresh")
self.refresh_btn.setFixedWidth(150)
group_btns = QHBoxLayout()
main_app = QVBoxLayout()
main_app.addWidget(self.listview)
main_app.addLayout(group_btns)
group_btns.addWidget(self.add_img_btn)
group_btns.addWidget(self.clear_db_btn)
group_btns.addWidget(self.refresh_btn)
widget = QWidget()
vboxlay = QHBoxLayout(widget)
vboxlay.addLayout(main_app)
self.setCentralWidget(widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
window = MainWindow()
window.setStyleSheet("background-color:#fff;")
window.show()
sys.exit(app.exec_())
Related
I'm trying to reorder this QSqlTableModel from a QListView but it seems impossible i tried every thing i found on the internet ( offical documentation, examples forums blogs ) but Nothing happend,i activated the moving action and override flags method mimeData method, honnestly i don't know if i did it correctly. the drag action is working but the problem is on dragging i think.
Here is my advanced programm
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
import sys, os
def create_connection():
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("medias.sqlite")
if not db.open():
print(db.lastError().text())
return False
q = QSqlQuery()
if not q.exec(
"""
CREATE TABLE IF NOT EXISTS fichiers (
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
path VARCHAR(300) NOT NULL
)
"""
):
print(q.lastError().text())
return False
print(db.tables())
return True
class listModel(QSqlTableModel):
def __init__(self,*args, **kwargs):
super(listModel, self).__init__(*args, **kwargs)
self.setEditStrategy(QSqlTableModel.OnFieldChange)
print('mimetype', self.mimeTypes())
self.setTable("fichiers")
self.select()
def flags(self, index):
return Qt.ItemIsEditable | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDropEnabled
def ajouter(self, fichier):
rec = self.record()
rec.setValue("path", fichier)
self.insertRecord(-1, rec)
self.select()
def mimeData(self, indexes):
types = self.mimeTypes()
mimeData = QMimeData()
print('mimedata', mimeData)
encodedData = QByteArray()
stream = QDataStream(encodedData, QIODevice.WriteOnly)
for index in indexes:
if not index.isValid():
continue
if index.isValid():
node = index.internalPointer()
text = self.data(index, Qt.DisplayRole)
mimeData.setData(types[0], encodedData)
return mimeData
def supportedDragActions(self):
return Qt.MoveAction
def supportedDropActions(self):
return Qt.MoveAction
def dropMimeData(self, data, action, row, column, parent):
if action == Qt.MoveAction:
print ("Moving")
self.insertRows(row, 1, QModelIndex())
idx = self.index(row, 0, QModelIndex())
self.setData(idx, self.mimeTypes())
return True
if row != -1:
beginRow = row
elif parent.isValid():
beginRow = parent.row()
else:
beginRow = rowCount(QModelIndex())
class StyledItemDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
data = str(index.data())
text = data.split('/')[-1]
option.text = f"{index.row() + 1} - {text}"
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(900, 180, 800, 600)
self.setWindowTitle("Media Display")
self.setWindowIcon(QIcon('favicon.png'))
self.model = listModel()
self.listview = QListView()
delegate = StyledItemDelegate(self.listview)
self.listview.setItemDelegate(delegate)
self.listview.setModel(self.model)
self.listview.setModelColumn(1)
self.listview.setAcceptDrops(True)
self.listview.setDragEnabled(True)
self.listview.setDragDropOverwriteMode(True)
self.listview.viewport().setAcceptDrops(True)
# self.listview.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listview.setDropIndicatorShown(True)
self.listview.setDragDropMode(QAbstractItemView.InternalMove)
self.init_ui()
def addImage(self):
fichier_base, _ = QFileDialog.getOpenFileName(
self, "select video", QDir.homePath(), "Images (*.png *.xpm *.jpg *.jpeg)"
)
if fichier_base:
self.model.ajouter(fichier_base)
def clearDb(self):
query = QSqlQuery()
query.exec("DELETE FROM fichiers")
self.model.select()
def init_ui(self):
self.add_img_btn = QPushButton("Add image ")
self.add_img_btn.setFixedWidth(100)
self.add_img_btn.clicked.connect(self.addImage)
self.clear_db_btn = QPushButton("clear DB")
self.clear_db_btn.setFixedWidth(100)
self.clear_db_btn.clicked.connect(self.clearDb)
group_btns = QHBoxLayout()
main_app = QVBoxLayout()
main_app.addWidget(self.listview)
main_app.addLayout(group_btns)
group_btns.addWidget(self.add_img_btn)
group_btns.addWidget(self.clear_db_btn)
widget = QWidget()
vboxlay = QHBoxLayout(widget)
vboxlay.addLayout(main_app)
self.setCentralWidget(widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
window = MainWindow()
window.setStyleSheet("background-color:#fff;")
window.show()
sys.exit(app.exec_())
I hate to be "that guy" but... what do you actually expect to be able to do, here?
A QSQLTableModel is a representation of an SQL table, which will have its specific ordering. You are able to sort the records in different ways (with .sort() or, if you need anything more complex, by piping it through a QSortFilterProxyModel). Implementing an arbitrary ordering where you can drag and drop elements in different places, though, is not something I would expect to be able to do with an SQL table. If you really need something like that, you would probably have to implement your own custom model, on which you "load" the sql-fetched data but which you then manage on your own. There you can implement your own ordering scheme, which you can manipulate from the view.
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_())
I have main class with sub-classes as tabs.
I have GUI like this:
QApp.py - Mainwindow
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt5 import QtGui, QtWidgets
from QFilesTab import Files
from QWebservicesTab import Webservices
import qdarkstyle
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.task_bar()
self.tab_layout()
self.graph_elements()
self.center()
def task_bar(self):
### actions on meenubar
exitAct = QtWidgets.QAction('&Exit', self, shortcut='Ctrl+Q', statusTip='Exit application')
exitAct.triggered.connect(self.close)
moreinfo = QtWidgets.QAction('&Help', self, statusTip='More information')
moreinfo.triggered.connect(self.information)
### menubar
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAct)
fileMenu = menubar.addMenu('&Help')
fileMenu.addAction(moreinfo)
def graph_elements(self):
### basic geometry and color
self.setWindowTitle('Villain')
self.setWindowIcon(QtGui.QIcon('dc1.png'))
self.setStyleSheet((qdarkstyle.load_stylesheet_pyqt5()))
def tab_layout(self):
self.tabwidget = QtWidgets.QTabWidget()
self.tabwidget.addTab(Files(), 'Files Import') ### ADDED SUB_CLASS AS QTABWIDGET
self.tabwidget.addTab(Webservices(), 'Webservice')
self.setCentralWidget(self.tabwidget)
### center main window
def center(self):
qr = self.frameGeometry()
cp = QtWidgets.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def information(self):
QtWidgets.QMessageBox.information(self,'Information','Version: 2.0\n'\
'Please, contact karol.chojnowski#digitalcaregroup.com for comments and suggestions\n\n'\
'Digital Care - Data Processing Team')
This is my subclass, which i have as 'Files Import' tab on screen. I have to use credentials to connect with sql. So i ll try get it from my __main__
QFilesTab.py - subclass as qtabwidget
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
from PyQt5 import QtGui, QtWidgets, QtCore
import pyodbc
class Files(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout_init()
def layout_init(self):
operator = ['TMobile', 'PLK', 'Play', 'Orange']
variant = ['Select variant', 'Numer usługi/polisy', 'IMEI', 'PESEL', 'NIP',
'REGON', 'Nazwisko', 'Nazwa firmy']
###partners
self.pvbox = QtWidgets.QVBoxLayout()
self.buttongroup = QtWidgets.QButtonGroup(self)
for elements, forms in enumerate(operator):
element = str(forms)
self.partners = QtWidgets.QRadioButton(element)
self.buttongroup.addButton(self.partners, )
self.pvbox.addWidget(self.partners,)
self.buttongroup.buttonClicked.connect(self.on_itemSelected)
self.buttongroup.buttonClicked['int'].connect(self.on_itemSelected)
###variants
self.variants = QtWidgets.QComboBox()
for elements, forms in enumerate(variant):
element = str(forms)
self.variants.addItem(element)
self.variants.model().item(0).setEnabled(False)
self.variants.activated.connect(self.update_textbox)
self.textbox = QtWidgets.QLineEdit()
self.tablewidget = QtWidgets.QTableWidget()
self.tablewidget.setColumnCount(5)
self.tablewidget.setHorizontalHeaderLabels(['FileNameOriginal', 'OrderItemCode', 'Imported','InfoCode', 'Row'])
self.tablewidget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.tablewidget.horizontalHeader().setStretchLastSection(True)
self.tablewidget.resizeColumnsToContents()
self.tablewidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.tablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.pb = QtWidgets.QPushButton(self.tr('Run process'))
self.pb.setDisabled(True)
self.textbox.textChanged.connect(self.disableButton)
self.pb.clicked.connect(self.on_clicked_pb)
self.clearbutton = QtWidgets.QPushButton(self.tr('Clear all'))
self.clearbutton.setDisabled(True)
self.clearbutton.clicked.connect(self.on_clicked_clear)
vgroupbox = QtWidgets.QGroupBox('Options')
pgroupbox = QtWidgets.QGroupBox('Partner')
mainpanel = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight)
variantpanel = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.TopToBottom)
variantpanel.addWidget(self.variants)
variantpanel.addWidget(self.textbox)
variantpanel.addWidget(self.pb)
variantpanel.addWidget(self.clearbutton)
mainpanel.addWidget(pgroupbox)
mainpanel.addWidget(vgroupbox)
vgroupbox.setLayout(variantpanel)
test = QtWidgets.QVBoxLayout(self)
test.addLayout(self.pvbox)
pgroupbox.setLayout(test)
grid = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.TopToBottom, self)
grid.addLayout(mainpanel)
grid.addWidget(self.tablewidget)
self.setLayout(grid)
def setCredentials(self, credentials): ################################
self._credentials = credentials
def update_textbox(self, text):
self.textbox.clear()
textline = self.variants.itemText(text)
self.textbox.setPlaceholderText(textline)
if textline == 'IMEI':
regexp = QtCore.QRegExp('^(?=.{0,16}$)(0\d+|[1-9][0-9]+)$')
elif textline == 'PESEL':
regexp = QtCore.QRegExp('^(?=.{0,11}$)(0\d+|[1-9][0-9]+)$')
elif textline == 'NIP':
regexp = QtCore.QRegExp('^(?=.{0,10}$)(0\d+|[1-9][0-9]+)$')
elif textline == 'REGON':
regexp = QtCore.QRegExp('^(?=.{0,9}$)(0\d+|[1-9][0-9]+)$')
elif textline == 'Nazwisko':
regexp = QtCore.QRegExp('[A-Za-zżźćńółęąśŻŹĆĄŚĘŁÓŃ]*')
else:
regexp = None
self.textbox.setValidator(QtGui.QRegExpValidator(regexp))
#QtCore.pyqtSlot(QtWidgets.QAbstractButton)
#QtCore.pyqtSlot(int)
def on_itemSelected(self, index):
if isinstance(index, QtWidgets.QAbstractButton):
self.base = None
element = '{}'.format(index.text())
if element == 'Play':
self.base = 'W2_FileImportWorkerP4'
elif element == 'TMobile':
self.base= 'W2_FileImportWorkerTmobileFIX'
elif element == 'Orange':
self.base = 'W2_FileImportWorkerOCP'
elif element == 'PLK':
self.base = 'W2_FileImportWorkerPLK'
return self.base
elif isinstance(index, int):
pass
#QtCore.pyqtSlot()
def disableButton(self):
val = bool(self.textbox.text())
self.pb.setDisabled(not val)
self.clearbutton.setDisabled(not val)
#QtCore.pyqtSlot()
def disablesql(self):
self.textbox.setDisabled(True)
self.pb.setDisabled(True)
self.clearbutton.setDisabled(True)
#QtCore.pyqtSlot()
def enablesql(self):
self.textbox.setDisabled(False)
self.pb.setDisabled(False)
self.clearbutton.setDisabled(False)
### run process button
#QtCore.pyqtSlot()
def on_clicked_pb(self):
if self.textbox.text():
threading.Thread(target=self.sql_query, daemon=True).start()
### clear all
#QtCore.pyqtSlot()
def on_clicked_clear(self):
if self.textbox.text():
self.textbox.clear()
self.tablewidget.setRowCount(0)
self.tablewidget.setColumnWidth(3, 200)
def table_performance(self):
self.tablewidget.resizeColumnsToContents()
self.tablewidget.setColumnWidth(4, 2500)
self.tablewidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
#QtCore.pyqtSlot(str, str)
def show_warning(self, title, msg):
QtWidgets.QMessageBox.information(self, title, msg)
#QtCore.pyqtSlot(str, str)
def show_ok(self, title, msg):
QtWidgets.QMessageBox.information(self, title ,msg)
#QtCore.pyqtSlot()
def clear_items(self):
self.tablewidget.setRowCount(0)
#QtCore.pyqtSlot(int, int, str)
def add_item(self, row, column, val):
if row >= self.tablewidget.rowCount():
self.tablewidget.insertRow(self.tablewidget.rowCount())
newitem = QtWidgets.QTableWidgetItem(val)
self.tablewidget.setItem(row, column, newitem)
#QtCore.pyqtSlot()
def sort_items(self):
self.tablewidget.sortItems(0, order=QtCore.Qt.DescendingOrder)
def sql_query(self):
ser = '10.96.5.17\dqinstance'
print(self.credentials) #check
username, pwd = self._credentials ############################
imei = '%' + self.textbox.text() + '%'
self.clear_items()
try:
self.disablesql()
connection = pyodbc.connect(driver='{SQL Server}', server=ser,
user=username, password=pwd)
if self.base == 'W2_FileImportWorkerTmobileFIX':
cursor = connection.cursor()
res = cursor.execute('''
SELECT FI.FileNameOriginal,
FI.OrderItemCode,
FIR.Imported,
FIRI.InfoCode,
FR.Row
FROM BIDQ_W2_DB.dbo.[FileImports] AS FI
JOIN BIDQ_W2_DB.dbo.[FileImportRows] AS FIR ON FI.Id = FIR.FileImportId
JOIN BIDQ_W2_DB.dbo.[FileRows] AS FR ON FIR.RowId = FR.RowId
LEFT JOIN BIDQ_W2_DB.dbo.FileImportRowInfoes AS FIRI ON FR.RowId = FIRI.RowId
WHERE (FI.WorkerCode = ? or FI.WorkerCode = ?) and FR.Row LIKE ? ''',
(self.base, self.base, imei))
elif self.base == 'All':
cursor = connection.cursor()
res = cursor.execute(''' SELECT FI.FileNameOriginal,
FI.OrderItemCode,
FIR.Imported,
FIRI.InfoCode,
FR.Row
FROM BIDQ_W2_DB.dbo.[FileImports] AS FI
JOIN BIDQ_W2_DB.dbo.[FileImportRows] AS FIR ON FI.Id = FIR.FileImportId
JOIN BIDQ_W2_DB.dbo.[FileRows] AS FR ON FIR.RowId = FR.RowId
LEFT JOIN BIDQ_W2_DB.dbo.FileImportRowInfoes AS FIRI ON FR.RowId = FIRI.RowId
WHERE FR.Row LIKE ? ''',(imei))
else:
cursor = connection.cursor()
res = cursor.execute('''
SELECT FI.FileNameOriginal,
FI.OrderItemCode,
FIR.Imported,
FIRI.InfoCode,
FR.Row
FROM BIDQ_W2_DB.dbo.[FileImports] AS FI
JOIN BIDQ_W2_DB.dbo.[FileImportRows] AS FIR ON FI.Id = FIR.FileImportId
JOIN BIDQ_W2_DB.dbo.[FileRows] AS FR ON FIR.RowId = FR.RowId
LEFT JOIN BIDQ_W2_DB.dbo.FileImportRowInfoes AS FIRI ON FR.RowId = FIRI.RowId
WHERE FI.WorkerCode = ? and FR.Row LIKE ? ''', (self.base, imei))
if not cursor.rowcount:
QtCore.QMetaObject.invokeMethod(self, 'show_warning',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, 'IMEI'), QtCore.Q_ARG(str, 'No items found'))
else:
QtCore.QMetaObject.invokeMethod(self, 'clear_items', QtCore.Qt.QueuedConnection)
QtCore.QThread.msleep(10)
for row, form in enumerate(res):
for column, item in enumerate(form):
QtCore.QMetaObject.invokeMethod(self, 'add_item',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(int, row), QtCore.Q_ARG(int, column),
QtCore.Q_ARG(str, str(item)))
QtCore.QThread.msleep(10)
QtCore.QMetaObject.invokeMethod(self, 'sort_items', QtCore.Qt.QueuedConnection)
self.table_performance()
QtCore.QMetaObject.invokeMethod(self, 'show_ok',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, 'Done'), QtCore.Q_ARG(str, 'Items found'))
cursor.close()
except:
QtCore.QMetaObject.invokeMethod(self, 'show_warning',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, 'Error'), QtCore.Q_ARG(str, 'Something went wrong\n\n' \
'Contact karol.chojnowski#digitalcaregroup.com'))
self.enablesql()
When i try get credentials from main - got error like this :
Im trying to pass credentials in __main__ to Files(). What is wrong? Before when I had only one main class without tabs it worked.... What is the best way to pass credentials in this case? Im asking because im goin to use these credentials in other tabs. Should I create subclass only for credentials?
# -- coding: utf-8 --
import sys
from PyQt5 import QtWidgets,QtGui
from QLogin import LoginDialog
from QApp import MainWindow
from QFilesTab import Files
import os
def resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath('.'), relative_path)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
login = LoginDialog()
login.setWindowIcon(QtGui.QIcon(resource_path('dc1.png')))
if login.exec_() != QtWidgets.QDialog.Accepted:
sys.exit(-1)
files = Files()
window = MainWindow()
window.setWindowIcon(QtGui.QIcon(resource_path('dc1.png')))
window.setGeometry(500, 150, 800, 500)
files.setCredentials(login.credentials()) ###############################
window.show()
sys.exit(app.exec_())
In code i put multiple ################# when i have problematic places.
Edited:
LoginDialog
# -- coding: utf-8 --
from PyQt5.QtWidgets import QLineEdit,QDialogButtonBox,QFormLayout,QDialog,QMessageBox
from PyQt5 import QtWidgets,QtCore
import qdarkstyle
import pyodbc
class LoginDialog(QDialog):
def __init__(self, parent=None):
super(LoginDialog,self).__init__(parent)
self.init_ui()
def init_ui(self):
### delete question mark
self.setWindowFlags(self.windowFlags()
^ QtCore.Qt.WindowContextHelpButtonHint)
### login & password fields
self.username = QLineEdit(self)
self.password = QLineEdit(self)
self.password.setEchoMode(QLineEdit.Password)
loginLayout = QFormLayout()
loginLayout.addRow('Username', self.username)
loginLayout.addRow('Password', self.password)
self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttons.accepted.connect(self.control)
self.buttons.rejected.connect(self.reject)
layout = QtWidgets.QVBoxLayout(self)
layout.addLayout(loginLayout)
layout.addWidget(self.buttons)
self.setLayout(layout)
### set window title & stylesheet
self.setWindowTitle('Villain - 10.96.6.14 ')
#self.setWindowIcon(QtGui.QIcon('dc1.png'))
self.setStyleSheet((qdarkstyle.load_stylesheet_pyqt5()))
###lock resize
self.setSizeGripEnabled(False)
self.setFixedSize(self.sizeHint())
def credentials(self):
return self.username.text(), self.password.text()
###log by usins sql credentials
def control(self):
ser = '10.96.5.17\dqinstance'
login = self.username.text()
pwd = self.password.text()
try:
self.connection = pyodbc.connect(driver='{SQL Server}', server=ser,
user=login, password=pwd)
cursor = self.connection.cursor()
cursor.close()
self.accept()
except:
QMessageBox.warning(self, 'Error', 'Wrong username or password! \n\n'
'Please use the SQL Server credentials ')
This is how I solved this:
In Main.py i have passed argument from LoginDIalog class to MainWindow class.
window.tab_layout(login.credentials())
In QApp.py i have edited method tab_layout:
def tab_layout(self, credentials):
self.tabwidget = QtWidgets.QTabWidget()
self.tabwidget.addTab(Files(credentials), 'Files Import')
self.tabwidget.addTab(Webservices(), 'Webservice')
self.setCentralWidget(self.tabwidget)
In QFilesTab.py i have added to Files class constuctor self._credentials = credentials and deleted method setCredentials. Now, my sql_query method is using credentials from constuctor.
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()