PySide + SQLAlchemy 中 QTableView 的“模型”设计

Posted

技术标签:

【中文标题】PySide + SQLAlchemy 中 QTableView 的“模型”设计【英文标题】:Design of the 'model' for QTableView in PySide + SQLAlchemy 【发布时间】:2012-05-16 01:11:53 【问题描述】:

我的问题实际上是如何设置可以从 PySide 的 QTableView 类访问的 SQLAlchemy 声明性模型。

我只是想从本质上为Object Relational tutorial 实现一个前端

很遗憾,我有几点困惑。我会试着解释我在哪里。

我遵循了 SQLAlchemy 教程,直到我有两个相关的表,并且可以毫无问题地操作/查询这些表。尝试使用我自己的模型建立QTableView class 显然需要setData() method,或者使用默认模型需要setItem() method。

所以问题是如何设计模型。我认为这意味着定义这两种方法之一来查询/修改数据库。我不知道这样做的正确方法。

该模型应该像用户名字和姓氏一样在几行上重复,直到显示所有地址,然后转到下一个用户。我可以使用嵌套的 for 循环来执行此操作,以便在提示符处打印此内容,但我不认为制作一个大列表是可行的方法,因为这似乎违背了首先拥有数据库的意义......

我也不知道当数据库增长时会发生什么,整个表会被实例化并保存在内存中,还是 Qt 会在用户滚动时加载行和列以供查看?

这里有很多文字很抱歉,但想清楚一点。如果有任何其他我可以添加的内容,请告诉我。或者,如果我完全走错了路......

from PySide import QtCore, QtGui
from sqlalchemy import Column, Integer, String, Text, Sequence, ForeignKey, Date, Boolean, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref, aliased
import datetime


engine = create_engine('sqlite:///reminder.db')

Base = declarative_base()

class User(Base):
    __tablename__ = 'users_db'
    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    lastname = Column(String)
    firstname = Column(String)
    contact = Column(String)
    history = Column(Text)
    notes = Column(Text)

    addresses = relationship('Address', order_by='Address.id', 
                               backref='user', cascade='all, delete, delete-orphan')


    def __init__(self, firstname, lastname, contact):
        self.firstname = firstname
        self.lastname = lastname
        self.contact = contact

    def __repr__(self):
        return "<User('0', '1', '2')>".format(self.firstname, self.lastname, self.contact)


class Address(Base):
    __tablename__ = 'addresses_db'
    id = Column(Integer, primary_key=True)
    address = Column(String(150))
    date = Column(Date)
    check1 = Column(Boolean)
    check2 = Column(Boolean)

    user_id = Column(Integer, ForeignKey('users_db.id'))

    def __init__(self, address, date):
        self.address = address
        self.date = date
        self.check1 = False
        self.check2 = False

    def __repr__(self):
        return "<Address('0', '1')>".format(self.address, self.date)

if __name__ == '__main__':
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    header = [User.firstname, User.lastname, nextaddressfromUser]

>>> for user in session.query(User).all():
...     for addr in user.addresses:
...         print user.firstname, user.lastname, addr.address

【问题讨论】:

【参考方案1】:

首先,让我们忘记查询,并使用您正在使用的循环。您在 UI 中寻找的是基本的东西。我从缺乏文档中发现,对于基本的事情,最好使用比 QWhateverView 更好的QTableWidget(或任何QWhateverWidget)。您不需要为简单的事情定义自己的模型。因此,我将向您展示如何使用 QTableWidget 来做到这一点。您需要为(行,列)的每个单元格创建一个 QTableWidgetItem。我遇到的一个问题是在添加它们之前必须设置行数。无论如何,在这里:

import sys
from PySide import QtGui, QtCore

class Test(QtGui.QWidget):

    def __init__(self, rows):
        super(Test, self).__init__()

        self.table = QtGui.QTableWidget()
        self.table.setColumnCount(3)
        # Optional, set the labels that show on top
        self.table.setHorizontalHeaderLabels(("First Name", "Last Name", "Address"))

        self.table.setRowCount(len(rows))
        for row, cols in enumerate(rows):
            for col, text in enumerate(cols):
                table_item = QtGui.QTableWidgetItem(text)
                # Optional, but very useful.
                table_item.setData(QtCore.Qt.UserRole+1, user)
                self.table.setItem(row, col, table_item)

        # Also optional. Will fit the cells to its contents.
        self.table.resizeColumnsToContents()

        # Just display the table here.
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.table)
        self.setLayout(layout)

if __name__ == "__main__":
    # ...
    rows = []
    # Here I have to fill it in an array, because you need to know the number of rows before adding... There might be a better solution though.
    for user in session.query(User).all():
        for addr in user.addresses:
            # These are the columns on each row (firstname, lastname, address)
            rows.append((user.firstname, user.lastname, addr.address))

    app = QtGui.QApplication(sys.argv)
    test = Test(rows)
    test.show()
    app.exec_()

您可能会考虑使用的另一件事是QTreeWidget,它支持多列,您可以让它看起来像一个表格,但默认情况下没有可编辑的单元格,它可能更适合您的数据。

现在对于查询,您可能希望将其设为一个查询,以避免循环遍历结果并且必须为每个用户执行一个额外的查询。比如:

query = session.query(User.firstname, User.lastname, Address.address).filter(Address.user_id == User.id)
    for row in query.all():
        # firstname, lastname, address = row
        rows.append(row)

对于很多行,我认为有一个解决方案,但是您需要定义自己的模型并在查询中使用LIMITs。由于缺乏文档和教程,这并不容易......

附带说明,您不需要在您的 Address 和 User 类上定义 __init__ 方法,因为您没有做任何特别的事情,SQLAlchemy 可以自动为您完成。您可以直接在 Column 定义中定义默认值。

更新:以使用QTableWidgetItem.setData 为例,假设我们想在双击时删除用户。我们将使用itemDoubleClicked 信号。

# in the __init__ function
self.table.itemDoubleClicked.connect(self.onItemDoubleClick)

# in onItemDoubleClicked function
def onItemDoubleClicked(self, item):
    # Every data has a role, you can specify your own (it's an integer) as long as it's greater than UserRole. Others are used internally, like DisplayRole and some others you can find in the QtCore package.
    # You can use data with other widgets also, not just TableWidgets.
    user = item.data(QtCore.Qt.UserRole+1)
    # you get a session however you want, then delete the user. This object is the same as the one you passed earlier when creating the item, it can be whatever you like.
    session.delete(user)
    session.commit()

【讨论】:

看起来不错。来晚了,暂时不能试试。你能解释一下 table_item.setData(QtCore.Qt.UserRole+1, user) 这样你就可以实际操作用户对象,而不仅仅是显示他的名字......(例如)。我将更新我的答案以显示一个示例。 非常感谢您的解释。我认为您只是在发布的第一个代码中简洁明了,并且我可以轻松地在正确的位置添加对正确用户的引用,并且您没有使用任何魔法。 是的,当然,那里的“用户”没有任何意义。您可以将其附加到列列表中并在显示结果之前将其删除。没有魔法:)

以上是关于PySide + SQLAlchemy 中 QTableView 的“模型”设计的主要内容,如果未能解决你的问题,请参考以下文章

结合 Qgraphics 和 sqlalchemy 时出现元类错误

ELK stat集群部署+Grafana及可视化图形

PySide/pyQt 在 QTableView 中显示数据

PySide 子线程中的计时器

Pyside2.QtCore.QObject 不是 MyTableWidget 的直接基类——PySide2 出错

pySide: ExtensionLoader_Pyside_QtGUI.py 找不到指定的模块