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

问题描述 投票:7回答:1

我的问题是如何设置一个可以从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
python sql sqlalchemy pyside
1个回答
7
投票

首先,让我们忘记查询,并使用您正在使用的循环。您在UI中寻找的是一件基本的事情。我从缺乏文档中发现,最好使用QTableWidget(或任何QWhateverWidget)比QWhateverView更基本的东西。您不需要为简单的事物定义自己的模型。所以,我将向您展示如何使用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。由于缺乏文档和教程,这并不容易......

作为旁注,您不需要在地址和用户类上定义__init__方法,因为您没有做任何特殊的事情,SQLAlchemy可以自动为您执行此操作。您可以直接在列定义中定义默认值。

更新:有关使用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()
© www.soinside.com 2019 - 2024. All rights reserved.