How to iterate over QSqlTableModel and export to .csv - python

I'm trying to export data from SQlite3 database to .csv, using pyqt5, QSqlTableModel and csv. I can export all the data from my database, I'm trying to export only the selected rows from QSqlTableModel, I really liked to mess around QSqlTableModel class, the write/read interaction is really amazing.
I got to this stage by following a couple of examples from others users.
def on_Click(self):
with open("cadastro.csv", 'w', newline='') as csvfile:
csvwriter = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
if self.tableView.selectionModel().hasSelection():
names = []
rows = self.tableView.selectionModel().selectedIndexes()
for i in rows:
name = self.tableView.model().index(i.row(), i.column()).data()
names.append(name)
print(name)
csvwriter.writerow(
['id', 'instituicao', 'crianca', 'data_de_nascimento', 'manequim', 'calcado', 'responsavel'])
csvwriter.writerow(names)
# csvwriter.writerows(map(lambda x: [x], names))
# print(i.row())
else:
print('No row selected!')
So I'm trying to select rows and save that selection on a csv file like that.
enter image description here
The result is:
enter image description here
and I need something like that :
enter image description here
Also here is the rest of the code.
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.db = QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("bolsa_natal.db")
self.db.open()
self.model = QSqlTableModel()
self.initializedModel()
self.tableView = QTableView()
# turn off row numbers
self.tableView.verticalHeader().setVisible(False)
# turn off horizontal headers
# self.tableView.horizontalHeader().setVisible(False)
self.tableView.resizeColumnsToContents()
self.tableView.resizeRowsToContents()
self.tableView.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
self.source_model = self.model
# self.initializedModel()
self.proxy_model = QSortFilterProxyModel(self.source_model)
self.searchcommands = QLineEdit("")
self.searchcommands.setObjectName(u"searchcommands")
self.searchcommands.setAlignment(Qt.AlignLeft)
self.buttonCadastro = QPushButton('to .csv', self)
self.buttonCadastro.clicked.connect(self.exportCadastro)
self.proxy_model.setSourceModel(self.source_model)
self.tableView.setModel(self.proxy_model)
# hide columns from the maintableview
# self.tableView.hideColumn(0)
# self.tableView.hideColumn(3)
# self.tableView.hideColumn(4)
# self.tableView.hideColumn(5)
# self.tableView.hideColumn(6)
# self.tableView.hideColumn(7)
self.proxy_model.setFilterRegExp(QRegExp(self.searchcommands.text(), Qt.CaseInsensitive,
QRegExp.FixedString))
# search all columns
self.proxy_model.setFilterKeyColumn(-1)
# enable sorting by columns
self.tableView.setSortingEnabled(True)
# set editing disabled for my use
# self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.tableView.setWordWrap(True)
self.layout = QVBoxLayout()
self.command_description = QLabel()
self.command_description.setWordWrap(True)
# self.command_requires_label = QLabel("Command Requires:")
# self.command_requires_label.setWordWrap(True)
hLayout = QHBoxLayout()
# hLayout.addWidget(self.command_requires_label)
self.layout.addWidget(self.tableView)
self.layout.addWidget(self.searchcommands)
self.layout.addWidget(self.buttonCadastro)
self.layout.addWidget(self.command_description)
self.layout.addLayout(hLayout)
self.setLayout(self.layout)
self.resize(730, 600)
self.searchcommands.textChanged.connect(self.searchcommands.update)
self.searchcommands.textChanged.connect(self.proxy_model.setFilterRegExp)
self.searchcommands.setText("")
self.searchcommands.setPlaceholderText(u"buscar")
# self.tableView.clicked.connect(self.listclicked)
# self.startDate.dateChanged.connect(self.startDate.update)
# self.startDate.dateChanged.connect(self.FilterBetweenDates)
# self.endDate.dateChanged.connect(self.endDate.update)
# self.endDate.dateChanged.connect(self.FilterBetweenDates)
# Save Button
def exportCadastro(self):
with open('cadastro.csv', 'w') as stream: # 'w'
writer = csv.writer(stream, lineterminator='\n')
for rowNumber in range(self.model.rowCount()):
fields = [
self.model.data(
# self.tableView.selectionModel().selectedRows()
self.model.index(rowNumber, columnNumber),
Qt.DisplayRole
)
for columnNumber in range(self.model.columnCount())
]
print(fields)
writer.writerow(fields)
def initializedModel(self):
self.model.setTable("cadastro")
self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
# self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)
self.model.select()
self.model.setHeaderData(0, Qt.Horizontal, "ID")
self.model.setHeaderData(1, Qt.Horizontal, "instituicao")
self.model.setHeaderData(2, Qt.Horizontal, "crianca")
self.model.setHeaderData(3, Qt.Horizontal, "data_de_nascimento")
self.model.setHeaderData(4, Qt.Horizontal, "manequim")
self.model.setHeaderData(5, Qt.Horizontal, "calcado")
self.model.setHeaderData(6, Qt.Horizontal, "responsavel")
def onAddRow(self):
self.model.insertRows(self.model.rowCount(), 1)
self.model.submit()
def onDeleteRow(self):
self.model.removeRow(self.tableView.currentIndex().row())
self.model.submit()
self.model.select()
def closeEvent(self, event):
self.db.close()
# def listclicked(self, index):
# row = index.row()
# cmd = self.proxy_model.data(self.proxy_model.index(row, 3))
# cmd_requires = self.proxy_model.data(self.proxy_model.index(row, 4))
# cmd_description = self.proxy_model.data(self.proxy_model.index(row, 5))
# print(cmd_description)
# self.command_description.setText(cmd_description)
# self.command_requires_label.setText('This command requires being executed via: ' + cmd_requires.upper())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

