You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySide6/PyQt6库存系统:编辑删除功能仅对首行生效求助

库存系统编辑/删除行异常问题

我用PySide6、PyQt6(QtDesigner)开发库存系统的数据库模块,接近完成时遇到bug:编辑或删除任意行时,始终只有第一行会被选中并操作,导致异常行为。相关代码如下:

ButtonWidget代码

class ButtonWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 创建两个按钮并添加到水平布局
        edit_icon = QtGui.QIcon("Icons/edit.svg")
        self.edit_button = QtWidgets.QPushButton()
        self.edit_button.setIcon(edit_icon)
        delete_icon = QtGui.QIcon("Icons/trash3.svg")
        self.delete_button = QtWidgets.QPushButton()
        self.delete_button.setIcon(delete_icon)
        layout = QHBoxLayout()
        layout.addWidget(self.delete_button)
        layout.addWidget(self.edit_button)
        self.setLayout(layout)

EditDialog代码

class EditDialog(QDialog):
    def __init__(self, parent=None, row_data=None):
        super(EditDialog, self).__init__(parent)
        self.ui = Ui_Edit_Dialog()
        self.ui.setupUi(self)

        # 设置输入框初始值
        self.ui.item_id_lineEdit.setText(str(row_data[0]))
        self.ui.product_name_lineEdit.setText(row_data[1])
        self.ui.category_lineEdit.setText(row_data[2])
        self.ui.quantity_lineEdit.setText(row_data[3])
        self.ui.price_lineEdit.setText(row_data[4])

MainWindow代码

class MainWindow(QMainWindow):
    def __init__(self): 
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # 页面切换
        self.ui.btn_menu_1.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_1))
        self.ui.btn_menu_2.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_2))
        self.ui.btn_menu_3.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_3))
        self.ui.btn_menu_4.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_4))
        self.show()
        # 页面4功能绑定
        self.ui.add_btn.clicked.connect(self.add_btn_function)
        self.loadData()
        self.ui.search_lineEdit.returnPressed.connect(self.search_function)
    
    def add_btn_function(self):
        product_name = self.ui.product_name_lineEdit.text()
        category = self.ui.category_lineEdit.text()
        quantity = self.ui.quantity_lineEdit.text()
        price = self.ui.price_lineEdit.text()

        if not all([product_name, category, quantity, price]):
            QMessageBox.warning(self, '错误', '请填写所有字段。')
            return
        else:
            cursor.execute(f"INSERT INTO items (productname, category, quantity, price) VALUES (?, ?, ?, ?)", (product_name, category, quantity, price))
            conn.commit()
            print("成功创建条目: ", product_name, category, quantity, price)
            # 清空输入框
            self.ui.product_name_lineEdit.setText('')
            self.ui.category_lineEdit.setText('')
            self.ui.quantity_lineEdit.setText('')
            self.ui.price_lineEdit.setText('')
            # 刷新表格
            self.loadData()
    
    def search_function(self):
        search_term = self.ui.search_lineEdit.text()
        if not search_term:
            self.loadData()
            return

        # 清空表格现有内容
        self.ui.tableWidget.setRowCount(0)

        # 查询匹配搜索词的条目
        cursor.execute(f"SELECT * FROM items WHERE productname LIKE '%{search_term}%' OR category LIKE '%{search_term}%'")
        rows = cursor.fetchall()

        # 将匹配条目添加到表格
        for row in rows:
            row_position = self.ui.tableWidget.rowCount()
            self.ui.tableWidget.insertRow(row_position)
            for i, item in enumerate(row):
                self.ui.tableWidget.setItem(row_position, i, QTableWidgetItem(str(item)))
    
    # 加载数据
    def loadData(self):
        connection = sqlite3.connect("DBMS.db")
        cursor = connection.cursor()
        query = "SELECT * FROM items"
        result = cursor.execute(query)
        self.ui.tableWidget.setRowCount(0)
        tablerow = 0
        for row_number, row_data in enumerate(result):
            self.ui.tableWidget.insertRow(row_number)
            for column_number, data in enumerate(row_data):
                self.ui.tableWidget.setItem(row_number, column_number, QTableWidgetItem(str(data))) 

            btn_widget = ButtonWidget()
            btn_widget.delete_button.clicked.connect(lambda row=row_number: self.delete_function(row))
            btn_widget.edit_button.clicked.connect(lambda row=row_number: self.edit_function(row))
            self.ui.tableWidget.setCellWidget(row_number, 5, btn_widget)

    # 删除功能
    def delete_function(self, row):
        # 显示确认对话框
        confirm = QMessageBox.question(self, '确认删除', '确定要删除这个条目吗?', QMessageBox.Yes | QMessageBox.No)
        if confirm == QMessageBox.Yes:
            # 获取要删除的条目ID
            item_id = self.ui.tableWidget.item(row, 0).text()
            # 从数据库删除条目
            cursor.execute(f"DELETE FROM items WHERE id = ?", (item_id))
            conn.commit()
            print(f"成功删除ID为 {item_id} 的条目")
            # 从表格移除行
            self.ui.tableWidget.removeRow(row)

    def edit_function(self,row):
        # 获取选中行的数据
        row_data = []
        for column in range(self.ui.tableWidget.columnCount()):
            item = self.ui.tableWidget.item(row, column)
            if item is not None:
                row_data.append(item.text())
            else:
                row_data.append('')
        
        # 打开编辑对话框
        dialog = EditDialog(self, row_data=row_data)
        if dialog.exec() == QDialog.Accepted:
            # 获取修改后的值
            productname = dialog.ui.product_name_lineEdit.text()
            category = dialog.ui.category_lineEdit.text()
            quantity = dialog.ui.quantity_lineEdit.text()
            price = dialog.ui.price_lineEdit.text()
            
            # 更新数据库值
            connection = sqlite3.connect("DBMS.db")
            cursor = connection.cursor()
            query = ("UPDATE items SET productname = ?, category = ?, quantity = ?, price = ? WHERE id = ?")
            values = (productname, category, quantity, price, item_id) # 注释:ID从1开始,所以用row+1?
            cursor.execute(query, values)
            connection.commit()
            
            # 刷新表格
            self.loadData()

