Update treemodel in real time PySide - python

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)

Related

How to make custom QStandardItem for QTreeView?

In my project there is a window for editing files. Each file has some properties that the user can change with several checkboxes. Also each file has an icon.
Now it is implemented through QScrollArea in which widgets are added. It looks like this:
Now I want to add folder support for files. I would like to support drag n drop and so on.
I thought it would be a good idea in my case to use QTreeView.
The widget for the files has already been drawn and it suits me completely:
I haven't worked with QT's model/view framework before. after a couple of days of trying to draw something acceptable, I broke my brain. That's what I did:
import sys
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from scanner.models import *
from gui.styles.settings import *
from gui.utils import setup_fonts
class BaseFileItem(QStandardItem):
def __init__(self, parent=None):
super(BaseFileItem, self).__init__(parent)
self.setEditable(False)
class TitleItem(QStandardItem):
file_type = 'title'
class FolderItem(QStandardItem):
file_type = 'folder'
class PreviewItem(BaseFileItem):
file_type = 'preview'
class FileItem(BaseFileItem):
file_type = 'file'
class Model(QStandardItemModel):
def supportedDropActions(self):
return Qt.MoveAction
class Delegate(QStyledItemDelegate):
checkbox_width = 50
checkboxes = [
'main',
'preview',
'use',
]
def sizeHint(self, option, index):
match index.data(1002):
case 'title':
height = 32
case 'folder':
height = 24
case _:
height = 48
return QSize(height, height)
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
match index.data(1002):
case 'title':
item_type = 'label'
case 'folder':
return
case _:
item_type = 'checkbox'
for column, name in enumerate(self.checkboxes):
self.add_checkbox_item(painter, option, column, name, item_type)
def add_checkbox_item(self, painter, option, column, text, item_type):
rect = option.rect
if item_type == 'checkbox':
item = QStyleOptionButton()
ce = QStyle.CE_CheckBox
else:
item = QStyleOptionHeader()
item.text = text
ce = QStyle.CE_HeaderLabel
item.item_type = item_type
x, y = rect.left() + rect.width() - (column + 1) * self.checkbox_width, rect.top()
item.rect = QRect(x, y, self.checkbox_width, rect.height())
QApplication.style().drawControl(ce, item, painter)
class AssetEditFilesView(QWidget):
rows = ['Asset files', 'Previews', 'Candidates']
def __init__(self, files, parent=None):
self.files = files
super(AssetEditFilesView, self).__init__(parent)
self.setContentsMargins(24, 24, 24, 24)
self.model = Model()
self.tree = QTreeView(self)
self.tree.setContentsMargins(4, 4, 4, 4)
self.tree.setItemDelegate(Delegate(self.tree))
self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.tree.setSortingEnabled(True)
self.tree.setDragEnabled(True)
self.tree.setAcceptDrops(True)
self.tree.setDropIndicatorShown(True)
self.tree.header().hide()
self.tree.setModel(self.model)
self.import_data(files)
self.tree.expandAll()
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
def import_data(self, data):
self.model.setRowCount(0)
for group, files in zip(self.rows, self.prepare_files(data)):
if files:
self.add_files_to_group(group, files)
def add_files_to_group(self, group, files):
files = sorted(files, key=lambda x: str(x.data().asset_path))
data = [f.data().asset_path.parts for f in files]
max_len = max([len(x) for x in data])
prepared = [x for x in zip(*[[y[i] if i < len(y) else None for i in range(max_len)] for y in data])]
structure = self.create_structure(prepared, files)
root = self.add_file_row(self.model.invisibleRootItem(), group, TitleItem)
for folder, items in structure.items():
file_model = PreviewItem if group == 'Previews' else FileItem
self.add_items(root, folder, items, file_model=file_model)
def add_items(self, root, folder, items, file_model=FileItem):
if folder == '.':
for item in items:
self.add_file_row(root, item, file_model)
else:
new_root = self.add_file_row(root, folder, FolderItem)
for _folder, _items in items.items():
self.add_items(new_root, _folder, _items, file_model=file_model)
def add_file_row(self, root, item, file_model):
item_name = str(item.filename.name) if hasattr(item, 'filename') else item
item_model = file_model(item_name)
item_model.setData(item, 1001)
item_model.setData(file_model.file_type, 1002)
print(root)
root.appendRow(item_model)
return item_model
def create_structure(self, prepared, files):
structure = {'.': []}
for *parts, file in zip(*prepared, [f.data() for f in files]):
parts = list(filter(lambda x: x is not None, parts))
parent = structure
for part in parts:
if part not in parent:
parent[part] = {'.': []}
parent = parent[part]
parent['.'].append(file)
return structure
def prepare_files(self, data):
groups = [[], [], []]
for file in data:
if type(file) in [PreviewImage, PreviewVideo]:
group = 1
elif isinstance(file, BaseFileModel):
group = 0
else:
group = 2
item = QStandardItem(str(file.filename.name))
item.setData(file)
groups[group].append(item)
return groups
style = f'''
AssetEditFilesView QTreeView {{
{Fonts.normal}
border: 2px;
border-radius: 8px;
background-color: {Colors.asset_edit_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
'''
if __name__ == '__main__':
files = [
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png', asset_path=Path('./huita/huyatina')),
BaseFileModel(filename=r'c:\temp0048.png', asset_path=Path('./huita')),
BaseFileModel(filename=r'c:\temp0127.png', asset_path=Path('./ne_huita')),
BaseFileModel(filename=r'c:\temp0229.png', asset_path=Path('./ne_huita/huiyatina')),
PreviewVideo(filename=r'c:\references.gif', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp321.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp0267.png', asset_path=Path('./previews/generated')),
PreviewImage(filename=r'c:\temp3.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.bin', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.bin.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/2/1')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1')),
]
app = QApplication(sys.argv)
setup_fonts(app)
app.setStyleSheet(style)
view = AssetEditFilesView(files)
view.setGeometry(800, 500, 800, 640)
view.setWindowTitle('QTreeview Example')
view.show()
sys.exit(app.exec())
I have 3 entities:
file (should look like on the 1st screenshot (3 checkboxes and an icon))
folder (must contain a name)
title (in fact the folder only the font should be larger)
how do i implement all entities using 1 QStyledItemDelegate?
Can I use a ready-made custom QWidget as a file entity?
the only thing I managed to do was to implement a more or less working sizeHint, but I have no idea how to add a file widget.
I will be grateful for any help, as now I am close to implementing all this through QScrollArea instead of QTreeView
i need some thing like this:
I also can't figure out how to apply styles to a QStandardItem, QStyledItemDelegate,