You have to iterate over the selected rows:
def exportCadastro(self):
with open("cadastro.csv", "w") as stream:
writer = csv.writer(stream, lineterminator="\n")
for row in self.tableView.selectionModel().selectedRows():
fields = []
for col in range(self.tableView.model().columnCount()):
field = self.tableView.model().index(row, col).data()
fields.append(field)
writer.writerow(fields)

Related

Update treemodel in real time PySide

How can I make it so when I click the Randomize button, for the selected treeview items, the treeview updates to show the changes to data, while maintaining the expanding items states and the users selection? Is this accomplished by subclasses the StandardItemModel or ProxyModel class? Help is much appreciated as I'm not sure how to resolve this issue.
It's a very simple example demonstrating the issue. When clicking Randmoize, all it's doing is randomly assigning a new string (name) to each coaches position on the selected Team.
import os
import sys
import random
from PySide2 import QtGui, QtWidgets, QtCore
class Team(object):
def __init__(self, name='', nameA='', nameB='', nameC='', nameD=''):
super(Team, self).__init__()
self.name = name
self.headCoach = nameA
self.assistantCoach = nameB
self.offensiveCoach = nameC
self.defensiveCoach = nameD
def randomize(self):
names = ['doug', 'adam', 'seth', 'emily', 'kevin', 'mike', 'sarah', 'cassy', 'courtney', 'henry']
cnt = len(names)-1
self.headCoach = names[random.randint(0, cnt)]
self.assistantCoach = names[random.randint(0, cnt)]
self.offensiveCoach = names[random.randint(0, cnt)]
self.defensiveCoach = names[random.randint(0, cnt)]
print('TRADED PLAYERS')
TEAMS = [
Team('Cowboys', 'doug', 'adam', 'seth', 'emily'),
Team('Packers'),
Team('Lakers', 'kevin', 'mike', 'sarah', 'cassy'),
Team('Yankees', 'courtney', 'henry'),
Team('Gators'),
]
class MainDialog(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.resize(600,400)
self.button = QtWidgets.QPushButton('Randomize')
self.itemModel = QtGui.QStandardItemModel()
self.proxyModel = QtCore.QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.itemModel)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setDynamicSortFilter(True)
self.proxyModel.setFilterKeyColumn(0)
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.proxyModel)
self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.treeView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.treeView.setAlternatingRowColors(True)
self.treeView.setSortingEnabled(True)
self.treeView.setUniformRowHeights(False)
self.treeView.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.selectionModel = self.treeView.selectionModel()
# layout
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.addWidget(self.treeView)
self.mainLayout.addWidget(self.button)
self.mainWidget = QtWidgets.QWidget()
self.mainWidget.setLayout(self.mainLayout)
self.setCentralWidget(self.mainWidget)
# connections
self.selectionModel.selectionChanged.connect(self.updateControls)
self.button.clicked.connect(self.randomizeTeams)
# begin
self.populateModel()
self.updateControls()
def randomizeTeams(self):
for proxyIndex in self.selectionModel.selectedRows():
sourceIndex = self.proxyModel.mapToSource(proxyIndex)
item = self.itemModel.itemFromIndex(sourceIndex)
team = item.data(QtCore.Qt.UserRole)
team.randomize()
# UPDATE UI...
def updateControls(self):
self.button.setEnabled(self.selectionModel.hasSelection())
def populateModel(self):
self.itemModel.clear()
self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])
# add teams
for ts in TEAMS:
col1 = QtGui.QStandardItem(ts.name)
col1.setData(ts, QtCore.Qt.UserRole)
# add coaches
childCol1 = QtGui.QStandardItem('Head Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.headCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Head Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.assistantCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Offensive Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.offensiveCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Defensive Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.defensiveCoach)
col1.appendRow([childCol1, childCol2])
self.itemModel.appendRow([col1])
self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainDialog()
window.show()
app.exec_()
if __name__ == '__main__':
pass
main()
Your Team class should be a subclass of QStandardItem, which will be the top-level parent in the model. This class should create its own child items (as you are currently doing in the for-loop of populateModel), and its randomize method should directly reset the item-data of those children. This will ensure the changes are immediately reflected in the model.
So - it's really just a matter of taking the code you already have and refactoring it accordingly. For example, something like this should work:
TEAMS = {
'Cowboys': ('doug', 'adam', 'seth', 'emily'),
'Packers': (),
'Lakers': ('kevin', 'mike', 'sarah', 'cassy'),
'Yankees': ('courtney', 'henry'),
'Gators': (),
}
class Team(QtGui.QStandardItem):
def __init__(self, name):
super(Team, self).__init__(name)
for coach in ('Head', 'Assistant', 'Offensive', 'Defensive'):
childCol1 = QtGui.QStandardItem(f'{coach} Coach')
childCol2 = QtGui.QStandardItem()
self.appendRow([childCol1, childCol2])
def populate(self, head='', assistant='', offensive='', defensive=''):
self.child(0, 1).setText(head)
self.child(1, 1).setText(assistant)
self.child(2, 1).setText(offensive)
self.child(3, 1).setText(defensive)
def randomize(self, names):
self.populate(*random.sample(names, 4))
class MainDialog(QtWidgets.QMainWindow):
...
def randomizeTeams(self):
for proxyIndex in self.selectionModel.selectedRows():
sourceIndex = self.proxyModel.mapToSource(proxyIndex)
item = self.itemModel.itemFromIndex(sourceIndex)
if not isinstance(item, Team):
item = item.parent()
item.randomize(self._coaches)
def populateModel(self):
self.itemModel.clear()
self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])
self._coaches = []
# add teams
for name, coaches in TEAMS.items():
team = Team(name)
team.populate(*coaches)
self._coaches.extend(coaches)
self.itemModel.appendRow([team])
self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)

