Инвертирование столбцов и строк в QTableView с помощью PyQt5

Для виджета PyQT5 мне нужно отображать данные из SQL-запроса в базу данных SQLite с инвертированными / повернутыми столбцами и строками. В идеале в QTableView. (В этой таблице будет только 2 столбца: один для имен предыдущих столбцов и один для их значений. Таблица предназначена для отображения статистики, которая будет агрегирована в SQL-запросе, который вернет только одну строку. Поэтому я хочу перейти от одна строка с несколькими столбцами, до 2 столбцов с несколькими строками.)

Я придумал обходной путь, который делает правильные вещи, используя вместо этого QFormLayout, но он выглядит некрасиво и кажется очень неэлегантным. (См. Метод display_data(self).)

#!/usr/bin/python3

from PyQt5 import QtSql
from PyQt5.QtWidgets import (QFormLayout, QWidget, 
                             QLabel, QLineEdit, QApplication)

import sys

class InvertedTable(QWidget):
    def __init__(self, company):
        super().__init__()
        self.db_file = "test.db"
        self.company = company
        self.create_connection()
        self.fill_table()
        self.init_UI()
        self.display_data()

    def create_connection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(self.db_file)
        if not self.db.open():
            print("Cannot establish a database connection to {}!".format(self.db_file))
            return False

    def fill_table(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()
        q.exec_("DROP TABLE IF EXISTS Cars;")
        q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""") 
        q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)") 
        q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
        self.db.commit()

    def init_UI(self):
        self.resize(300,100)
        self.layout = QFormLayout()
        self.setLayout(self.layout)

    def display_data(self):
        query = "select * from cars where company = '{}'".format(self.company)
        q = QtSql.QSqlQuery()
        q.exec_(query)
        self.check_error(q)
        record = q.record()
        columns = record.count()
        q.next()
        for i in range(columns):
            column_name = record.field(i).name()
            col_field = QLabel(column_name, self)
            value = q.value(i)
            value_field = QLineEdit(self)
            value_field.setText(value)
            self.layout.addRow(col_field, value_field)

    def closeEvent(self, e):
        if (self.db.open()):
            self.db.close()

    def check_error(self, q):
        lasterr = q.lastError()
        if lasterr.isValid():
            print(lasterr.text())
            self.db.close()
            exit(1)


def main():
    app = QApplication(sys.argv)
    ex = InvertedTable("Honda")
    ex.show()

    result = app.exec_()
    sys.exit(result)


if __name__ == '__main__':
    main()      

Как правильно сделать это с помощью QTableView?

Я действительно не понимаю, в чем проблема. Почему нельзя просто заполнить таблицу необходимыми значениями? Как вы думаете, почему необходимо использовать макет формы?

ekhumoro 27.03.2018 19:10

@ekhumoro: да, я могу это сделать. Стало кажется неуклюжим и неэлегантным запрашивать, а затем заполнять таблицу, но я не знал, как заставить QTableModel вести себя так, как я хочу, поэтому я использовал FormLayout, чтобы проиллюстрировать, что мне нужно.

CodingCat 28.03.2018 11:04

Было бы намного проще заполнить таблицу с помощью QStandardItemModel - все, что вам нужно сделать, это инвертировать обычные циклы строки / столбца. Так что я до сих пор не понимаю, в чем смысл макета формы. (И, конечно, я знаю, что это не самый эффективный способ взаимодействия с базой данных - мне просто было любопытно, почему вы не можете использовать таблицу).

ekhumoro 28.03.2018 19:19
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
781
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Правильный способ работы с QTableView - это иметь QTableModel.

Как назло, существует QSqlTableModel, который позволяет вам построить модель таблицы на основе таблицы SQL. Funt ответил на аналогичный вопрос, указав на QIdentityProxyModel, который можно использовать «поверх этого» для изменения представления модели данных путем переопределения методов mapToSource и mapFromSource.

Есть также способы транспонировать результат запроса SQL непосредственно из команды SQL. См. здесь.

Также стоит прочитать: Программирование в представлении модели с помощью Qt. Это версия C++, но PyQt следует тем же принципам (и классы имеют то же имя).

Надеюсь, это поможет.

Спасибо за указатели. (Особенно полезно было последнее - большая часть документации Qt не очень удобна для новичков, и трудно найти эти сложные части.) Я действительно надеялся, что любая из ProxyModels Qt предоставит в качестве опции переворачивание столбцов и строк, не имея в подкласс, поскольку мне кажется, что это, вероятно, обычная проблема.

CodingCat 28.03.2018 11:02
Ответ принят как подходящий

После еще нескольких поисков и прочтения полезных указателей, оставленных @PlikPlok, я нашел решение здесь:

По-видимому, эта функциональность не предоставляется никакими Qt-классами из коробки, поэтому вам нужно создать подкласс как QAbstractProxyModel, так и QSqlRelationalDelegate, а затем использовать их в своей таблице:

#!/usr/bin/python3

import sys
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QApplication,
                             QGridLayout, QTableView)
from PyQt5.Qt import (QModelIndex, QAbstractProxyModel, QSqlRelationalDelegate)
from PyQt5.QtCore import Qt

class FlippedProxyModel(QAbstractProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)

    def mapFromSource(self, index):
        return self.createIndex(index.column(), index.row())

    def mapToSource(self, index):
        return self.sourceModel().index(index.column(), index.row(), QModelIndex())

    def columnCount(self, parent):
        return self.sourceModel().rowCount(QModelIndex())

    def rowCount(self, parent):
        return self.sourceModel().columnCount(QModelIndex())

    def index(self, row, column, parent):
        return self.createIndex(row, column)

    def parent(self, index):
        return QModelIndex()

    def data(self, index, role):
        return self.sourceModel().data(self.mapToSource(index), role)

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            return self.sourceModel().headerData(section, Qt.Vertical, role)
        if orientation == Qt.Vertical:
            return self.sourceModel().headerData(section, Qt.Horizontal, role)


class FlippedProxyDelegate(QSqlRelationalDelegate):
    def createEditor(self, parent, option, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super().createEditor(parent, option, base_index)

    def setEditorData(self, editor, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super().setEditorData(editor, base_index)

    def setModelData(self, editor, model, index):
        base_model = model.sourceModel()
        base_index = model.mapToSource(index)
        return super().setModelData(editor, base_model, base_index)


class InvertedTable(QWidget):
    def __init__(self, company):
        super().__init__()
        self.db_file = "test.db"
        self.company = company
        self.create_connection()
        self.fill_table()
        self.create_model()
        self.init_UI()

    def create_connection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(self.db_file)
        if not self.db.open():
            print("Cannot establish a database connection to {}!".format(self.db_file))
            return False

    def fill_table(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()
        q.exec_("DROP TABLE IF EXISTS Cars;")
        q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""") 
        q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)") 
        q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
        self.db.commit()

    def create_model(self):
        self.model = QtSql.QSqlTableModel()
        q = QtSql.QSqlQuery()
        query = """SELECT * from cars where company = 'Honda'
         """
        q.exec_(query)
        self.model.setQuery(q)
        self.proxy = FlippedProxyModel() # use flipped proxy model
        self.proxy.setSourceModel(self.model)

    def init_UI(self):
        self.grid = QGridLayout()
        self.setLayout(self.grid)
        self.table = QTableView()
        self.table.setModel(self.proxy)
        self.table.setItemDelegate(FlippedProxyDelegate(self.table)) # use flipped proxy delegate
        self.table.horizontalHeader().hide()

        self.grid.addWidget(self.table, 0, 0)

    def closeEvent(self, e):
        if (self.db.open()):
            self.db.close()

    def check_error(self, q):
        lasterr = q.lastError()
        if lasterr.isValid():
            print(lasterr.text())
            self.db.close()
            exit(1)


def main():
    app = QApplication(sys.argv)
    ex = InvertedTable("Honda")
    ex.show()

    result = app.exec_()
    sys.exit(result)


if __name__ == '__main__':
    main()      

Другие вопросы по теме