How to iterate over QSqlTableModel and export to .csv

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)

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

dynamically generating unique combo box and filling them with two different sources

I want to dynamically generate Label on left & combos on the right in a grid
{'PHOTOSHOP': '6.5', 'NUKE': '7.0v9', 'MAYA': '2014', 'TESTING': '1.28', 'KATANA': '1.7', 'MARI': '4.0'}
{'PHOTOSHOP': '10.5', 'NUKE': '6.3v6', 'MAYA': '2012', 'TESTING': '1.28', 'KATANA': '1.0', 'MARI': '1.0'}
my first problem is creating combos box in a way i can access them later outside the method based on name created at the time of for loop
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QWidget):
"""docstring for MainWindow"""
def __init__(self, dataIn1, dataIn2):
super(MainWindow, self).__init__()
self._dataIn1 = dataIn1
self._dataIn2 = dataIn2
self.buildUI()
def main(self):
self.show()
def buildUI(self):
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setSpacing(10)
self.combo = QtGui.QComboBox('combo')
for index, item in enumerate(self._dataIn1.iteritems()):
# Below line doesn't work which i want to make work
# objective is to assign unique name so i can access-
# them later outside this method
#self.item[0]+"_Combo" = QtGui.QComboBox()
self.gridLayout.addWidget(QtGui.QLabel(item[0]), index, 0)
# once uique combo is created I want to populate them from dataIn1 & dataIn2 lists
self.gridLayout.addWidget(self.combo.addItems([item[-1]]), index, 1)
self.setLayout(self.gridLayout)
self.setWindowTitle('Experiment')
def main():
app = QtGui.QApplication(sys.argv)
smObj = MainWindow(dataIn1, dataIn2)
smObj.main()
app.exec_()
if __name__ == '__main__':
main()
secondly I want to those combo box to be filled in by each Keys value from both dataIn1 and dataIn2 sources..
To dynamically create attributes, you can use setattr:
setattr(self, 'combo%d' % index, combo)
However, it's probably much more flexible to keep the combos in a list (then you can easily iterate over them afterwards).
Your loop should end up looking something like this:
data1 = {
'PHOTOSHOP': '6.5', 'NUKE': '7.0v9', 'MAYA': '2014',
'TESTING': '1.28', 'KATANA': '1.7', 'MARI': '4.0',
}
data2 = {
'PHOTOSHOP': '10.5', 'NUKE': '6.3v6', 'MAYA': '2012',
'TESTING': '1.28', 'KATANA': '1.0', 'MARI': '1.0',
}
self.combos = []
for index, (key, value) in enumerate(data1.items()):
label = QtGui.QLabel(key, self)
combo = QtGui.QComboBox(self)
combo.addItem(value)
combo.addItem(data2[key])
self.combos.append(combo)
# or setattr(self, 'combo%d' % index, combo)
layout.addWidget(label, index, 0)
layout.addWidget(combo, index, 1)

Categories