mimepyside6dropqsqltablemodel

Drop Mime data (csv file) into QTableView with custom QSqlTableModel is not working


I'm using a QTableView along with a superclassed QSqlTableModel to display a sqlite table into Qt and inserting new records dropping a csv file.

I have followed the doc and came up with the example reported below. I have recreated a very light reproducible example to show what is happening to my code, no sanity check or quality code has been intentionally used. It's tested against PySide6

import sys

from qtpy.QtWidgets import QApplication, QTableView, QWidget
from qtpy.QtCore import QModelIndex, QMimeData, Qt
from qtpy.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery
from pandas import read_csv

def create_table():
    # Dummy very simple table
    _query_str = """CREATE TABLE MyTable ( 
    ID     INTEGER PRIMARY KEY AUTOINCREMENT,
    Field1 INTEGER,
    Field2 TEXT);"""

    query = QSqlQuery(db=db, query=_query_str)
    query.exec_()

class MyTableModel(QSqlTableModel):

    def __init__(self, table_name, db):
        QSqlTableModel.__init__(self, db=db)
        self.setTable(table_name)

    def canDropMimeData(self, data: QMimeData, action: Qt.DropAction, row: int, column: int, parent: QModelIndex) -> bool:
        return True # <-- Just for the example

    def supportedDropActions(self) -> Qt.DropAction:
        return Qt.DropAction.CopyAction | Qt.DropAction.MoveAction | Qt.DropAction.LinkAction

    def dropMimeData(self, data: QMimeData, action: Qt.DropAction, row: int, column: int, parent: QModelIndex) -> bool:
        csv_filename = data.urls()[0].toLocalFile()
        df = read_csv(csv_filename, delimiter=',', header=0)

        for _, row in df.iterrows():
            record = self.record()
            record.remove(0) # <-- Remove the ID field
            record.setValue('Field1', row['Field1'].values[0])
            record.setValue('Field2', row['Field2'].values[0])
            self.insertRecord(-1, record)


if __name__ == '__main__':
    # In memory database just for the purpose of the example
    db = QSqlDatabase.addDatabase("QSQLITE", ":memory:")
    db.open()
    if not db.open():
        raise "Database not opened"
    create_table()
    app = QApplication([])
    table = QTableView()
    model = MyTableModel('MyTable', db)
    table.setModel(model)
    table.setAcceptDrops(True)
    table.show()

    sys.exit(app.exec_())

What I get is that canDropMimeData and supportedDropActions are correctly called, but (using debug) dropMimeData is never called

And the below image shows that, even if canDropMimeData returns True, the file seems not to be accepted.

enter image description here

Edit 1 - QSqlTableModel issue

I found out that the problem is with QSqlTableModel. If I use a bare QStandardItemModel, everything works fine. Any work-around?


Solution

  • By default, item models don't provide drag and drop support.

    In order to properly allow that, many aspects have to be checked, including that the flags() returned by any index that would accept drop must also have the Qt.ItemIsDropEnabled.

    If you want to allow that only for the model (drop on an empty area, not on items), that index would be the root index, aka, an invalid index:

        def flags(self, index):
            flags = super().flags(index)
            if not index.isValid():
                flags |= Qt.ItemIsDropEnabled
            return flags