问题分析与修复方案

核心问题1:全局数据库连接/游标混用

你在add_btn_function、delete_function中直接使用全局的cursor和conn,但loadData又创建了局部连接,这种混用会导致数据库状态不一致,可能引发数据读取错误,进而影响行定位逻辑。

修复:
所有数据库操作统一使用局部连接,避免全局变量干扰:

def delete_function(self, row):
    confirm = QMessageBox.question(self, '确认删除', '确定要删除这个条目吗?', QMessageBox.Yes | QMessageBox.No)
    if confirm == QMessageBox.Yes:
        item_id = self.ui.tableWidget.item(row, 0).text()
        # 新建局部连接
        connection = sqlite3.connect("DBMS.db")
        cursor = connection.cursor()
        # 注意元组格式,末尾加逗号避免类型错误
        cursor.execute("DELETE FROM items WHERE id = ?", (item_id,))
        connection.commit()
        connection.close() # 操作完成后关闭连接
        print(f"成功删除ID为 {item_id} 的条目")
        self.ui.tableWidget.removeRow(row)

核心问题2:edit_function中未定义item_id

在编辑功能的数据库更新代码里,直接使用了未定义的item_id变量,会触发NameError,导致更新失败,可能被误判为行选中问题。

修复:
从选中行的数据中提取item_id:

def edit_function(self,row):
    row_data = []
    for column in range(self.ui.tableWidget.columnCount()):
        item = self.ui.tableWidget.item(row, column)
        if item is not None:
            row_data.append(item.text())
        else:
            row_data.append('')
    
    dialog = EditDialog(self, row_data=row_data)
    if dialog.exec() == QDialog.Accepted:
        productname = dialog.ui.product_name_lineEdit.text()
        category = dialog.ui.category_lineEdit.text()
        quantity = dialog.ui.quantity_lineEdit.text()
        price = dialog.ui.price_lineEdit.text()
        # 从行数据中获取item_id
        item_id = row_data[0]
        
        connection = sqlite3.connect("DBMS.db")
        cursor = connection.cursor()
        query = ("UPDATE items SET productname = ?, category = ?, quantity = ?, price = ? WHERE id = ?")
        values = (productname, category, quantity, price, item_id)
        cursor.execute(query, values)
        connection.commit()
        connection.close()
        
        self.loadData()

核心问题3:SQL注入风险(额外优化)

search_function中使用字符串拼接构造SQL语句,存在SQL注入风险,同时可能导致搜索逻辑异常。

修复:
改用参数化查询:

def search_function(self):
    search_term = self.ui.search_lineEdit.text()
    if not search_term:
        self.loadData()
        return

    self.ui.tableWidget.setRowCount(0)
    
    connection = sqlite3.connect("DBMS.db")
    cursor = connection.cursor()
    # 参数化查询,避免注入风险
    cursor.execute("SELECT * FROM items WHERE productname LIKE ? OR category LIKE ?", (f'%{search_term}%', f'%{search_term}%'))
    rows = cursor.fetchall()
    connection.close()

    for row in rows:
        row_position = self.ui.tableWidget.rowCount()
        self.ui.tableWidget.insertRow(row_position)
        for i, item in enumerate(row):
            self.ui.tableWidget.setItem(row_position, i, QTableWidgetItem(str(item)))

内容的提问来源于stack exchange,提问作者Hydroxy21

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 09:56:21