Наследник от QSqlTableModel не понимает NULL

Рейтинг: 0Ответов: 2Опубликовано: 24.07.2023

в PyQT5 собираю обычную стандартную связку sqlite + QSqlTableModel + QTableView. Всё работает, записи редактируются, изменения в бд сохраняются.
Далее, допустим, я хочу добавить выравнивание текста в ячейках через Qt.TextAlignmentRole (не важно), для этого я создаю свою модель, наследуюсь от QSqlTableModel и переопределяю метод data:

class MyTableModelSql(QSqlTableModel):
       
    def data(self, idx, role):
        return super().data(idx, role)

model = MyTableModelSql(self, db=db)

С этого момента редактировать строки в которых есть пустые NULL ячейки уже не получается, в вертикальном заголовке появляется знак (!)

введите сюда описание изображения введите сюда описание изображения

Первая ссылка гугла ведет на этот вопрос на офф. форуме qt. Есть помеченный ответ. Автор ответа объясняет, что проблема связана возвращаемым QVariant своей модели, который отличается от возвращаемого от стандартной модели QSqlTableModel. Причина в особенности языка python, в котором нет типа Null, в С++ такой проблемы нет, и предлагает какой-то страшный костыль по обвязке(обертке?) результата. Костыль у меня не заработал.

Собственно 2 вопроса:

  • тот ответ был дан в 2018 году, за 5 лет возможно проблема была решена стандартными средствами PyQT, если кто-то знает решение прошу помочь
  • как мне установить выравнивание текста в ячейках без перенаследования класса QSqlTableModel, есть ли другие способы?

Прикладываю минимальный код, полностью воспроизводящий ошибку:

import sys
from PyQt5.QtSql import QSqlTableModel, QSqlDatabase
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QMainWindow, QVBoxLayout

db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("main.db")
db.open()

# run once
# query0 = QSqlQuery("DROP TABLE testtable")
# query1 = QSqlQuery("CREATE TABLE testtable (col1 TEXT, col2 TEXT, col3 TEXT)")
# query2 = QSqlQuery("""INSERT INTO testtable (col1, col2, col3) VALUES 
#                   ('full row', 3453, 345),
#                   ('half row', NULL, 6343),
#                   ('empty row', NULL, NULL)""")

class MyTableModelSql(QSqlTableModel):
   
    def data(self, idx, role):
        return super().data(idx, role)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # model = QSqlTableModel редактирование записей работает
        # model = MyTableModelSql редактирование записей не работает
        model = MyTableModelSql(self, db=db)
        model.setTable('testtable')
        model.select()
        self.view = QTableView()
        self.view.setModel(model)

        layout = QVBoxLayout()
        layout.addWidget(self.view)
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Ответы

▲ 1Принят

Код из форума не сработал т.к. QVariant.value - функция поэтому нужно использовать QVariant.value() а не QVariant.value Вот исправленный код:

import sys

from PyQt5 import sip
from PyQt5.QtCore import QVariant
from PyQt5.QtSql import QSqlTableModel, QSqlDatabase, QSqlQuery
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QMainWindow, QVBoxLayout

db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("main.db")
db.open()


# run once
# query0 = QSqlQuery("DROP TABLE testtable")
# query1 = QSqlQuery("CREATE TABLE testtable (col1 TEXT, col2 TEXT, col3 TEXT)")
# query2 = QSqlQuery("""INSERT INTO testtable (col1, col2, col3) VALUES
#                   ('full row', 3453, 345),
#                   ('half row', NULL, 6343),
#                   ('empty row', NULL, NULL)""")

class MyTableModelSql(QSqlTableModel):

    def data(self, idx, role):
        was_enabled = sip.enableautoconversion(QVariant, False)
        value = super().data(idx, role)
        sip.enableautoconversion(QVariant, was_enabled)

        if not value.isValid():
            return None
        if value.isNull():
            return None

        return value.value()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # model = QSqlTableModel редактирование записей работает
        # model = MyTableModelSql редактирование записей не работает
        model = MyTableModelSql(self, db=db)
        model.setTable('testtable')
        model.select()
        self.view = QTableView()
        self.view.setModel(model)

        layout = QVBoxLayout()
        layout.addWidget(self.view)
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()
▲ 0

Не знаю точно как Phyton (я на с++ пишу), но вы role проверяете? DisplayRole или там еще много разных вариантов вроде бы.

И меня один встречный вопрос мучает давно. Можно в питоне зайти отладчиком в функцию например в super().data(idx, role) ?

Далее, допустим, я хочу добавить выравнивание текста в ячейках через Qt.TextAlignmentRole (не важно), для этого я создаю свою модель, наследуюсь от QSqlTableModel и переопределяю метод data:

Я конечно могу ошибаться, но роль TextAlignmentRole вы должны реализовывать сами, создавать контейнер, где хранятся элайменты для колонок и т.д.

QSqlTableModel реализует только DisplayRole и EditRole. Во всяком случае так в Qt 4.