При нажатии на кнопку «Отмена» удаленный индекс не возвращается, как эту проблему решить?

Есть программа, которая добавляет проект OrCAD. Головной код программы - main.py В программе есть раздел "Редактирование", который изменяет проект, добавляя или удаляя пользователем индексы. В разделе есть кнопка "Отмена", которая удаляет добавленный или добавляет удаленный пользователем индекс. Функция работает только на добавленный, а на удалённый нет. Все осуществляется через RAWData.py . Вот код.

class EditRawData(QWidget):
    signalLogConsole = QtCore.pyqtSignal(QtCore.QVariant)
    def __init__(self, parent=None, Database=None):
        super().__init__(parent, QtCore.Qt.WindowType.Window)
        self.DataBase = Database
        self.__buildInterface()

    def populateTreeFromDatabase(self):
        # Создаем defaultdict для хранения сгруппированных данных
        grouped_data = defaultdict(list)
        # Проходим через каждую строку результата запроса к базе данных
        while self.query.next():
            product1 = self.query.value(0)
            product2 = self.query.value(1)
            product3 = self.query.value(2)
            product4 = self.query.value(3)
            product5 = self.query.value(4)
            product6 = self.query.value(5)
            #
            # Проверяем, есть ли уже product1 в defaultdict, если нет, то добавляем его
            if product1 not in grouped_data:
                grouped_data[product1] = []
            grouped_data[product1].append((product2, product3, product4, product5, product6))

            # Проходим по сгруппированным данным и сортируем продукты в каждой группе с использованием естественной сортировки на основе product2
            for product1, products in grouped_data.items():
                sorted_products = natsorted(products, key=lambda x: x[0], alg=ns.IGNORECASE)
                # Создаем родительский элемент (QStandardItem) для каждого product1 и добавляем его в корень дерева
                parent_item = QStandardItem(product1)
                self.root_item.appendRow(parent_item)
                # Создаем дочерние элементы для каждого значения атрибута продукта и добавляем их в качестве детей родительского элемента
                for product in sorted_products:
                    child_items = [QStandardItem(str(product[i])) for i in range(5)]
                    parent_item.appendRow(child_items)

    def populateTreeFromDatabase(self, key):
        self.root_item.removeRows(0, self.root_item.rowCount())

        if not self.query.exec(
                "SELECT ClassPart, PartReference, ManufacturerPartNumber, Manufacturer, Quantity, Description FROM RAWDATA"):
            print("Ошибка выполнения запроса:", self.query.lastError().text())
            return

        # Создаем defaultdict для хранения сгруппированных данных
        grouped_data = defaultdict(list)

        # Проходим через каждую строку результата запроса к базе данных
        while self.query.next():
            product1 = self.query.value(0)
            product2 = self.query.value(1)
            product3 = self.query.value(2)
            product4 = self.query.value(3)
            product5 = self.query.value(4)
            product6 = self.query.value(5)

            # print(key)
            if key not in grouped_data:
                grouped_data[key] = []

            # "SELECT ClassPart, PartReference, ManufacturerPartNumber, Manufacturer, Quantity, Description

            if key == 'product1':
                j = 0
                temp_value = 'product1'
                self.tree_model.setHorizontalHeaderLabels(["PartReference", "ManufacturerPartNumber", "Manufacturer", "Quantity", "Description"])
                self.root_item = self.tree_model.invisibleRootItem()
                grouped_data[product1].append((product2, product3, product4, product5, product6))
            elif key == 'product2':
                j = 0
                temp_value = 'product2'
                self.tree_model.setHorizontalHeaderLabels(["ClassPart", "ManufacturerPartNumber", "Manufacturer", "Quantity", "Description"])
                self.root_item = self.tree_model.invisibleRootItem()
                grouped_data[product2].append((product1, product3, product4, product5, product6))
            elif key == 'product4':
                j = 1
                temp_value = 'product4'
                self.tree_model.setHorizontalHeaderLabels(["ClassPart", "PartReference", "ManufacturerPartNumber", "Quantity", "Description"])
                self.root_item = self.tree_model.invisibleRootItem()
                grouped_data[product4].append((product1, product2, product3, product5, product6))
            else:
                print("ERROR!!!")

        # Проходим по сгруппированным данным и сортируем продукты в каждой группе с использованием естественной сортировки на основе product2
        for key, products in grouped_data.items():
            sorted_products = natsorted(products, key=lambda x: x[j], alg=ns.IGNORECASE)

            # Создаем родительский элемент (QStandardItem) для каждого ключа и добавляем его в корень дерева
            parent_item = QStandardItem(key)
            self.root_item.appendRow(parent_item)

            # Создаем дочерние элементы для каждого значения атрибута продукта и добавляем их в качестве детей родительского элемента
            for product in sorted_products:
                child_items = [QStandardItem(str(product[i])) for i in range(5)]
                parent_item.appendRow(child_items)

        self.root_item.removeRow(0)

    def update_database(self):
        election_model = self.tree_view.selectionModel()
        selected_indexes = selection_model.selectedIndexes()
        product = []
        parent_index = selected_indexes[0].parent()
        parent_item = self.tree_model.itemFromIndex(parent_index)
        class_part = parent_item.text()
    
        for index in selected_indexes:
            item = self.tree_model.itemFromIndex(index)
            prod_value = item.text()
            print(prod_value)
            product.append(prod_value)
    
            if self.query.exec(f"UPDATE RAWDATA SET PartReference = '{product[0]}', ManufacturerPartNumber = '{product[1]}', Manufacturer = '{product[2]}', Quantity = '{product[3]}', Description  = '{product[4]}' WHERE ClassPart = '{class_part}' AND PartReference = '{prev_indexes[0]}' AND ManufacturerPartNumber = '{prev_indexes[1]}'"):
                print("Data updated successfully")
                for i, value in enumerate(product):
                    item = self.tree_model.itemFromIndex(selected_indexes[i])
                    item.setText(value)
                    self.tree_model.setData(selected_indexes[i], value)
    
                    # Обновление данных в представлении (TreeView)
                    self.tree_view.setModel(self.tree_model)
            else:
                print("Ошибка выполнения запроса:", self.query.lastError().text())

    def update_database(self):
        selection_model = self.tree_view.selectionModel()
        selected_indexes = selection_model.selectedIndexes()
        product = []

        parent_index = selected_indexes[0].parent()
        parent_item = self.tree_model.itemFromIndex(parent_index)
        class_part = parent_item.text()

        for index in selected_indexes:
            item = self.tree_model.itemFromIndex(index)
            prod_value = item.text()
            print(prod_value)
            product.append(prod_value)

        if self.query.exec(f"UPDATE RAWDATA SET PartReference = '{product[0]}', ManufacturerPartNumber = '{product[1]}', Manufacturer = '{product[2]}', Quantity = '{product[3]}', Description  = '{product[4]}' WHERE ClassPart = '{class_part}' AND PartReference = '{prev_indexes[0]}' AND ManufacturerPartNumber = '{prev_indexes[1]}'"):
            print("Data updated successfully")

            # Обновление данных в модели
            for i, value in enumerate(product):
                item = self.tree_model.itemFromIndex(selected_indexes[i])
                item.setText(value)
                self.tree_model.setData(selected_indexes[i], value)

            # Обновление данных в представлении (TreeView)
            self.tree_view.setModel(self.tree_model)
        else:
            print("Ошибка выполнения запроса:", self.query.lastError().text())

    def __buildInterface(self):
        self.setWindowTitle("Просмотр и редактирование данных")
        self.resize(700, 500)
        #=====Кнопки управления============
        self.btnAddRecord = QPushButton("Добавить запись")
        self.btnDelRecord = QPushButton("Удалить запись")
        self.btnClassification = QPushButton("Классификация")
        self.btnGroup = QPushButton("Группировка")
        self.btnCancel = QPushButton("Отмена")
        self.hbox1 = QHBoxLayout()
        self.hbox1.addWidget(self.btnAddRecord)
        self.hbox1.addWidget(self.btnDelRecord)
        self.hbox1.addWidget(self.btnClassification)
        self.hbox1.addWidget(self.btnGroup)
        self.hbox1.addWidget(self.btnCancel)
        self.btnDelRecord.clicked.connect(self.delRecord)
        self.btnAddRecord.clicked.connect(self.addRecord)
        self.btnClassification.clicked.connect(self.Classification)
        self.btnCancel.clicked.connect(self.Cancel)
        self.btnGroup.clicked.connect(self.Group)
        # ====================================
        # переключатели для управления группировкой
        self.hboxRadio = QHBoxLayout()
        self.rbtnNoGroup = QRadioButton("Без группировки")
        self.rbtnTypeGroup = QRadioButton("Группировка по типу")
        self.rbtnPartNumber = QRadioButton("Группировка по наименованию")
        self.rbtnManufacturerGroup = QRadioButton("Группировка по производителю")
        self.hboxRadio.addWidget(self.rbtnNoGroup)
        self.hboxRadio.addWidget(self.rbtnTypeGroup)
        self.hboxRadio.addWidget(self.rbtnPartNumber)
        self.hboxRadio.addWidget(self.rbtnManufacturerGroup)
        self.rbtnNoGroup.setChecked(True)
        self.rbtnNoGroup.toggled.connect(self.Group)
        self.rbtnTypeGroup.toggled.connect(self.Group)
        self.rbtnPartNumber.toggled.connect(self.Group)
        self.rbtnManufacturerGroup.toggled.connect(self.Group)
        #===================================
        #создаем модель
        self.stm = QtSql.QSqlTableModel(self)
        self.stm.setTable("RAWDATA")
        #self.stm.setSort(2, QtCore.Qt.SortOrder.AscendingOrder)
        self.stm.select()

        self.tree_view = QtWidgets.QTreeView()
        self.table_view = QtWidgets.QTableView()
        self.tree_model = QStandardItemModel()
        self.tree_model.setHorizontalHeaderLabels(["PartReference", "ManufacturerPartNumber", "Manufacturer", "Quantity" , "Description"])
        self.root_item = self.tree_model.invisibleRootItem()

        self.upload = QSqlQuery()
        self.query = QSqlQuery()
        # Получение данных из базы данных
        #if not self.query.exec(
        #         "SELECT ClassPart, PartReference, ManufacturerPartNumber, Manufacturer, Quantity, Description FROM RAWDATA"):
        #     print("Ошибка выполнения запроса:", self.query.lastError().text())
        #     return

        self.populateTreeFromDatabase('product1')
        self.tree_view.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
        self.tree_view.setModel(self.tree_model)
        self.selection_model = self.tree_view.selectionModel()
        self.selection_model.selectionChanged.connect(self.on_selection_changed)

        column = 0
        self.table_view.setIndexWidget(self.tree_view.model().index(0, column), self.tree_view)

        self.vbox1 = QVBoxLayout()
        self.vbox1.addLayout(self.hbox1)
        self.vbox1.addLayout(self.hboxRadio)
        self.vbox1.addWidget(self.tree_view)
        self.setLayout(self.vbox1)

    def on_selection_changed(self, selected, deselected):
        # Очистим список при смене выделения
        prev_indexes.clear()

        # Получим список выбранных индексов и сохраним их
        for index in selected.indexes():
            text = index.data()  # Получаем текстовое значение из ячейки
            # print(text)
            prev_indexes.append(text)  # Добавляем текстовое значение в список
        # print(prev_indexes)



    def keyPressEvent(self, event):
        # попробовать перебиндить на ctrl + s или f5
        if event.key() == QtCore.Qt.Key.Key_F5:
            self.save_button_event()
        # комбинация shift + левый клик
        if event.key() == QtCore.Qt.Key.Key_Shift:
            self.tree_view.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
        super().keyPressEvent(event)

    def save_button_event(self):
        self.update_database()
        print("Save!")

    def keyReleaseEvent(self, event):
        if event.key() == QtCore.Qt.Key.Key_Shift:
            self.tree_view.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
        super().keyReleaseEvent(event)

    def delRecord(self):
        selection_model = self.tree_view.selectionModel()
        selected_indexes = selection_model.selectedIndexes()
        rows_to_remove = []
        values_to_remove = []
        parent_values = []

        for index in selected_indexes:
            row = index.row()
            if row not in rows_to_remove:
                rows_to_remove.append(row)

            item = self.tree_model.itemFromIndex(index)
            value = item.text()
            if index.column() < 2:
                values_to_remove.append(value)
                print(value)

        for row in sorted(rows_to_remove, reverse=True):
            parent_index = selected_indexes[0].parent()

            parent_item = self.tree_model.itemFromIndex(parent_index)
            parent_text = parent_item.text()
            parent_values.append(parent_text)

            self.tree_model.removeRow(row, parent_index)
            print(parent_text)
            print(row)

        x = 0
        for i in range((len(values_to_remove))//2):
            print(temp_value)
            if temp_value == 'product1':
               if self.query.exec(f"DELETE FROM RAWDATA WHERE PartReference = '{values_to_remove[x]}' AND ManufacturerPartNumber = '{values_to_remove[x+1]}'"):
                   print("Data updated successfully")
               else:
                   print("Ошибка выполнения запроса:", self.query.lastError().text())
            elif temp_value == 'product2':
                if self.query.exec(f"DELETE FROM RAWDATA WHERE ClassPart = '{values_to_remove[x]}' AND ManufacturerPartNumber = '{values_to_remove[x + 1]}' AND PartReference = '{parent_values[(x//2)]}'"):
                    print("Data updated successfully")
                else:
                    print("Ошибка выполнения запроса:", self.query.lastError().text())
            elif temp_value == 'product4':
                if self.query.exec(f"DELETE FROM RAWDATA WHERE ClassPart = '{values_to_remove[x]}' AND PartReference = '{values_to_remove[x + 1]}'"):
                    print("Data updated successfully")
                else:
                    print("Ошибка выполнения запроса:", self.query.lastError().text())

            x = x + 2

        self.tree_model.submit()
        self.tree_view.setModel(self.tree_model)

    def addRecord(self):
        # Создаем стек отмены, если он еще не был создан
        if not hasattr(self, 'undo_stack'):
            self.undo_stack = []
        selection_model = self.tree_view.selectionModel()
        selected_indexes = selection_model.selectedIndexes()

        # Определяем родительскую группу выделенной строки
        parent_index = selected_indexes[0].parent()

        # Создаем пустую строку для отображения в TreeView
        empty_row = [QStandardItem('-') for _ in range(5)]  # Определение переменной empty_row
        parent_item = self.tree_model.itemFromIndex(parent_index)
        row_position = selected_indexes[0].row() + 1

        # Проверяем, есть ли уже такие элементы в дереве
        existing_items = [parent_item.child(i) for i in range(parent_item.rowCount())]
        if empty_row not in existing_items:
            if row_position <= parent_item.rowCount():  # Проверяем, что позиция вставки превышает количество элементов в родительском элементе
                parent_item.insertRow(row_position, empty_row)
                # Сохраняем информацию о последней добавленной записи
                self.last_added_index = (parent_item, row_position)
                # Обновляем представление (TreeView) с новой строкой
                self.tree_view.setModel(self.tree_model)

                # Если операция добавления прошла успешно, добавляем действие в стек отмены
                self.undo_stack.append(('add', parent_item, row_position, empty_row.copy()))  # Создаем копию empty_row
            else:
                print("Ошибка: Позиция вставки превышает количество элементов в родительском элементе.")
        else:
            print("Ошибка: Пустая строка уже существует в данной группе.")

        class_part = parent_item.text()
        if self.query.exec(f"INSERT INTO RAWDATA (ClassPart, PartReference, ManufacturerPartNumber, Manufacturer, Quantity, Description) VALUES ('{class_part}', '-', '-', '-', '-', '-')"):
            print("Data add successfully")
            if self.query.exec(
                    f"UPDATE RAWDATA SET PartReference = '-', ManufacturerPartNumber = '-', Manufacturer = '-', Quantity = '-', Description  = '-' WHERE ClassPart = '{class_part}' AND PartReference = '-' AND ManufacturerPartNumber = '-'"):
                print("Data updated successfully")
            else:
                print("Ошибка выполнения запроса:", self.query.lastError().text())
        else:
            print("Ошибка выполнения запроса:", self.query.lastError().text())

    #процедура проведения классификации компонентов. Вызывается в режиме редактирования сырых импортированных данных.
    #Работает исключительно с базой данных
    def Classification(self):
        if self.DataBase.open():
            query = QtSql.QSqlQuery()
            query_new = QtSql.QSqlQuery()
            query_upd = QtSql.QSqlQuery()
            query.clear()
            #формируем запрос из таблицы импортированных данных
            Result = query.exec("SELECT ClassPart, PartReference FROM RAWDATA")
            if Result:
                while query.next():
                    currentClassPart = query.value("ClassPart")
                    currentPartReference = query.value("PartReference")
                    #если текущий  класс не равен классу из таблицы ALPHABETPOSITION в базе данных
                    # то обновляем поле
                    tableClassPart = getRefdesDescription(query_new,getRefdes(currentPartReference))
                    if  tableClassPart != currentClassPart:
                        query_upd.exec("UPDATE RAWDATA SET ClassPart = " + '"' + tableClassPart + '"' + " WHERE PartReference = " + '"' + currentPartReference + '"')
                        self.signalLogConsole.emit(
                            ["W: Изменение классификации позиции " + currentPartReference + ": было " + currentClassPart + " стало " + tableClassPart])

                self.stm.select()

    def Group(self):
        global temp_value
        if self.rbtnTypeGroup.isChecked():
            temp_value = 'product1'
            self.populateTreeFromDatabase('product1')
        elif self.rbtnNoGroup.isChecked():
            self.stm.setFilter("")
            self.stm.select()
        elif self.rbtnPartNumber.isChecked():
            temp_value = 'product2'
            self.populateTreeFromDatabase('product2')
        elif self.rbtnManufacturerGroup.isChecked():
            temp_value = 'product4'
            self.populateTreeFromDatabase('product4')

            grouped_data = defaultdict(list)
            while self.query.next():
                product1 = self.query.value(3)
                product2 = self.query.value(0)
                product3 = self.query.value(1)
                product4 = self.query.value(2)
                product5 = self.query.value(4)
                product6 = self.query.value(5)
                if product1 not in grouped_data:
                    grouped_data[product1] = []
                grouped_data[product1].append((product2, product3, product4, product5, product6))

            for product1, products in grouped_data.items():
                sorted_products = natsorted(products, key=lambda x: x[0], alg=ns.IGNORECASE)
                parent_item = QStandardItem(product1)
                self.root_item.appendRow(parent_item)
                for product in sorted_products:
                    child_items = [QStandardItem(str(product[i])) for i in range(5)]
                    parent_item.appendRow(child_items)

            self.tree_view.setModel(self.tree_model)
            column = 0
            self.table_view.setIndexWidget(self.tree_view.model().index(0, column), self.tree_view)
            self.table_view.update()  # Обновляем представление TableView
            self.tree_view.update()  # Обновляем представление TreeView


        #print("DEBUG70:")
        #self.mod = QtSql.QSqlQuery("SELECT id, PartReference, ManufacturerPartNumber, Manufacturer FROM RAWDATA GROUP BY ManufacturerPartNumber")
        #self.mod.exec()
        #self.model = QtSql.QSqlQueryModel()
        #self.model.setQuery(self.mod)

        #self.editTab.setModel(self.model)
        #print("DEBUG71:")
        ##self.stm.select()
        #print("DEBUG72:")
        return 0

    def Cancel(self):
        try:
            if hasattr(self, 'undo_stack') and self.undo_stack:
                # Получаем все действия из стека отмены
                actions_to_undo = list(reversed(self.undo_stack))
                print("Действия для отмены:", actions_to_undo)  # Выводим содержимое стека отмены для отладки
                for action, parent_item, row_position, removed_row in actions_to_undo:
                    if action == 'add':
                        parent_item.removeRow(row_position)
                        print("Добавление элемента отменено.")
                    elif action == 'del':
                        # Получаем родительский индекс и вставляем удаленную строку
                        self.tree_model.insertRow(row_position, removed_row)
                        print("Удаление элемента отменено.")
                print("Содержимое стека отмены перед очисткой:", self.undo_stack)
                self.undo_stack.clear()  # Очищаем стек отмены
                self.tree_model.layoutChanged.emit()
            else:
                print("Отмененное действие не найдено в стеке отмены.")
        except Exception as e:
            print("Ошибка при отмене:", e)

Очень прошу помочь мне с этой проблемой. Нужно, чтобы кнопка "Отмена" отменяла все действия, совершенные пользователем.


Ответы (0 шт):