Displaying an multiple excel tables into different tabs (one table per tab)

I'm trying to make a widget with multiple tabs that are occupied with different excel tables per tab and can't figure why I cant populate each tab separately.
About the tables the number of columns remain the same for all Excel tables only the number of rows change.
import sys
from PyQt5 import QtCore , QtWidgets ,QtGui
import pandas as pd
import os
class MyApp(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint().width(), self.sizeHint().height()
self.resize(self.width(), self.height())
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.table = QtWidgets.QTableWidget()
self.tabs = QtWidgets.QTabWidget()
self.table.setSortingEnabled(True)
self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.selectionModel().selectionChanged.connect(self.selected)
# self.add_flight_tab()
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
self.setLayout(layout)
def load_data(self, path_to_xl):
df = pd.read_excel(path_to_xl,sheet_name=0, usecols=["AWB", "Destn.", "SCC", "Agent Name",
"Stated Pcs/Wgt/Vol (kg/CBM)","Booked Flight"])
if df.size == 0:
return
df.fillna('', inplace=True)
self.table.setRowCount(df.shape[0])
self.table.setColumnCount(df.shape[1])
self.table.setHorizontalHeaderLabels(df.columns)
self.table.insertColumn(self.table.columnCount())
self.table.setHorizontalHeaderItem(6 , QtWidgets.QTableWidgetItem("Offload"))
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
for row in df.iterrows():
values = row[1]
for i in range(df.shape[0]):
check_box = QtWidgets.QTableWidgetItem()
check_box.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
check_box.setCheckState(QtCore.Qt.CheckState())
self.table.setItem(i, 6, check_box)
for col_index, value in enumerate(values):
if isinstance(value, (float, int)):
value = '{0:0,.0f}'.format(value)
tableItem = QtWidgets.QTableWidgetItem(str(value))
self.table.setItem(row[0], col_index, tableItem)
def add_flight_tab(self, path_files):
files = os.listdir(path_files)
new_ls = []
for xl, tab_ind in zip(files, range(files.__sizeof__())):
if xl.endswith(".xlsx") or xl.endswith(".XLSX"):
new_ls.append(xl)
self.tabs.insertTab(tab_ind,QtWidgets.QTableWidget(), xl)
self.tabs.setCurrentIndex(tab_ind)
return new_ls
if __name__ == '__main__':
path_xl = "path to excel"
app = QtWidgets.QApplication(sys.argv)
myApp = MyApp()
myApp.show()
# print(myApp.add_flight_tab())
try:
sys.exit(app.exec_())
except SystemExit:
print("Closing window...")
Everything I have tried gives two results(there must be something I'm missing out in here) :
The Widget is populated but the are no other tabs only thing changed is the following lines:
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
layout.addChildWidget(self.table)
For now the names of the tabs is irrelevant , I only need to have number of tabs according to number of Excel files and each tab is filled with table accordingly.

print tableview or model in pyqt5

I'm trying to print a content of a tableview or the model taht running the table view by using the Qprinter and QPrintPreviewDialog but the best that I can get is an empty table like this
this is my code for handle Preview
def handlePreview(self):
dialog = QtPrintSupport.QPrintPreviewDialog()
dialog.setFixedSize(1000,690)
dialog.paintRequested.connect(self.handlePaintRequest)
dialog.exec_()
nd for the handle Print Request
def handlePaintRequest(self, printer):
#printer = QPrinter()
database = QSqlDatabase("QPSQL")
database.setHostName("localhost")
database.setDatabaseName("database")
database.setUserName("username")
database.setPassword("password")
database.open()
self.model_hjd = QSqlTableModel(db=database)
self.model_hjd.setTable('transactions')
date = str(self.dateEdit_10.text())
date_2 = str(self.dateEdit_14.text())
self.model_hjd.select()
filter_ft = "date_d BETWEEN '%s' AND '%s'" % (date, date_2)
self.model_hjd.setFilter(filter_ft)
rows = self.model_hjd.rowCount()
columns = self.model_hjd.columnCount()
print (rows)
print (columns)
self.model_hjd = QtGui.QStandardItemModel(self)
#self.tableView_22.setModel(self.model_hjd)
#self.table.setModel(self.model_hjd)
for row in range(self.model_hjd.rowCount()):
for column in range(self.model_hjd.columnCount()):
myitem = self.model_hjd.item(row,column)
if myitem is None:
item = QtGui.QStandardItem("")
self.model_hjd.setItem(row, column, item)
document = QtGui.QTextDocument()
cursor = QtGui.QTextCursor(document)
#model_hjd = self.tableView_22.model_hjd()
table = cursor.insertTable(rows, columns)
for row in range(rows):
for column in range(table.columns()):
cursor.insertText(self.model_hjd.item(row, column))
cursor.movePosition(QtGui.QTextCursor.NextCell)
document.print_(printer)
Is there any idea or a hit to fix this?
I find a way to make it work, but it need more correction
def handlePaintRequest(self, printer):
database = QSqlDatabase("QPSQL")
database.setHostName("localhost")
database.setDatabaseName("database)
database.setUserName("user")
database.setPassword("password")
database.open()
model_hjd = QSqlTableModel(db=database)
model_hjd.setTable('transactions')
model_hjd.setHeaderData(0, Qt.Horizontal,"id")
model_hjd.setHeaderData(1, Qt.Horizontal,"montant")
model_hjd.setHeaderData(2, Qt.Horizontal,"medecin")
model_hjd.setHeaderData(3, Qt.Horizontal,"patient")
model_hjd.setHeaderData(4, Qt.Horizontal,"acte")
model_hjd.setHeaderData(5, Qt.Horizontal,"date")
model_hjd.setHeaderData(6, Qt.Horizontal,"temps")
model_hjd.setEditStrategy(QSqlTableModel.OnManualSubmit)
model_hjd.removeColumns(7,1)
#model_hjd.removeColumn(5)
date = str(self.dateEdit_10.text())
date_2 = str(self.dateEdit_14.text())
self.tableView_22.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
model_hjd.setSort(6, Qt.DescendingOrder)
self.tableView_22.setModel(model_hjd)
model_hjd.select()
filter_ft = "date_d BETWEEN '%s' AND '%s'" % (date, date_2)
model_hjd.setFilter(filter_ft)
self.model = QtGui.QStandardItemModel(self)
item = QtGui.QStandardItem()
self.model.appendRow(item)
self.model.setData(self.model.index(0, 5), "", 0)
self.tableView_22.resizeColumnsToContents()
self.tableView_22.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
# Selection of columns
self.selectionModel = self.tableView_22.selectionModel()
self.tableView_22.setModel(model_hjd)
document = QTextDocument()
cursor = QTextCursor(document)
tableFormat = QTextTableFormat()
tableFormat.setBorder(0.2)
tableFormat.setBorderStyle(3)
tableFormat.setCellSpacing(0);
tableFormat.setTopMargin(0);
tableFormat.setCellPadding(4)
table = cursor.insertTable(model_hjd.rowCount() + 1, model_hjd.columnCount(), tableFormat)
### get headers
myheaders = []
for i in range(0, model_hjd.columnCount()):
myheader = model_hjd.headerData(i, Qt.Horizontal)
cursor.insertText(myheader)
cursor.movePosition(QTextCursor.NextCell)
for row in range(0, model_hjd.rowCount()):
for col in range(0, model_hjd.columnCount()):
index = model_hjd.index( row, col )
cursor.insertText(str(index.data()))
cursor.movePosition(QTextCursor.NextCell)
document.print_(printer)
the result
###########################################
Is there a way to fix the date and time format?
and add line in to the table

How to properly order columns in a QTreeView based on a list as input

Basically I am displaying a QTreeView with 20 columns. I want the user to re-arrange the columns and push a save button to store the ordering as a list in an ini-file. On starting up the program, I want to re-order the columns based on the settings from the ini-file.
I am storing the original colum orders as list in "list_origin".
The desired order is in "list_custom".
E.g.
list_origin=['From', 'Subject', 'Date']
list_custom=['Date', 'Subject', 'From']
Now the problem is, when I move columns with the model headers moveSection() command, the original indexes are sometimes not correct anymore, because the columns might get inserted in between and thus lose their origin position index.
See example below: pushing the button "Rearrange cols to Date/Subject/From" will create an undesired order of the columns. How to arrange the colums in the desired order, based on the list_custom?
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# file: treeView_FindCol.py
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
def find_col_by_name(tree_obj, col_name: str) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
if model:
for col in range(model.columnCount()):
header = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
if str(header) == col_name:
pos_found = col
return pos_found
def find_col_by_index(tree_obj, col_index: int) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
header = tree_obj.header()
pos_found = header.visualIndex(col_index)
header_txt = model.headerData(pos_found, Qt.Horizontal, Qt.DisplayRole)
return pos_found
class App(QWidget):
FROM, SUBJECT, DATE = range(3)
def __init__(self):
super().__init__()
self.title = 'PyQt5 Treeview Example - pythonspot.com'
self.left = 800
self.top = 200
self.width = 640
self.height = 240
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
pushButton = QPushButton("Rearrange cols to Date/Subject/From")
groupBox = QGroupBox("Inbox")
treeView = QTreeView()
treeView.setRootIsDecorated(False)
treeView.setAlternatingRowColors(True)
pushButton.clicked.connect(lambda: self.rearrange_column_layout(treeView))
dataLayout = QHBoxLayout()
dataLayout.addWidget(treeView)
dataLayout.addWidget(pushButton)
groupBox.setLayout(dataLayout)
model = self.createMailModel(self)
treeView.setModel(model)
self.addMail(model, 'service#github.com', 'Your Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#github.com', 'Github Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#phone.com', 'Your Phone Bill','01/01/2017 04:05 PM')
self.addMail(model, 'service#abc.com', 'aaaYour Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#def.com', 'bbbGithub Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#xyz.com', 'cccYour Phone Bill','01/01/2017 04:05 PM')
mainLayout = QVBoxLayout()
mainLayout.addWidget(groupBox)
self.setLayout(mainLayout)
self.show()
def createMailModel(self,parent):
model = QStandardItemModel(0, 3, parent)
model.setHeaderData(self.FROM, Qt.Horizontal, "From")
model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Subject")
model.setHeaderData(self.DATE, Qt.Horizontal, "Date")
return model
def addMail(self,model, mailFrom, subject, date):
model.insertRow(0)
model.setData(model.index(0, self.FROM), mailFrom)
model.setData(model.index(0, self.SUBJECT), subject)
model.setData(model.index(0, self.DATE), date)
def rearrange_column_layout(self, treeView):
print("restore_column_layout() called.")
list_custom: list = ['Date', 'Subject', 'From']
list_origin: list = []
model = treeView.model()
header = treeView.header()
col_count = model.columnCount()
for col_search_index in range(col_count):
col_found = header.visualIndex(col_search_index)
header_txt = model.headerData(col_search_index, Qt.Horizontal, Qt.DisplayRole)
list_origin.append(header_txt)
print(f"{list_origin=}")
print(f"{list_custom=}")
pos_custom: int = 0
pos_origin_last: int = 0
for item_custom in list_custom:
pos_origin: int = 0
for item_origin in list_origin:
if item_custom == item_origin:
msg_txt = f"moving col '{item_origin}' from {pos_origin} to {pos_custom}."
print(msg_txt)
QMessageBox.information(self, f"{item_origin}", msg_txt)
header.moveSection(pos_origin, pos_custom)
pos_origin_last = pos_origin
pos_origin += 1
pos_custom += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to set the column ordering based on an input list
"list_origin" contains the header names in original order, e.g.
list_origin=['From', 'Subject', 'Date']
"list_custom" is the desired order of the columns in my QTreeView, e.g.
list_custom=['Date', 'Subject', 'From']
Now I am iterating over the custom list, and want to put the columns based on this positions with header moveSection().
So the algorithm basically is:
step1: start with index 0 of the custom list, it's "Date"
step2: get the position of "Date" from origin list, which is 2.
step3: call moveSection(2,0)
Trace output:
moving col 'Date' from 2 to 0.
moving col 'Subject' from 1 to 1.
moving col 'From' from 0 to 2.
But anyway the result is "From"/"Subject"/"Date" (!) and not as desired "Date"/"Subject"/"From".
The logic is to obtain the index of each element at the time of the iteration since the visual order is changing within the loop:
def rearrange_column_layout(self, treeView):
to_list = ["Date", "Subject", "From"]
header = treeView.header()
model = treeView.model()
for i, c in enumerate(to_list[:-1]):
from_list = [
model.headerData(header.logicalIndex(visual_index), Qt.Horizontal)
for visual_index in range(header.count())
]
j = from_list.index(c)
header.moveSection(j, i)

Exceptions not working as expected in PyQt5

I am making a form dialog in PyQt5. The objective of the program is to generate numbers according to a particular format depending on the Part type and store these numbers in an Excel sheet. The excel sheet is named 'Master Part Sheet.xlsx' and stored in the same directory as the script. The name of the excel sheet can be changed in the workbookLayer Class. The number of generated numbers is determined by the entry in the number of part numbers required field
The customer selected in the drop down menu also plays a role in generating a part number(only if it is a Machine Part). A number from 0-99 is stored in a dictionary against each customers name which is accessed while generating corresponding part numbers.
The input fields have to be checked when a user enters a value in them. Every time the value entered is incorrect(not in a certain range/ not in the specified format), an exception is called. The values are checked when the generate Qpushbutton in the form dialog is clicked.
The NTID field must be of the form UUUNUUU where U stands for upper case string and N stands for a Number. It is checked for with regular expressions and must otherwise throw exceptions.
The machine number and line number fields must be in the range 0-99, and must otherwise throw an exception prompting the user to check his/her entries in the message label.
After the entry of data by the user and these checks, the part numbers must be generated and written onto the excel sheet.
If the part type is a standard part, the part numbers are written onto the Standard parts sheet of the workbook. The max number of parts that can exist in this sheet are 10000. The part numbers must be unique.
If the sheet is a machine part, new sheets are created in the workbook where the part numbers are written onto. The sheets are created based on the customer name the machine number and the line number. One sheet can have a maximum of 1000 parts and the numbers generated shouldn't be repeated in the sheet i.e they are unique.
Below is the code
'''import all necessary libraries required for the project'''
import os
import numpy as np
import openpyxl
from openpyxl.workbook import Workbook
from openpyxl import load_workbook
import random
import time
import pickle
import re
class workbookLayer:
'''The constructor for the workbook object'''
def __init__(self, source_file='Master Part Sheet.xlsx'): # Change source_file to get different sheet as source
self.source_file = source_file
self.part_type = None
self.line_number = None
self.machine_number = None
self.customer_name = None
self.ntid = None
self.get_sheet_name()
'''Get the path of the Excel workbook and load the workbook onto the object'''
def get_sheet_name(self):
self.file_path = os.path.realpath(self.source_file)
self.wb = load_workbook(self.file_path)
self.wb.get_sheet_names()
return self.file_path, self.wb
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QMessageBox)
import sys
'''The Pop up Box for addition of a new customer'''
class DialogCustomerAdd(QDialog):
NumGridRows = 3
NumButtons = 4
def __init__(self):
super(DialogCustomerAdd, self).__init__()
self.createFormGroupBox()
self.ok_button=QPushButton("OK")
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.ok_button)
self.ok_button.clicked.connect(self.ok_button_action)
self.setLayout(mainLayout)
self.setWindowTitle("Add New Customer")
def ok_button_action(self):
global CUSTOMER_NAME
global CUSTOMER_ID
CUSTOMER_NAME = self.customer_name_text.text()
CUSTOMER_ID = self.customer_id_text.text()
self.close()
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Customer Details")
layout = QFormLayout()
self.customer_name_label = QLabel("Name of the Customer")
self.customer_name_text = QLineEdit()
self.customer_id_label = QLabel("Enter Customer ID")
self.customer_id_text = QLineEdit()
layout.addRow(self.customer_name_label, self.customer_name_text)
layout.addRow(self.customer_id_label, self.customer_id_text)
self.formGroupBox.setLayout(layout)
'''The Pop up Box for deletion of an existing customer'''
class DialogCustomerDelete(QDialog):
NumGridRows = 3
NumButtons = 4
def __init__(self):
super(DialogCustomerDelete, self).__init__()
self.createFormGroupBox()
self.ok_button=QPushButton("OK")
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.ok_button)
self.ok_button.clicked.connect(self.ok_button_action)
self.setLayout(mainLayout)
self.setWindowTitle("Delete Existing Customer")
def ok_button_action(self):
global POP_CUSTOMER
POP_CUSTOMER = self.customer_delete_combobox.currentText()
self.customer_delete_combobox.clear()
self.close()
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Customer Details")
layout = QFormLayout()
self.customer_name_label = QLabel(" Select the Customer to be deleted")
self.customer_delete_combobox = QComboBox()
self.customer_delete_combobox.addItems(CUSTOMER_LIST)
layout.addRow(self.customer_name_label)
layout.addRow(self.customer_delete_combobox)
self.formGroupBox.setLayout(layout)
class DialogMain(QDialog):
NumGridRows = 3
NumButtons = 4
wbl = workbookLayer()
def __init__(self):
super(DialogMain, self).__init__()
self.createFormGroupBox()
generate_button=QPushButton("Generate")
generate_button.clicked.connect(self.generateNumbers)
self.user_message_label = QLabel(" ")
buttonBox = QDialogButtonBox(QDialogButtonBox.Cancel)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.user_message_label)
mainLayout.addWidget(generate_button)
mainLayout.addWidget(self.add_new_customer_button)
mainLayout.addWidget(self.delete_customer_button)
buttonBox.clicked.connect(self.closeIt)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.open_or_create_pickle_file()
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
self.customer_name_combobox.addItems(customer_name)
self.add_new_customer_button.clicked.connect(self.add_customer_window)
self.delete_customer_button.clicked.connect(self.delete_customer_window)
self.setWindowTitle("Part Number Generator ")
global CUSTOMER_LIST
CUSTOMER_LIST = customer_name
def open_or_create_pickle_file(self):
self.customer_dict = dict()
try:
assert open("customer_diction.pickle","rb")
pickle_in = open("customer_diction.pickle","rb")
self.customer_dict = pickle.load(pickle_in)
pickle_in.close()
except FileNotFoundError:
pickle_out = open("customer_dict.pickle","wb")
def add_new_customer_to_pickle(self):
global CUSTOMER_NAME
global CUSTOMER_ID
global CUSTOMER_LIST
self.customer_name_combobox.addItem(CUSTOMER_NAME)
self.customer_dict[CUSTOMER_NAME] = CUSTOMER_ID
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
CUSTOMER_LIST = customer_name
pickle_out = open("customer_diction.pickle","wb")
pickle.dump(self.customer_dict, pickle_out)
pickle_out.close()
def delete_customer_from_pickle(self):
global POP_CUSTOMER
del self.customer_dict[POP_CUSTOMER]
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
global CUSTOMER_LIST
CUSTOMER_LIST = customer_name
self.customer_name_combobox.clear()
self.customer_name_combobox.addItems(customer_name)
pickle_out = open("customer_diction.pickle","wb")
pickle.dump(self.customer_dict, pickle_out)
pickle_out.close()
def add_customer_window(self):
widget = DialogCustomerAdd()
widget.exec_()
self.add_new_customer_to_pickle()
def delete_customer_window(self):
widget = DialogCustomerDelete()
widget.exec_()
self.delete_customer_from_pickle()
global POP_CUSTOMER
self.user_message_label.setText('The customer ' + POP_CUSTOMER + ' has been deleted' )
def closeIt(self):
self.close()
def generateNumbers(self):
self.wbl.line_number = self.line_number_text.text()
self.wbl.machine_number = self.machine_number_text.text()
self.wbl.customer_name =self.customer_name_combobox.currentText()
self.wbl.part_type = self.part_type_combobox.currentText()
self.wbl.ntid = self.ntid_text.text()
try:
self.check_ntid()
except AssertionError:
self.user_message_label.setText('Please Enter User Details Correctly')
try:
if int(self.wbl.machine_number) > 99:
raise ValueError
except ValueError:
self.user_message_label.setText('Please Enter machine number within 100')
try:
if int(self.wbl.line_number) > 99:
raise ValueError
except ValueError:
self.user_message_label.setText('Please Enter Line number within 100')
self.create_sheet_and_check_values()
try:
self.part_number_generator()
except PermissionError:
self.user_message_label.setText('Please Close the excel sheet before using this application')
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Part Details")
layout = QFormLayout()
self.part_type_label = QLabel("Part Type:")
self.part_type_combobox = QComboBox()
self.part_type_combobox.addItems(['Standard Part', 'Machine Part'])
self.customer_name_label = QLabel("Customer:")
self.customer_name_combobox = QComboBox()
self.add_new_customer_button = QPushButton("Add New Customer")
self.delete_customer_button = QPushButton("Delete Existing Customer")
self.ntid_label = QLabel("NTID of the User:")
self.ntid_text = QLineEdit()
self.machine_number_label = QLabel("Machine Number of the Part:")
self.machine_number_text = QLineEdit()
self.line_number_label = QLabel("Line Number of the Part:")
self.line_number_text = QLineEdit()
self.part_numbers_label = QLabel("Number of Part Numbers Required:")
self.part_numbers_spinbox = QSpinBox()
self.part_numbers_spinbox.setRange(1, 999)
layout.addRow(self.part_type_label, self.part_type_combobox)
layout.addRow(self.customer_name_label, self.customer_name_combobox)
layout.addRow(self.ntid_label, self.ntid_text)
layout.addRow(self.machine_number_label, self.machine_number_text)
layout.addRow(self.line_number_label, self.line_number_text)
layout.addRow(self.part_numbers_label, self.part_numbers_spinbox)
self.formGroupBox.setLayout(layout)
def check_ntid(self):
pattern = re.compile("^[A-Z][A-Z][A-Z][0-9][A-Z][A-Z][A-Z]")
assert pattern.match(self.wbl.ntid)
def create_sheet_and_check_values(self):
self.check_ntid()
if self.wbl.part_type == 'Machine Part':
self.prefix = int('3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number)
self.check_and_create_customer_sheet()
else:
self.prefix = 500000
self.add_standard_parts_sheet()
self.dest_filename = self.wbl.file_path
self.ws1 = self.wbl.wb[self.wbl.sheet_name]
self.random_numbers = self.part_numbers_spinbox.value()
if (self.wbl.part_type == 'Machine Part'):
try:
assert self.random_numbers < (1002 -(self.ws1.max_row))
except AssertionError:
self.user_message_label.setText('You have exceeded the range of allowable parts in the machine, you can get only {} more part numbers'.format(1001 -(self.ws1.max_row)))
else:
try:
assert self.random_numbers < (10002 -(self.ws1.max_row))
except AssertionError:
self.user_message_label.setText('You have exceeded the range of allowable parts in this list, you can get only {} more part numbers'.format(10001 -(self.ws1.max_row)))
'''This section of the method contains the logic for the generation of random number parts
according to the required format and also checks for clashes with already existing part numbers'''
def part_number_generator(self):
if (self.wbl.part_type == 'Machine Part'):
part_numbers_list = random.sample(range( self.prefix*1000, self.prefix*1000 + 1000), self.random_numbers)
else:
part_numbers_list = random.sample(range( self.prefix*10000, self.prefix*10000 + 10000), self.random_numbers)
first_column = set([(self.ws1[x][0].value) for x in range(2,self.ws1.max_row + 1 )])
while True:
additional_part_numbers_list = random.sample(range( self.prefix*1000, self.prefix*1000 + 1000), self.random_numbers - len (part_numbers_list))
part_numbers_list = part_numbers_list + additional_part_numbers_list
part_numbers_list = list(set(part_numbers_list) - (first_column))
if len(part_numbers_list) == self.random_numbers:
break
for row in range( self.ws1.max_row + 1, self.ws1.max_row + 1 + len(part_numbers_list)):
'''write the generated part numbers onto the excel sheet corresponding to the customer'''
self.ws1.cell(column = 1 , row = row, value = part_numbers_list.pop())
self.ws1.cell(column = 3 , row = row, value = self.wbl.ntid)
self.wbl.wb.save(self.wbl.file_path)
self.user_message_label.setText(str(self.part_numbers_spinbox.value()) + " Part Numbers have been successfully generated. Please check the sheet " + self.wbl.sheet_name)
def check_and_create_customer_sheet(self):
self.wbl.sheet_name = '3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number +'xxx'
if self.customer_not_exists():
self.wbl.wb.create_sheet(self.wbl.sheet_name)
self.wbl.wb.save(self.wbl.file_path)
'''Check if the customer sheet exists'''
def customer_not_exists(self):
sheet_name = '<Worksheet "'+ '3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number +'xxx' + '">'
return True if sheet_name not in str(list(self.wbl.wb)) else False
'''Check if standard parts sheet exists. If not make a new sheet'''
def add_standard_parts_sheet(self):
sheet_name = '<Worksheet "'+ 'Standard Parts'+ '">'
if sheet_name not in str(list(self.wbl.wb)):
self.wbl.wb.create_sheet('Standard Parts')
self.wbl.wb.save(self.wbl.file_path)
self.wbl.sheet_name = 'Standard Parts'
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = DialogMain()
sys.exit(dialog.exec_())
The generateNumbers() function in the DialogMain() Class is called every time a 'generate' QpushButton is clicked in the QDialog layout. The program checks for correctness of the inputs and generates random numbers according to the required format in an excel sheet through openpyxl. The exceptions for the self.wbl.ntid and PermissionError work as expected. However the exceptions for self.wbl.machine_number and self.wb1.line_number i.e for machine number inputs and line number inputs in the dialog seem to be ignored and the program executes even when the user entry is wrong. Only the exception for NTID behaves as expected and the other exceptions fail to be raised even on wrong entry.
The dialog Box
I have added the entire code used to make it reproducible. Thanks

Categories